myprocess.cpp

Go to the documentation of this file.
00001 /*
00002         Description: interface to sync and async external program execution
00003 
00004         Author: Marco Costalba (C) 2005-2006
00005 
00006         Copyright: See COPYING file that comes with this distribution
00007 
00008 */
00009 #include <unistd.h> // usleep(), environ
00010 #include <qapplication.h>
00011 #include <qeventloop.h>
00012 #include "exceptionmanager.h"
00013 #include "common.h"
00014 #include "domain.h"
00015 #include "myprocess.h"
00016 
00017 MyProcess::MyProcess(QObject *go, Git* g, const QString& wd, bool err) : QProcess(g) {
00018 
00019         guiObject = go;
00020         git = g;
00021         workDir = wd;
00022         receiver = NULL;
00023         runOutput = NULL;
00024         errorReportingEnabled = err;
00025         canceling = async = false;
00026         exitStatus = true;
00027 }
00028 
00029 bool MyProcess::runAsync(SCRef rc, QObject* rcv, SCRef buf, QStringList* env) {
00030 
00031         async = true;
00032         runCmd = rc;
00033         receiver = rcv;
00034         setupSignals();
00035         if (!launchMe(runCmd, buf, env))
00036                 return false; // caller will delete us
00037 
00038         return true;
00039 }
00040 
00041 bool MyProcess::runSync(SCRef rc, QByteArray* ro, QObject* rcv, SCRef buf, QStringList* env) {
00042 
00043         async = false;
00044         runCmd = rc;
00045         runOutput = ro;
00046         receiver = rcv;
00047         if (runOutput)
00048                 runOutput->resize(0);
00049 
00050         setupSignals();
00051 
00052         busy = true; // we have to wait here until we exit
00053 
00054         if (!launchMe(runCmd, buf, env))
00055                 return false;
00056 
00057         // we are now leaving our safe domain context and
00058         // jump right in to processEvents() hyperspace
00059         Domain* d = git->curContext();
00060         git->setCurContext(NULL);
00061 
00062         EM_BEFORE_PROCESS_EVENTS;
00063 
00064         while (busy) {
00065                 /* we need a on_readyReadStdout() call here to avoid rare
00066                  * hangs that could occur if launched program fullfills
00067                  * its stdout buffer while we are sleeping
00068                  */
00069                 on_readyReadStdout();
00070                 usleep(20000); // suspend 20ms to let OS reschedule
00071                 isRunning();
00072         }
00073 
00074         EM_AFTER_PROCESS_EVENTS;
00075 
00076         if (git->curContext())
00077                 qDebug("ASSERT in MyProcess::runSync, context is %p "
00078                        "instead of NULL", (void*)git->curContext());
00079 
00080         git->setCurContext(d); // restore our context
00081 
00082         return exitStatus;
00083 }
00084 
00085 void MyProcess::setupSignals() {
00086 
00087         connect(git, SIGNAL(cancelAllProcesses()), this, SLOT(on_cancel()));
00088         connect(this, SIGNAL(readyReadStdout()), this, SLOT(on_readyReadStdout()));
00089         connect(this, SIGNAL(processExited()), this, SLOT(on_processExited()));
00090         if (receiver) {
00091                 connect(this, SIGNAL(readyReadStderr()), this, SLOT(on_readyReadStderr()));
00092                 connect(this, SIGNAL(procDataReady(const QByteArray&)),
00093                         receiver, SLOT(on_procDataReady(const QByteArray&)));
00094                 connect(this, SIGNAL(eof()), receiver, SLOT(on_eof()));
00095         }
00096         Domain* d = git->curContext();
00097         if (d)
00098                 connect(d, SIGNAL(cancelDomainProcesses()), this, SLOT(on_cancel()));
00099 }
00100 
00101 void MyProcess::sendErrorMsg(bool notStarted) {
00102 
00103         if (!errorReportingEnabled)
00104                 return;
00105 
00106         QString errorDesc(readStderr());
00107         if (notStarted)
00108                 errorDesc = QString::fromAscii("Unable to start the process!");
00109 
00110         const QString cmd(arguments().join(" ")); // hide any QUOTE_CHAR or related stuff
00111         MainExecErrorEvent* e = new MainExecErrorEvent(cmd, errorDesc);
00112         QApplication::postEvent(guiObject, e);
00113 }
00114 
00115 void MyProcess::appendSystemEnvironment(QStringList* env) {
00116 
00117         if (!env)
00118                 return;
00119 
00120         /* global 'environ' is an array of pointers to the
00121          * environment strings, defined in <unistd.h>
00122          */
00123         char** envval = ::environ;
00124         for ( ; *envval; envval++)
00125                 *env << QString(*envval);
00126 }
00127 
00128 bool MyProcess::launchMe(SCRef runCmd, SCRef buf, QStringList* env) {
00129 
00130         const QStringList sl(splitArgList(runCmd));
00131         setArguments(sl);
00132         setWorkingDirectory(workDir);
00133         appendSystemEnvironment(env);
00134 
00135         connect(this, SIGNAL(launchFinished()), this, SLOT(on_launchFinished()));
00136         isLaunching = true;
00137 
00138         bool ok = launch(buf, env);
00139         if (!ok)
00140                 sendErrorMsg(true);
00141 
00142         while (isLaunching)
00143                 EM_PROCESS_EVENTS;
00144 
00145         return ok;
00146 }
00147 
00148 void MyProcess::on_launchFinished() {
00149 
00150         disconnect(this, SIGNAL(launchFinished()), this, SLOT(on_launchFinished()));
00151         isLaunching = false;
00152 }
00153 
00154 void MyProcess::on_readyReadStdout() {
00155 // workaround pipe buffer size limit. In case of big output, buffer
00156 // can became full and an hang occurs, so we read all data as soon
00157 // as possible and store it in runOutput
00158 
00159         if (canceling)
00160                 return;
00161 
00162         if (receiver)
00163                 emit procDataReady(readStdout());
00164 
00165         else if (runOutput)
00166                 QGit::baAppend(*runOutput, readStdout());
00167         else
00168                 readStdout(); // always flush input buffer
00169 }
00170 
00171 void MyProcess::on_readyReadStderr() {
00172 
00173         if (canceling)
00174                 return;
00175 
00176         if (receiver)
00177                 emit procDataReady(readStderr()); // redirect to stdout
00178         else
00179                 dbs("ASSERT in myReadFromStderr: NULL receiver");
00180 }
00181 
00182 void MyProcess::on_processExited() {
00183 
00184         if (canceling || !normalExit() || canReadLineStderr())
00185                 exitStatus = false;
00186 
00187         if (!canceling) { // no more noise after cancel
00188 
00189                 if (receiver)
00190                         emit eof();
00191 
00192                 if (!exitStatus)
00193                         sendErrorMsg();
00194         }
00195         busy = false;
00196         if (async)
00197                 deleteLater();
00198 }
00199 
00200 void MyProcess::on_cancel() {
00201 
00202         canceling = true;
00203         QProcess::tryTerminate();
00204 }
00205 
00206 const QStringList MyProcess::splitArgList(SCRef cmd) {
00207 // return argument list handling quotes and double quotes
00208 // substring, as example from:
00209 // cmd some_arg "some thing" v='some value'
00210 // to (comma separated fields)
00211 // sl = <cmd,some_arg,some thing,v='some value'>
00212 
00213         // early exit the common case
00214         if (!(   cmd.contains(QGit::QUOTE_CHAR)
00215               || cmd.contains("\"")
00216               || cmd.contains("\'")))
00217                 return QStringList::split(' ', cmd);
00218 
00219         // we have some work to do...
00220         // first find a possible separator
00221         const QString sepList("#%&!?"); // separator candidates
00222         uint i = 0;
00223         while (cmd.contains(sepList[i]) && i < sepList.length())
00224                 i++;
00225 
00226         if (i == sepList.length()) {
00227                 dbs("ASSERT no unique separator found");
00228                 return QStringList();
00229         }
00230         const QChar& sepChar(sepList[i]);
00231 
00232         // remove all spaces
00233         QString newCmd(cmd);
00234         newCmd.replace(QChar(' '), sepChar);
00235 
00236         // re-add spaces in quoted sections
00237         restoreSpaces(newCmd, sepChar);
00238 
00239         // QUOTE_CHAR is used internally to delimit arguments
00240         // with quoted text wholly inside as
00241         // arg1 = <[patch] cool patch on "cool feature">
00242         // and should be removed before to feed QProcess
00243         newCmd.remove(QGit::QUOTE_CHAR);
00244 
00245         // QProcess::setArguments doesn't want quote
00246         // delimited arguments, so remove trailing quotes
00247         QStringList sl(QStringList::split(sepChar, newCmd));
00248         QStringList::iterator it(sl.begin());
00249         for ( ; it != sl.end(); ++it) {
00250                 if (((*it).left(1) == "\"" && (*it).right(1) == "\"") ||
00251                    ((*it).left(1) == "\'" && (*it).right(1) == "\'"))
00252                         *it = (*it).mid(1, (*it).length() - 2);
00253         }
00254         return sl;
00255 }
00256 
00257 void MyProcess::restoreSpaces(QString& newCmd, SCRef sepChar) {
00258 // restore spaces inside quoted text, supports nested quote types
00259 
00260         QString quoteChar;
00261         bool replace = false;
00262         for (uint i = 0; i < newCmd.length(); i++) {
00263 
00264                 const QChar& c = newCmd[i];
00265 
00266                 if (    !replace
00267                     && (c == QGit::QUOTE_CHAR || c == "\"" || c == "\'")
00268                     && (newCmd.contains(c) % 2 == 0)) {
00269 
00270                                 replace = true;
00271                                 quoteChar = c;
00272                                 continue;
00273                 }
00274                 if (replace && (c == quoteChar)) {
00275                         replace = false;
00276                         continue;
00277                 }
00278                 if (replace && c == sepChar)
00279                         newCmd[i] = QChar(' ');
00280         }
00281 }

Generated on Fri Dec 7 21:57:38 2007 for QGit by  doxygen 1.5.3