ClientKitApi.cpp #1

  • //
  • guest/
  • james_creasy/
  • ClientKit/
  • ClientKitApi.cpp
  • View
  • Commits
  • Open Download .zip Download (13 KB)
//
// ClientKitApi.cpp - defines interface to JavaScript and includes the Perforce C++ API
//


#include "ClientKitApi.h"
#include "mainwindow.h"

#include "clientapi.h"
#include <enviro.h>

#include <QString>
#include <QStringList>
#include <QFile>
#include <QDir>
#include <QProcess>
#include <QDesktopServices>
#include <QUrl>
#include <QVariantMap>
#include <QVariantList>
#include <QCoreApplication>
#include <QLocale>
#include <QWebPage>


class HClientUser : public ClientUser
{
public:
	HClientUser(ClientKitApi* ha) {mClientKitApi = ha;}

	virtual void OutputInfo( char /*level*/, const char * data ) 
	{
		QVariantMap entry;
		entry.insert(data, "");
		mTaggedList.append(entry);
	}

	virtual void OutputStat(StrDict *varList) 
	{
		StrRef var, val;
        QVariantMap entry;

		for( int i = 0; varList->GetVar( i, var, val ); i++ ) 
		{
			if( var == "func" || var == P4Tag::v_specFormatted ) 
				continue;
            entry.insert(var.Text(),val.Text());
		}
        mTaggedList.append(entry);
	}

	virtual void OutputError( const char * errBuf)
	{
		QString errmsg = QString("OutputError: " + QString(errBuf));
		mClientKitApi->errorList()->append(errmsg);
	}

	virtual void InputData( StrBuf *strbuf, Error *e )
	{
		QString inputData = mClientKitApi->inputData();
		if (inputData.isEmpty())
		{
			e->Set( E_FAILED, "No user-input supplied." );
			mClientKitApi->errorList()->append("Error: No input data. Did you forget to call p4.setInput()?");
		}

		if (mClientKitApi)
			strbuf->Set(inputData.toLocal8Bit());	// todo: refactor into constructor
	}

	virtual int	Help( char *const *help )
	{
		QString errmsg =  QString("Help: " + QString(*help));
		mClientKitApi->errorList()->append(errmsg);
		return 0;
	}

	void clearOutputMap() {mTaggedList.clear();}

	QVariantList getOutputList() {return mTaggedList;}

private:
	QVariantList	mTaggedList;
	ClientKitApi*	mClientKitApi;
};


ClientKitApi::ClientKitApi(QObject* /*parent*/, MainWindow* mainWindow, QString pointerName)
{
	mMainWindow = mainWindow;
	mMyJSPointerName = pointerName;
}

QString ClientKitApi::inputData()
{
	return mInputForm;
}

// todo: this code could be moved to class Connection
bool ClientKitApi::updateConnection(QString p, QString u, QString c)
{
	bool changedSomething;

	if (!p.isEmpty())
	{
		changedSomething = (mConnection.port == p);
		mConnection.port = p;
	}

	if (!u.isEmpty())
	{
        changedSomething = (mConnection.user == u);
        mConnection.user = u;
	}

	if (!c.isEmpty())
	{
        changedSomething = (mConnection.client == c);
        mConnection.client = c;
	}

	return changedSomething;
}

// Create a new pointer that's exposed to the JS from P4ClientKit
//
// Allows JS to manage connections similar to the Perforce derived APIs
//
// todo: Perhaps return a object with error/success message?
//
// The reason that this call does not take arguments for the connection 
// (which is safer) is because it's slightly less flexible to do it that way
//

QString ClientKitApi::createP4(const QString& newP4PointerName)
{
	QString returnMessage;

	// 1. Check to see if we already have a p4 pointer of this name
	if (mMainWindow->p4PointersList().contains(newP4PointerName))
	{
		returnMessage="Error: p4 pointer name '"+newP4PointerName+"' already created";
		return returnMessage;	
	}

	if (mMainWindow->p4PointersList().count() > 1000)
	{
		returnMessage="Error: Exceeds allowable number of p4 pointers (1000)";	// 1000 might be too many
		return returnMessage;	
	}

	// create and expose the new pointer
	mMainWindow->createClientKitApi(newP4PointerName);
	returnMessage="Success: p4 pointer name '"+newP4PointerName+"' created";
	return returnMessage;	
}

//
// 1. A callback for AJAX style calling needs to be added to run()
//    e.g. p4.run("info","",callbackFunction);
//
// 1a. Run the commands in a different thread so we can implement non-blocking
//     callbacks (AJAX)
//
QVariant ClientKitApi::run( const QString& command)
{
    QVariantList args;
	return run(command, args);
}

// Convenience override 
QVariant ClientKitApi::run(const QVariantList& cmdTokens)
{
	if (cmdTokens.isEmpty())
		return QVariant();

        QVariantList tokens = cmdTokens;

        QString cmd = tokens.takeFirst().toString();
	return run(cmd, tokens);
}

// Main p4.run() function
QVariant ClientKitApi::run(const QString& command, const QVariantList& args)
{
	ClientApi client;
	Connection con = connection();

	if (con.isEmpty())
	{
		QVariantMap errorMap;			
		errorMap.insert("Error", "No connection defined");
		return errorMap;
	}

	client.SetPort( con.port.toLatin1() );	// todo: Don't limit to Latin1 charset
	client.SetUser( con.user.toLatin1() );
	client.SetClient( con.client.toLatin1() );
	if (!con.charset.toLatin1().isEmpty())
		client.SetCharset( con.charset.toLatin1() );

	client.SetProtocol("tag", "");	// Pretty much always use tagged. There are a few exceptions like diff2

	Error e;
	client.Init( &e );

	int numArgs = args.count();

	HClientUser ui(this);

	QByteArray ba0;
	QByteArray ba1;
	QByteArray ba2;
	QByteArray ba3;
	QByteArray ba4;
	QByteArray ba5;
	char* arg0;
	char* arg1;
	char* arg2;
	char* arg3;
	char* arg4;
	char* arg5;

	if (numArgs > 0) //todo: process more than 6 args
	{
        ba0 = args[0].toString().toLatin1();
		arg0 = ba0.data(); 
		if (numArgs > 1)
		{
            ba1 = args[1].toString().toLatin1();
			arg1 = ba1.data(); 
			if (numArgs > 2)
			{
                ba2 = args[2].toString().toLatin1();
				arg2 = ba2.data(); 
				if (numArgs > 3)
				{
                    ba3 = args[3].toString().toLatin1();
					arg3 = ba3.data(); 
					if (numArgs > 4)
					{
                        ba4 = args[4].toString().toLatin1();
						arg4 = ba4.data(); 
						if (numArgs > 5)
						{
                            ba5 = args[5].toString().toLatin1();
							arg5 = ba5.data(); 
							if (numArgs > 6)
							{	
								mErrorList += "Warning: Exceeded maximum number of processed arguments";
							}
						}
					}
				}
			}
		}
	}

	char    *argv[] = { arg0, arg1, arg2, arg3, arg4, arg5 };
	int      argc = numArgs;
	client.SetArgv( argc, argv );
	ui.clearOutputMap();
	mErrorList.clear();
    client.Run( command.toLocal8Bit(), &ui );
    client.Final( &e );

	QVariantList infoList = ui.getOutputList();

	QVariantMap returnMap;
	returnMap.insert("size", infoList.count());
	returnMap.insert("data", infoList);
	if (mErrorList.count() > 0)
	{
		returnMap.insert("error", mErrorList);
	}

	return returnMap;	// returning a QMap is the magic that causes a JavaScript object to appear in the JavaScript code
}

// 
// 1. todo: Design a security feature to restrict where and what types of files can be written by the JavaScript
//
bool ClientKitApi::createFile(const QString& filename, const QString& fileContents)
{
    // add security check
    QString fName = filename;
    int last = filename.lastIndexOf(QDir::separator());

    //check for a directory pre-pended on the file, and create a directory if needed
    if( last > -1 )
    {
        //separate the directory and create it
        QString directory = filename.left(last);
        QDir dir;
        dir.mkdir(directory);
        dir.setCurrent(directory);

        //separate the file name
        fName = filename.right(filename.length() - (last + 1));
    }

    QFile file(fName);
    if (file.open(QFile::ReadWrite))
	{
		QByteArray fileArray = fileContents.toAscii();
        file.write(fileArray);
		file.close();
		return true;
	}
	return false;
}

// todo: Change so this does not set the configuration on a "get" method
//       Because we don't hang on the connection, we don't have a client api to 
//       read this from. 
//
QString ClientKitApi::getConfig()
{
    ClientApi client;
    Connection con = connection();

    if (con.isEmpty())
    {
        return "No connection defined";
    }

    client.SetPort( con.port.toLatin1() );
    client.SetUser( con.user.toLatin1() );
    client.SetClient( con.client.toLatin1() );

    client.SetProtocol("tag", "");	// Pretty much always use tagged. There are a few exceptions like diff2

    Error e;
    client.Init( &e );

    qDebug(client.GetConfig().Text());

    return client.GetConfig().Text();
}


//
// 1. todo: Check to see if this is the desired way to read a file. If translation
//    from 8 bit into 16 bit Unicode is desired, should use QTextStream instead
//
QVariant ClientKitApi::readFile(const QString& fname)
{
	 QVariantList retList;
     QFile file(fname);
     if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
	 {
		 retList.append("Could not read file '" + fname + "'");
         return retList;
	 }

     while (!file.atEnd()) 
	 {
         QByteArray line = file.readLine();
         retList.append(line);
     }
	QVariant retVar(retList);
	 return retVar;
}

// Returns list of all files and directories in ClientKitApi's current directory
//
// ls() of an empty string returns the drives
// 
// Although it perhaps seems awkward to force the JS to include the dir name
// (rather than having a target dir) it does keep behavior out of the C++ container
//
//	1. todo: Could expose more powerful entryList methods (file filters, sorting) if needed
//
QVariant ClientKitApi::ls(const QString& dirname)
{
    return ls(dirname, false);
}

QVariant ClientKitApi::ls(const QString& dirname, bool fileInfo)
{
    if (fileInfo)
    {
        if (dirname.isEmpty())
        {
            QString returnMessage = "Error: Please include a local directory path.";
            return returnMessage;
        }
        else
        {
            QDir dir(dirname);
            QFileInfoList slist = dir.entryInfoList();
            QVariantList outputList;
            for (int i=2; i < slist.count(); i++)
            {
                QVariantMap outputMap;
                
                if (slist[i].isDir())
                    outputMap.insert("type","dir");
                else if (slist[i].isFile())
                    outputMap.insert("type","file");
                
                outputMap.insert("path",slist[i].canonicalFilePath());
                outputMap.insert("isHidden",slist[i].isHidden());
                
                outputList.append(outputMap);
            }
            return outputList;
        }
    }
    else
    {
        QStringList slist;
        if (dirname.isEmpty())
        {	
            QFileInfoList drives = QDir::drives();
            
            for (int i = 0; i < drives.size(); ++i) 
            {
                QString drive = drives.at(i).absoluteFilePath();
                slist.append(drive);
            }
        }
        else
        {
            QDir dir(dirname);
            slist = dir.entryList();
        }
        QVariant retVar(slist);
        return retVar;
    }
}

QVariant ClientKitApi::currentDir()
{
	QDir cdir = QDir::current();
	return cdir.absolutePath();
}

QVariant ClientKitApi::applicationPath()
{
	QString appDirPath = QCoreApplication::applicationDirPath();
	return QVariant(appDirPath);
}

/////////////////////////////// Container API ///////////////////////////////////////////////

void ClientKitApi::setWindowSize(const int& x, const int& y)
{
	mMainWindow->resize(x, y);
}

void ClientKitApi::setWindowStyle(const QString& styleName)
{
	mMainWindow->setWindowStyle(styleName);
}

QVariant ClientKitApi::getWindowSize()
{
	QVariantMap size;
	size.insert("width", mMainWindow->size().width());
	size.insert("height", mMainWindow->size().height());
	return QVariant(size);
}

QString ClientKitApi::getWindowStyle()
{
	return mMainWindow->getWindowStyle();
}

QStringList ClientKitApi::getWindowStyles()
{
	return mMainWindow->getWindowStyles();
}

void ClientKitApi::setWindowTitle(const QString & windowTitle)
{
	mMainWindow->setWindowTitle(windowTitle);
}

QVariant ClientKitApi::getSystemLocale()
{
	QLocale sl = QLocale::system();
	QLocale::Language lang = sl.language();
	QString langStr = QLocale::languageToString(lang);
	QString locStr = sl.name();
	QVariantMap retMap;
	retMap.insert("Locale Name", locStr);
	retMap.insert("Language Name", langStr);
	retMap.insert("Language id", lang);
	return QVariant(retMap);
}

//
// This is even more dangerous than createFile!
//
// Definitely add security, either by defining a whitelist, or prompting user
//
// Most users should choose to comment out this function
//

QString ClientKitApi::startProcess(const QString& program, const QStringList& arguments)
{
    QProcess *process = new QProcess();
    process->setProcessChannelMode(QProcess::MergedChannels);

    process->start(program, arguments);

    if( !process->waitForStarted() )
        return "process failed to start";


    if(!process->waitForFinished())
        return "process failed to finish";

    return process->readAll();
}

// This is useful for opening, e.g., a help page (opens in system default browser)
bool ClientKitApi::openUrlInBrowser(QString filepath)
{
    return QDesktopServices::openUrl(QUrl(filepath));
}





# Change User Description Committed
#1 8060 james_creasy Client Kit for Perforce