Version 2.6
[qgit4/redivivus.git] / src / myprocess.cpp
blob1795971b5a43abbdff86757773a34059229d92af
1 /*
2 Description: interface to sync and async external program execution
4 Author: Marco Costalba (C) 2005-2007
6 Copyright: See COPYING file that comes with this distribution
8 */
9 #include <QApplication>
10 #include <QTime>
11 #include "exceptionmanager.h"
12 #include "common.h"
13 #include "domain.h"
14 #include "myprocess.h"
16 MyProcess::MyProcess(QObject *go, Git* g, const QString& wd, bool err) : QProcess(g) {
18 guiObject = go;
19 git = g;
20 workDir = wd;
21 runOutput = NULL;
22 receiver = NULL;
23 errorReportingEnabled = err;
24 canceling = async = isWinShell = isErrorExit = false;
27 bool MyProcess::runAsync(SCRef rc, QObject* rcv, SCRef buf) {
29 async = true;
30 runCmd = rc;
31 receiver = rcv;
32 setupSignals();
33 if (!launchMe(runCmd, buf))
34 return false; // caller will delete us
36 return true;
39 bool MyProcess::runSync(SCRef rc, QByteArray* ro, QObject* rcv, SCRef buf) {
41 async = false;
42 runCmd = rc;
43 runOutput = ro;
44 receiver = rcv;
45 if (runOutput)
46 runOutput->clear();
48 setupSignals();
49 if (!launchMe(runCmd, buf))
50 return false;
52 QTime t;
53 t.start();
55 busy = true; // we have to wait here until we exit
57 while (busy) {
58 waitForFinished(20); // suspend 20ms to let OS reschedule
60 if (t.elapsed() > 200) {
61 EM_PROCESS_EVENTS;
62 t.restart();
65 return !isErrorExit;
68 void MyProcess::setupSignals() {
70 connect(git, SIGNAL(cancelAllProcesses()),
71 this, SLOT(on_cancel()));
73 connect(this, SIGNAL(readyReadStandardOutput()),
74 this, SLOT(on_readyReadStandardOutput()));
76 connect(this, SIGNAL(finished(int, QProcess::ExitStatus)),
77 this, SLOT(on_finished(int, QProcess::ExitStatus)));
79 if (receiver) {
81 connect(this, SIGNAL(readyReadStandardError ()),
82 this, SLOT(on_readyReadStandardError()));
84 connect(this, SIGNAL(procDataReady(const QByteArray&)),
85 receiver, SLOT(procReadyRead(const QByteArray&)));
87 connect(this, SIGNAL(eof()), receiver, SLOT(procFinished()));
89 Domain* d = git->curContext();
90 if (d)
91 connect(d, SIGNAL(cancelDomainProcesses()), this, SLOT(on_cancel()));
94 void MyProcess::sendErrorMsg(bool notStarted, SCRef err) {
96 if (!errorReportingEnabled)
97 return;
99 QString errorDesc(readAllStandardError());
100 errorDesc.prepend(err);
102 if (notStarted)
103 errorDesc = QString::fromLatin1("Unable to start the process!");
105 const QString cmd(arguments.join(" ")); // hide any QUOTE_CHAR or related stuff
106 MainExecErrorEvent* e = new MainExecErrorEvent(cmd, errorDesc);
107 QApplication::postEvent(guiObject, e);
110 bool MyProcess::launchMe(SCRef runCmd, SCRef buf) {
112 arguments = splitArgList(runCmd);
113 if (arguments.isEmpty())
114 return false;
116 setWorkingDirectory(workDir);
117 if (!QGit::startProcess(this, arguments, buf, &isWinShell)) {
118 sendErrorMsg(true);
119 return false;
121 return true;
124 void MyProcess::on_readyReadStandardOutput() {
126 if (canceling)
127 return;
129 if (receiver)
130 emit procDataReady(readAllStandardOutput());
132 else if (runOutput)
133 runOutput->append(readAllStandardOutput());
136 void MyProcess::on_readyReadStandardError() {
138 if (canceling)
139 return;
141 if (receiver)
142 emit procDataReady(readAllStandardError()); // redirect to stdout
143 else
144 dbs("ASSERT in myReadFromStderr: NULL receiver");
147 void MyProcess::on_finished(int exitCode, QProcess::ExitStatus exitStatus) {
149 // Checking exingStatus is not reliable under Windows where if the
150 // process was terminated with TerminateProcess() from another
151 // application its value is still NormalExit
153 // Checking exit code for a failing command is unreliable too, as
154 // exmple 'git status' returns 1 also without errors.
156 // On Windows exit code seems reliable in case of a command wrapped
157 // in Window shell interpreter.
159 // So to detect a failing command we check also if stderr is not empty.
160 QString errorDesc(readAllStandardError());
162 isErrorExit = (exitStatus != QProcess::NormalExit)
163 || (exitCode != 0 && isWinShell)
164 || !errorDesc.isEmpty()
165 || canceling;
167 if (!canceling) { // no more noise after cancel
169 if (receiver)
170 emit eof();
172 if (isErrorExit)
173 sendErrorMsg(false, errorDesc);
175 busy = false;
176 if (async)
177 deleteLater();
180 void MyProcess::on_cancel() {
182 canceling = true;
184 #ifdef Q_OS_WIN32
185 kill(); // uses TerminateProcess
186 #else
187 terminate(); // uses SIGTERM signal
188 #endif
189 waitForFinished();
192 const QStringList MyProcess::splitArgList(SCRef cmd) {
193 // return argument list handling quotes and double quotes
194 // substring, as example from:
195 // cmd some_arg "some thing" v='some value'
196 // to (comma separated fields)
197 // sl = <cmd,some_arg,some thing,v='some value'>
199 // early exit the common case
200 if (!( cmd.contains(QGit::QUOTE_CHAR)
201 || cmd.contains("\"")
202 || cmd.contains("\'")))
203 return cmd.split(' ', QString::SkipEmptyParts);
205 // we have some work to do...
206 // first find a possible separator
207 const QString sepList("#%&!?"); // separator candidates
208 int i = 0;
209 while (cmd.contains(sepList[i]) && i < sepList.length())
210 i++;
212 if (i == sepList.length()) {
213 dbs("ASSERT no unique separator found.");
214 return QStringList();
216 const QChar& sepChar(sepList[i]);
218 // remove all spaces
219 QString newCmd(cmd);
220 newCmd.replace(QChar(' '), sepChar);
222 // re-add spaces in quoted sections
223 restoreSpaces(newCmd, sepChar);
225 // QUOTE_CHAR is used internally to delimit arguments
226 // with quoted text wholly inside as
227 // arg1 = <[patch] cool patch on "cool feature">
228 // and should be removed before to feed QProcess
229 newCmd.remove(QGit::QUOTE_CHAR);
231 // QProcess::setArguments doesn't want quote
232 // delimited arguments, so remove trailing quotes
233 QStringList sl(newCmd.split(sepChar, QString::SkipEmptyParts));
234 QStringList::iterator it(sl.begin());
235 for ( ; it != sl.end(); ++it) {
236 if (((*it).left(1) == "\"" && (*it).right(1) == "\"") ||
237 ((*it).left(1) == "\'" && (*it).right(1) == "\'"))
238 *it = (*it).mid(1, (*it).length() - 2);
240 return sl;
243 void MyProcess::restoreSpaces(QString& newCmd, const QChar& sepChar) {
244 // restore spaces inside quoted text, supports nested quote types
246 QChar quoteChar;
247 bool replace = false;
248 for (int i = 0; i < newCmd.length(); i++) {
250 const QChar& c = newCmd[i];
252 if ( !replace
253 && (c == QGit::QUOTE_CHAR[0] || c == '\"' || c == '\'')
254 && (newCmd.count(c) % 2 == 0)) {
256 replace = true;
257 quoteChar = c;
258 continue;
260 if (replace && (c == quoteChar)) {
261 replace = false;
262 continue;
264 if (replace && c == sepChar)
265 newCmd[i] = QChar(' ');