]> git.lyx.org Git - lyx.git/blob - src/support/Systemcall.cpp
add generic helper class for calling functions in gui thread
[lyx.git] / src / support / Systemcall.cpp
1 /**
2  * \file Systemcall.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Asger Alstrup
7  * \author Angus Leeming
8  * \author Enrico Forestieri
9  * \author Peter Kuemmel
10  *
11  * Full author contact details are available in file CREDITS.
12  */
13
14 #include <config.h>
15
16 #include "support/debug.h"
17 #include "support/lstrings.h"
18 #include "support/qstring_helpers.h"
19 #include "support/Systemcall.h"
20 #include "support/SystemcallPrivate.h"
21 #include "support/os.h"
22 #include "support/ProgressInterface.h"
23
24
25 #include <cstdlib>
26 #include <iostream>
27
28 #include <QProcess>
29 #include <QTime>
30 #include <QThread>
31 #include <QCoreApplication>
32 #include <QDebug>
33
34 #define USE_QPROCESS
35
36
37 struct Sleep : QThread
38 {
39         static void millisec(unsigned long ms) 
40         {
41                 QThread::usleep(ms * 1000);
42         }
43 };
44
45
46
47
48 using namespace std;
49
50 namespace lyx {
51 namespace support {
52
53
54 class ProgressDummy : public ProgressInterface 
55 {
56 public:
57         ProgressDummy() {}
58
59         void processStarted(QString const &) {}
60         void processFinished(QString const &) {}
61         void appendMessage(QString const &) {}
62         void appendError(QString const &) {}
63         void clearMessages() {}
64         void lyxerrFlush() {}
65
66         void lyxerrConnect() {}
67         void lyxerrDisconnect() {}
68
69         void warning(QString const &, QString const &) {}
70         void toggleWarning(QString const &, QString const &, QString const &) {}
71         void error(QString const &, QString const &) {}
72         void information(QString const &, QString const &) {}
73 };
74
75
76 static ProgressInterface* progress_instance = 0;
77
78 void ProgressInterface::setInstance(ProgressInterface* p)
79 {
80         progress_instance = p;
81 }
82
83
84 ProgressInterface* ProgressInterface::instance()
85 {
86         if (!progress_instance) {
87                 static ProgressDummy dummy;
88                 return &dummy;
89         }
90         return progress_instance;
91 }
92
93
94
95
96 // Reuse of instance
97 #ifndef USE_QPROCESS
98 int Systemcall::startscript(Starttype how, string const & what,
99                                                         bool /*process_events*/)
100 {
101         string command = what;
102
103         if (how == DontWait) {
104                 switch (os::shell()) {
105                 case os::UNIX:
106                         command += " &";
107                         break;
108                 case os::CMD_EXE:
109                         command = "start /min " + command;
110                         break;
111                 }
112         }
113
114         return ::system(command.c_str());
115 }
116
117 #else
118
119 namespace {
120
121 /*
122  * This is a parser that (mostly) mimics the behavior of a posix shell but
123  * its output is tailored for being processed by QProcess.
124  *
125  * The escape character is the backslash.
126  * A backslash that is not quoted preserves the literal value of the following
127  * character, with the exception of a double-quote '"'. If a double-quote
128  * follows a backslash, it will be replaced by three consecutive double-quotes
129  * (this is how the QProcess parser recognizes a '"' as a simple character
130  * instead of a quoting character). Thus, for example:
131  *     \\  ->  \
132  *     \a  ->  a
133  *     \"  ->  """
134  *
135  * Single-quotes.
136  * Characters enclosed in single-quotes ('') have their literal value preserved.
137  * A single-quote cannot occur within single-quotes. Indeed, a backslash cannot
138  * be used to escape a single-quote in a single-quoted string. In other words,
139  * anything enclosed in single-quotes is passed as is, but the single-quotes
140  * themselves are eliminated. Thus, for example:
141  *    '\'    ->  \
142  *    '\\'   ->  \\
143  *    '\a'   ->  \a
144  *    'a\"b' ->  a\"b
145  *
146  * Double-quotes.
147  * Characters enclosed in double-quotes ("") have their literal value preserved,
148  * with the exception of the backslash. The backslash retains its special
149  * meaning as an escape character only when followed by a double-quote.
150  * Contrarily to the behavior of a posix shell, the double-quotes themselves
151  * are *not* eliminated. Thus, for example:
152  *    "\\"   ->  "\\"
153  *    "\a"   ->  "\a"
154  *    "a\"b" ->  "a"""b"
155  */
156 string const parsecmd(string const & inputcmd, string & outfile)
157 {
158         bool in_single_quote = false;
159         bool in_double_quote = false;
160         bool escaped = false;
161         string cmd;
162
163         for (size_t i = 0; i < inputcmd.length(); ++i) {
164                 char c = inputcmd[i];
165                 if (c == '\'') {
166                         if (in_double_quote || escaped) {
167                                 if (in_double_quote && escaped)
168                                         cmd += '\\';
169                                 cmd += c;
170                         } else
171                                 in_single_quote = !in_single_quote;
172                         escaped = false;
173                         continue;
174                 }
175                 if (in_single_quote) {
176                         cmd += c;
177                         continue;
178                 }
179                 if (c == '"') {
180                         if (escaped) {
181                                 cmd += "\"\"\"";
182                                 escaped = false;
183                         } else {
184                                 cmd += c;
185                                 in_double_quote = !in_double_quote;
186                         }
187                 } else if (c == '\\' && !escaped) {
188                         escaped = !escaped;
189                 } else if (c == '>' && !(in_double_quote || escaped)) {
190                         outfile = trim(inputcmd.substr(i + 1), " \"");
191                         return trim(cmd);
192                 } else {
193                         if (escaped && in_double_quote)
194                                 cmd += '\\';
195                         cmd += c;
196                         escaped = false;
197                 }
198         }
199         outfile.erase();
200         return cmd;
201 }
202
203 } // namespace anon
204
205
206
207 int Systemcall::startscript(Starttype how, string const & what, bool process_events)
208 {
209         string outfile;
210         QString cmd = toqstr(parsecmd(what, outfile));
211
212         SystemcallPrivate d(outfile);
213
214
215         d.startProcess(cmd);
216         if (!d.waitWhile(SystemcallPrivate::Starting, process_events, -1)) {
217                 LYXERR0("Systemcall: '" << cmd << "' did not start!");
218                 LYXERR0("error " << d.errorMessage());
219                 return 10;
220         }
221
222         if (how == DontWait) {
223                 QProcess* released = d.releaseProcess();
224                 (void) released; // TODO who deletes it?
225                 return 0;
226         }
227
228         if (!d.waitWhile(SystemcallPrivate::Running, process_events, 180000)) {
229                 LYXERR0("Systemcall: '" << cmd << "' did not finished!");
230                 LYXERR0("error " << d.errorMessage());
231                 LYXERR0("status " << d.exitStatusMessage());
232                 return 20;
233         }
234
235         int const exit_code = d.exitCode();
236         if (exit_code) {
237                 LYXERR0("Systemcall: '" << cmd << "' finished with exit code " << exit_code);
238         }
239
240         return exit_code;
241 }
242
243
244 SystemcallPrivate::SystemcallPrivate(const std::string& of) :
245                                 process_(new QProcess), 
246                                 out_index_(0),
247                                 err_index_(0),
248                                 out_file_(of), 
249                                 process_events_(false)
250 {
251         if (!out_file_.empty()) {
252                 // Check whether we have to simply throw away the output.
253                 if (out_file_ != os::nulldev())
254                         process_->setStandardOutputFile(toqstr(out_file_));
255         }
256
257         connect(process_, SIGNAL(readyReadStandardOutput()), SLOT(stdOut()));
258         connect(process_, SIGNAL(readyReadStandardError()), SLOT(stdErr()));
259         connect(process_, SIGNAL(error(QProcess::ProcessError)), SLOT(processError(QProcess::ProcessError)));
260         connect(process_, SIGNAL(started()), this, SLOT(processStarted()));
261         connect(process_, SIGNAL(finished(int, QProcess::ExitStatus)), SLOT(processFinished(int, QProcess::ExitStatus)));
262 }
263
264
265
266 void SystemcallPrivate::startProcess(const QString& cmd)
267 {
268         cmd_ = cmd;
269         if (process_) {
270                 state = SystemcallPrivate::Starting;
271                 process_->start(cmd_);
272         }
273 }
274
275
276 void SystemcallPrivate::processEvents()
277 {
278         if(process_events_) {
279                 QCoreApplication::processEvents(/*QEventLoop::ExcludeUserInputEvents*/);
280         }
281 }
282
283
284 void SystemcallPrivate::waitAndProcessEvents()
285 {
286         Sleep::millisec(100);
287         processEvents();
288 }
289
290
291 bool SystemcallPrivate::waitWhile(State waitwhile, bool process_events, int timeout)
292 {
293         if (!process_)
294                 return false;
295
296         process_events_ = process_events;
297
298         // Block GUI while waiting,
299         // relay on QProcess' wait functions
300         if (!process_events_) {
301                 if (waitwhile == Starting)
302                         return process_->waitForStarted(timeout);
303                 if (waitwhile == Running)
304                         return process_->waitForFinished(timeout);
305                 return false;
306         }
307
308         // process events while waiting, no timeout
309         if (timeout == -1) {
310                 while (state == waitwhile && state != Error) {
311                         waitAndProcessEvents();
312                 }
313                 return state != Error;
314         } 
315
316         // process events while waiting whith timeout
317         QTime timer;
318         timer.start();
319         while (state == waitwhile && state != Error && timer.elapsed() < timeout) {
320                 waitAndProcessEvents();
321         }
322         return (state != Error) && (timer.elapsed() < timeout);
323 }
324
325
326 SystemcallPrivate::~SystemcallPrivate()
327 {
328         if (out_index_) {
329                 out_data_[out_index_] = '\0';
330                 out_index_ = 0;
331                 cout << out_data_;
332         }
333         cout.flush();
334         if (err_index_) {
335                 err_data_[err_index_] = '\0';
336                 err_index_ = 0;
337                 cerr << err_data_;
338         }
339         cerr.flush();
340
341         killProcess();
342 }
343
344
345 void SystemcallPrivate::stdOut()
346 {
347         if (process_) {
348                 char c;
349                 process_->setReadChannel(QProcess::StandardOutput);
350                 while (process_->getChar(&c)) {
351                         out_data_[out_index_++] = c;
352                         if (c == '\n' || out_index_ + 1 == buffer_size_) {
353                                 out_data_[out_index_] = '\0';
354                                 out_index_ = 0;
355                                 ProgressInterface::instance()->appendMessage(QString::fromLocal8Bit(out_data_));
356                                 cout << out_data_;
357                         }
358                 }
359         }
360 }
361
362
363 void SystemcallPrivate::stdErr()
364 {
365         if (process_) {
366                 char c;
367                 process_->setReadChannel(QProcess::StandardError);
368                 while (process_->getChar(&c)) {
369                         err_data_[err_index_++] = c;
370                         if (c == '\n' || err_index_ + 1 == buffer_size_) {
371                                 err_data_[err_index_] = '\0';
372                                 err_index_ = 0;
373                                 ProgressInterface::instance()->appendError(QString::fromLocal8Bit(err_data_));
374                                 cerr << err_data_;
375                         }
376                 }
377         }
378 }
379
380
381 void SystemcallPrivate::processStarted()
382 {
383         if (state != Running) {
384                 state = Running;
385                 ProgressInterface::instance()->processStarted(cmd_);
386         }
387 }
388
389
390 void SystemcallPrivate::processFinished(int, QProcess::ExitStatus)
391 {
392         if (state != Finished) {
393                 state = Finished;
394                 ProgressInterface::instance()->processFinished(cmd_);
395         }
396 }
397
398
399 void SystemcallPrivate::processError(QProcess::ProcessError)
400 {
401         state = Error;
402         ProgressInterface::instance()->appendError(errorMessage());
403 }
404
405
406 QString SystemcallPrivate::errorMessage() const 
407 {
408         if (!process_)
409                 return "No QProcess available";
410
411         QString message;
412         switch (process_->error()) {
413                 case QProcess::FailedToStart:
414                         message = "The process failed to start. Either the invoked program is missing, "
415                                       "or you may have insufficient permissions to invoke the program.";
416                         break;
417                 case QProcess::Crashed:
418                         message = "The process crashed some time after starting successfully.";
419                         break;
420                 case QProcess::Timedout:
421                         message = "The process timed out. It might be restarted automatically.";
422                         break;
423                 case QProcess::WriteError:
424                         message = "An error occurred when attempting to write to the process-> For example, "
425                                       "the process may not be running, or it may have closed its input channel.";
426                         break;
427                 case QProcess::ReadError:
428                         message = "An error occurred when attempting to read from the process-> For example, "
429                                       "the process may not be running.";
430                         break;
431                 case QProcess::UnknownError:
432                 default:
433                         message = "An unknown error occured.";
434                         break;
435         }
436         return message;
437 }
438
439
440 QString SystemcallPrivate::exitStatusMessage() const
441 {
442         if (!process_)
443                 return "No QProcess available";
444
445         QString message;
446         switch (process_->exitStatus()) {
447                 case QProcess::NormalExit:
448                         message = "The process exited normally.";
449                         break;
450                 case QProcess::CrashExit:
451                         message = "The process crashed.";
452                         break;
453                 default:
454                         message = "Unknown exit state.";
455                         break;
456         }
457         return message;
458 }
459
460
461 int SystemcallPrivate::exitCode()
462 {
463         if (!process_)
464                 return -1;
465
466         return process_->exitCode();
467 }
468
469
470 QProcess* SystemcallPrivate::releaseProcess()
471 {
472         QProcess* released = process_;
473         process_ = 0;
474         return released;
475 }
476
477
478 void SystemcallPrivate::killProcess()
479 {
480         killProcess(process_);
481 }
482
483
484 void SystemcallPrivate::killProcess(QProcess * p)
485 {
486         if (p) {
487                 p->disconnect();
488                 p->closeReadChannel(QProcess::StandardOutput);
489                 p->closeReadChannel(QProcess::StandardError);
490                 p->close();
491                 delete p;
492         }
493 }
494
495
496
497 #include "moc_SystemcallPrivate.cpp"
498 #endif
499
500 } // namespace support
501 } // namespace lyx