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