// // 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)); }