[go: up one dir, main page]

File: myprocess.cpp

package info (click to toggle)
qgit 2.3-1
  • links: PTS
  • area: main
  • in suites: squeeze
  • size: 1,152 kB
  • ctags: 1,477
  • sloc: cpp: 11,857; makefile: 51; sh: 39
file content (267 lines) | stat: -rw-r--r-- 6,488 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
/*
	Description: interface to sync and async external program execution

	Author: Marco Costalba (C) 2005-2007

	Copyright: See COPYING file that comes with this distribution

*/
#include <QApplication>
#include <QTime>
#include "exceptionmanager.h"
#include "common.h"
#include "domain.h"
#include "myprocess.h"

MyProcess::MyProcess(QObject *go, Git* g, const QString& wd, bool err) : QProcess(g) {

	guiObject = go;
	git = g;
	workDir = wd;
	runOutput = NULL;
	receiver = NULL;
	errorReportingEnabled = err;
	canceling = async = isWinShell = isErrorExit = false;
}

bool MyProcess::runAsync(SCRef rc, QObject* rcv, SCRef buf) {

	async = true;
	runCmd = rc;
	receiver = rcv;
	setupSignals();
	if (!launchMe(runCmd, buf))
		return false; // caller will delete us

	return true;
}

bool MyProcess::runSync(SCRef rc, QByteArray* ro, QObject* rcv, SCRef buf) {

	async = false;
	runCmd = rc;
	runOutput = ro;
	receiver = rcv;
	if (runOutput)
		runOutput->clear();

	setupSignals();
	if (!launchMe(runCmd, buf))
		return false;

	QTime t;
	t.start();

	busy = true; // we have to wait here until we exit

	while (busy) {
		waitForFinished(20); // suspend 20ms to let OS reschedule

		if (t.elapsed() > 200) {
			EM_PROCESS_EVENTS;
			t.restart();
		}
	}
	return !isErrorExit;
}

void MyProcess::setupSignals() {

	connect(git, SIGNAL(cancelAllProcesses()),
	        this, SLOT(on_cancel()));

	connect(this, SIGNAL(readyReadStandardOutput()),
	        this, SLOT(on_readyReadStandardOutput()));

	connect(this, SIGNAL(finished(int, QProcess::ExitStatus)),
	        this, SLOT(on_finished(int, QProcess::ExitStatus)));

	if (receiver) {

		connect(this, SIGNAL(readyReadStandardError ()),
		        this, SLOT(on_readyReadStandardError()));

		connect(this, SIGNAL(procDataReady(const QByteArray&)),
		        receiver, SLOT(procReadyRead(const QByteArray&)));

		connect(this, SIGNAL(eof()), receiver, SLOT(procFinished()));
	}
	Domain* d = git->curContext();
	if (d)
		connect(d, SIGNAL(cancelDomainProcesses()), this, SLOT(on_cancel()));
}

void MyProcess::sendErrorMsg(bool notStarted, SCRef err) {

	if (!errorReportingEnabled)
		return;

	QString errorDesc(readAllStandardError());
	errorDesc.prepend(err);

	if (notStarted)
		errorDesc = QString::fromAscii("Unable to start the process!");

	const QString cmd(arguments.join(" ")); // hide any QUOTE_CHAR or related stuff
	MainExecErrorEvent* e = new MainExecErrorEvent(cmd, errorDesc);
	QApplication::postEvent(guiObject, e);
}

bool MyProcess::launchMe(SCRef runCmd, SCRef buf) {

	arguments = splitArgList(runCmd);
	if (arguments.isEmpty())
		return false;

	setWorkingDirectory(workDir);
	if (!QGit::startProcess(this, arguments, buf, &isWinShell)) {
		sendErrorMsg(true);
		return false;
	}
	return true;
}

void MyProcess::on_readyReadStandardOutput() {

	if (canceling)
		return;

	if (receiver)
		emit procDataReady(readAllStandardOutput());

	else if (runOutput)
		runOutput->append(readAllStandardOutput());
}

void MyProcess::on_readyReadStandardError() {

	if (canceling)
		return;

	if (receiver)
		emit procDataReady(readAllStandardError()); // redirect to stdout
	else
		dbs("ASSERT in myReadFromStderr: NULL receiver");
}

void MyProcess::on_finished(int exitCode, QProcess::ExitStatus exitStatus) {

	// Checking exingStatus is not reliable under Windows where if the
	// process was terminated with TerminateProcess() from another
	// application its value is still NormalExit
	//
	// Checking exit code for a failing command is unreliable too, as
	// exmple 'git status' returns 1 also without errors.
	//
	// On Windows exit code seems reliable in case of a command wrapped
	// in Window shell interpreter.
	//
	// So to detect a failing command we check also if stderr is not empty.
	QString errorDesc(readAllStandardError());

	isErrorExit =   (exitStatus != QProcess::NormalExit)
	             || (exitCode != 0 && isWinShell)
	             || !errorDesc.isEmpty()
	             ||  canceling;

	if (!canceling) { // no more noise after cancel

		if (receiver)
			emit eof();

		if (isErrorExit)
			sendErrorMsg(false, errorDesc);
	}
	busy = false;
	if (async)
		deleteLater();
}

void MyProcess::on_cancel() {

	canceling = true;

#ifdef Q_OS_WIN32
	kill(); // uses TerminateProcess
#else
	terminate(); // uses SIGTERM signal
#endif
	waitForFinished();
}

const QStringList MyProcess::splitArgList(SCRef cmd) {
// return argument list handling quotes and double quotes
// substring, as example from:
// cmd some_arg "some thing" v='some value'
// to (comma separated fields)
// sl = <cmd,some_arg,some thing,v='some value'>

	// early exit the common case
	if (!(   cmd.contains(QGit::QUOTE_CHAR)
	      || cmd.contains("\"")
	      || cmd.contains("\'")))
		return cmd.split(' ', QString::SkipEmptyParts);

	// we have some work to do...
	// first find a possible separator
	const QString sepList("#%&!?"); // separator candidates
	int i = 0;
	while (cmd.contains(sepList[i]) && i < sepList.length())
		i++;

	if (i == sepList.length()) {
		dbs("ASSERT no unique separator found.");
		return QStringList();
	}
	const QChar& sepChar(sepList[i]);

	// remove all spaces
	QString newCmd(cmd);
	newCmd.replace(QChar(' '), sepChar);

	// re-add spaces in quoted sections
	restoreSpaces(newCmd, sepChar);

	// QUOTE_CHAR is used internally to delimit arguments
	// with quoted text wholly inside as
	// arg1 = <[patch] cool patch on "cool feature">
	// and should be removed before to feed QProcess
	newCmd.remove(QGit::QUOTE_CHAR);

	// QProcess::setArguments doesn't want quote
	// delimited arguments, so remove trailing quotes
	QStringList sl(newCmd.split(sepChar, QString::SkipEmptyParts));
	QStringList::iterator it(sl.begin());
	for ( ; it != sl.end(); ++it) {
		if (((*it).left(1) == "\"" && (*it).right(1) == "\"") ||
		   ((*it).left(1) == "\'" && (*it).right(1) == "\'"))
			*it = (*it).mid(1, (*it).length() - 2);
	}
	return sl;
}

void MyProcess::restoreSpaces(QString& newCmd, const QChar& sepChar) {
// restore spaces inside quoted text, supports nested quote types

	QChar quoteChar;
	bool replace = false;
	for (int i = 0; i < newCmd.length(); i++) {

		const QChar& c = newCmd[i];

		if (    !replace
		    && (c == QGit::QUOTE_CHAR[0] || c == '\"' || c == '\'')
		    && (newCmd.count(c) % 2 == 0)) {

				replace = true;
				quoteChar = c;
				continue;
		}
		if (replace && (c == quoteChar)) {
			replace = false;
			continue;
		}
		if (replace && c == sepChar)
			newCmd[i] = QChar(' ');
	}
}