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
9 #include <QApplication>
11 #include "exceptionmanager.h"
14 #include "myprocess.h"
16 MyProcess::MyProcess(QObject
*go
, Git
* g
, const QString
& wd
, bool err
) : QProcess(g
) {
23 errorReportingEnabled
= err
;
24 canceling
= async
= isWinShell
= isErrorExit
= false;
27 bool MyProcess::runAsync(SCRef rc
, QObject
* rcv
, SCRef buf
) {
33 if (!launchMe(runCmd
, buf
))
34 return false; // caller will delete us
39 bool MyProcess::runSync(SCRef rc
, QByteArray
* ro
, QObject
* rcv
, SCRef buf
) {
49 if (!launchMe(runCmd
, buf
))
55 busy
= true; // we have to wait here until we exit
58 waitForFinished(20); // suspend 20ms to let OS reschedule
60 if (t
.elapsed() > 200) {
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
)));
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();
91 connect(d
, SIGNAL(cancelDomainProcesses()), this, SLOT(on_cancel()));
94 void MyProcess::sendErrorMsg(bool notStarted
, SCRef err
) {
96 if (!errorReportingEnabled
)
99 QString
errorDesc(readAllStandardError());
100 errorDesc
.prepend(err
);
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())
116 setWorkingDirectory(workDir
);
117 if (!QGit::startProcess(this, arguments
, buf
, &isWinShell
)) {
124 void MyProcess::on_readyReadStandardOutput() {
130 emit
procDataReady(readAllStandardOutput());
133 runOutput
->append(readAllStandardOutput());
136 void MyProcess::on_readyReadStandardError() {
142 emit
procDataReady(readAllStandardError()); // redirect to stdout
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()
167 if (!canceling
) { // no more noise after cancel
173 sendErrorMsg(false, errorDesc
);
180 void MyProcess::on_cancel() {
185 kill(); // uses TerminateProcess
187 terminate(); // uses SIGTERM signal
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
209 while (cmd
.contains(sepList
[i
]) && i
< sepList
.length())
212 if (i
== sepList
.length()) {
213 dbs("ASSERT no unique separator found.");
214 return QStringList();
216 const QChar
& sepChar(sepList
[i
]);
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);
243 void MyProcess::restoreSpaces(QString
& newCmd
, const QChar
& sepChar
) {
244 // restore spaces inside quoted text, supports nested quote types
247 bool replace
= false;
248 for (int i
= 0; i
< newCmd
.length(); i
++) {
250 const QChar
& c
= newCmd
[i
];
253 && (c
== QGit::QUOTE_CHAR
[0] || c
== '\"' || c
== '\'')
254 && (newCmd
.count(c
) % 2 == 0)) {
260 if (replace
&& (c
== quoteChar
)) {
264 if (replace
&& c
== sepChar
)
265 newCmd
[i
] = QChar(' ');