]> git.lyx.org Git - lyx.git/blob - src/support/Systemcall.cpp
tiger support on mac snow leopard, include Qt4 frameworks, smart build script with...
[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                                 proc_(new QProcess), outindex_(0), errindex_(0),
246                                 outfile(of), showout_(false), showerr_(false), process_events(false)
247 {
248         if (!outfile.empty()) {
249                 // Check whether we have to simply throw away the output.
250                 if (outfile != os::nulldev())
251                         proc_->setStandardOutputFile(toqstr(outfile));
252         } else if (os::is_terminal(os::STDOUT))
253                 setShowOut(true);
254         if (os::is_terminal(os::STDERR))
255                 setShowErr(true);
256
257         connect(proc_, SIGNAL(readyReadStandardOutput()), SLOT(stdOut()));
258         connect(proc_, SIGNAL(readyReadStandardError()), SLOT(stdErr()));
259         connect(proc_, SIGNAL(error(QProcess::ProcessError)), SLOT(processError(QProcess::ProcessError)));
260         connect(proc_, SIGNAL(started()), this, SLOT(processStarted()));
261         connect(proc_, 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 (proc_) {
270                 state = SystemcallPrivate::Starting;
271                 proc_->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 proc_events, int timeout)
292 {
293         if (!proc_)
294                 return false;
295
296         process_events = proc_events;
297
298         // Block GUI while waiting,
299         // relay on QProcess' wait functions
300         if (!process_events) {
301                 if (waitwhile == Starting)
302                         return proc_->waitForStarted(timeout);
303                 if (waitwhile == Running)
304                         return proc_->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         flush();
329
330         if (outindex_) {
331                 outdata_[outindex_] = '\0';
332                 outindex_ = 0;
333                 if (showout_)
334                         cout << outdata_;
335         }
336         cout.flush();
337         if (errindex_) {
338                 errdata_[errindex_] = '\0';
339                 errindex_ = 0;
340                 if (showerr_)
341                         cerr << errdata_;
342         }
343         cerr.flush();
344
345         killProcess();
346 }
347
348
349 void SystemcallPrivate::flush()
350 {
351         if (proc_) {
352                 // If the output has been redirected, we write it all at once.
353                 // Even if we are not running in a terminal, the output could go
354                 // to some log file, for example ~/.xsession-errors on *nix.
355                 
356                 QString data = QString::fromLocal8Bit(proc_->readAllStandardOutput().data());
357                 if (showout_) 
358                         ProgressInterface::instance()->appendMessage(data);
359                 if (!os::is_terminal(os::STDOUT) && outfile.empty()) 
360                         cout << fromqstr(data);
361                 
362                 data = QString::fromLocal8Bit(proc_->readAllStandardError().data());
363                 if (showerr_) 
364                         ProgressInterface::instance()->appendError(data);
365                 if (!os::is_terminal(os::STDERR)) 
366                         cerr << fromqstr(data);                 
367                 
368         }
369 }
370
371
372 void SystemcallPrivate::stdOut()
373 {
374         if (proc_) {
375                 char c;
376                 proc_->setReadChannel(QProcess::StandardOutput);
377                 while (proc_->getChar(&c)) {
378                         outdata_[outindex_++] = c;
379                         if (c == '\n' || outindex_ + 1 == bufsize_) {
380                                 outdata_[outindex_] = '\0';
381                                 outindex_ = 0;
382                                 if (showout_) {
383                                         cout << outdata_;
384                                         ProgressInterface::instance()->appendMessage(QString::fromLocal8Bit(outdata_));         
385                                 }
386                         }
387                 }
388         }
389 }
390
391
392 void SystemcallPrivate::stdErr()
393 {
394         if (proc_) {
395                 char c;
396                 proc_->setReadChannel(QProcess::StandardError);
397                 while (proc_->getChar(&c)) {
398                         errdata_[errindex_++] = c;
399                         if (c == '\n' || errindex_ + 1 == bufsize_) {
400                                 errdata_[errindex_] = '\0';
401                                 errindex_ = 0;
402                                 if (showerr_) {
403                                         cerr << errdata_;
404                                         ProgressInterface::instance()->appendError(QString::fromLocal8Bit(errdata_));
405                                 }
406                         }
407                 }
408         }
409 }
410
411
412 void SystemcallPrivate::processStarted()
413 {
414         if (state != Running) {
415                 state = Running;
416                 ProgressInterface::instance()->processStarted(cmd_);
417         }
418 }
419
420
421 void SystemcallPrivate::processFinished(int, QProcess::ExitStatus)
422 {
423         if (state != Finished) {
424                 state = Finished;
425                 ProgressInterface::instance()->processFinished(cmd_);
426         }
427 }
428
429
430 void SystemcallPrivate::processError(QProcess::ProcessError)
431 {
432         state = Error;
433         ProgressInterface::instance()->appendError(errorMessage());
434 }
435
436
437 QString SystemcallPrivate::errorMessage() const 
438 {
439         if (!proc_)
440                 return "No QProcess available";
441
442         QString message;
443         switch (proc_->error()) {
444                 case QProcess::FailedToStart:
445                         message = "The process failed to start. Either the invoked program is missing, "
446                                       "or you may have insufficient permissions to invoke the program.";
447                         break;
448                 case QProcess::Crashed:
449                         message = "The process crashed some time after starting successfully.";
450                         break;
451                 case QProcess::Timedout:
452                         message = "The process timed out. It might be restarted automatically.";
453                         break;
454                 case QProcess::WriteError:
455                         message = "An error occurred when attempting to write to the process-> For example, "
456                                       "the process may not be running, or it may have closed its input channel.";
457                         break;
458                 case QProcess::ReadError:
459                         message = "An error occurred when attempting to read from the process-> For example, "
460                                       "the process may not be running.";
461                         break;
462                 case QProcess::UnknownError:
463                 default:
464                         message = "An unknown error occured.";
465                         break;
466         }
467         return message;
468 }
469
470
471 QString SystemcallPrivate::exitStatusMessage() const
472 {
473         if (!proc_)
474                 return "No QProcess available";
475
476         QString message;
477         switch (proc_->exitStatus()) {
478                 case QProcess::NormalExit:
479                         message = "The process exited normally.";
480                         break;
481                 case QProcess::CrashExit:
482                         message = "The process crashed.";
483                         break;
484                 default:
485                         message = "Unknown exit state.";
486                         break;
487         }
488         return message;
489 }
490
491
492 int SystemcallPrivate::exitCode()
493 {
494         if (!proc_)
495                 return -1;
496
497         return proc_->exitCode();
498 }
499
500
501 QProcess* SystemcallPrivate::releaseProcess()
502 {
503         QProcess* released = proc_;
504         proc_ = 0;
505         return released;
506 }
507
508
509 void SystemcallPrivate::killProcess()
510 {
511         killProcess(proc_);
512 }
513
514
515 void SystemcallPrivate::killProcess(QProcess * p)
516 {
517         if (p) {
518                 p->disconnect();
519                 p->closeReadChannel(QProcess::StandardOutput);
520                 p->closeReadChannel(QProcess::StandardError);
521                 p->close();
522                 delete p;
523         }
524 }
525
526
527
528 #include "moc_SystemcallPrivate.cpp"
529 #endif
530
531 } // namespace support
532 } // namespace lyx