]> git.lyx.org Git - lyx.git/blob - src/support/Systemcall.cpp
cleanup error messages
[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
23
24 #include <cstdlib>
25 #include <iostream>
26
27 #include <QProcess>
28 #include <QTime>
29 #include <QThread>
30 #include <QCoreApplication>
31 #include <QDebug>
32
33 #define USE_QPROCESS
34
35
36 struct Sleep : QThread
37 {
38         static void millisec(unsigned long ms) 
39         {
40                 QThread::usleep(ms * 1000);
41         }
42 };
43
44
45
46 using namespace std;
47
48 namespace lyx {
49 namespace support {
50
51
52
53 // Reuse of instance
54 #ifndef USE_QPROCESS
55 int Systemcall::startscript(Starttype how, string const & what,
56                                                         bool /*process_events*/)
57 {
58         string command = what;
59
60         if (how == DontWait) {
61                 switch (os::shell()) {
62                 case os::UNIX:
63                         command += " &";
64                         break;
65                 case os::CMD_EXE:
66                         command = "start /min " + command;
67                         break;
68                 }
69         }
70
71         return ::system(command.c_str());
72 }
73
74 #else
75
76 namespace {
77
78 string const parsecmd(string const & cmd, string & outfile)
79 {
80         bool inquote = false;
81         bool escaped = false;
82
83         for (size_t i = 0; i < cmd.length(); ++i) {
84                 char c = cmd[i];
85                 if (c == '"' && !escaped)
86                         inquote = !inquote;
87                 else if (c == '\\' && !escaped)
88                         escaped = !escaped;
89                 else if (c == '>' && !(inquote || escaped)) {
90                         outfile = trim(cmd.substr(i + 1), " \"");
91                         return trim(cmd.substr(0, i));
92                 } else
93                         escaped = false;
94         }
95         outfile.erase();
96         return cmd;
97 }
98
99 } // namespace anon
100
101
102
103 int Systemcall::startscript(Starttype how, string const & what, bool process_events)
104 {
105         string outfile;
106         QString cmd = toqstr(parsecmd(what, outfile));
107         SystemcallPrivate d(outfile);
108
109
110         d.startProcess(cmd);
111         if (!d.waitWhile(SystemcallPrivate::Starting, process_events, -1)) {
112                 LYXERR0("Systemcall: '" << cmd << " did not start!");
113                 LYXERR0("error " << d.errorMessage());
114                 return 10;
115         }
116
117         if (how == DontWait) {
118                 QProcess* released = d.releaseProcess();
119                 (void) released; // TODO who deletes it?
120                 return 0;
121         }
122
123         if (!d.waitWhile(SystemcallPrivate::Running, process_events, 180000)) {
124                 LYXERR0("Systemcall: '" << cmd << "' did not finished!");
125                 LYXERR0("error " << d.errorMessage());
126                 LYXERR0("status " << d.exitStatusMessage());
127                 return 20;
128         }
129
130         int const exit_code = d.exitCode();
131         if (exit_code) {
132                 LYXERR0("Systemcall: '" << cmd << "' finished with exit code " << exit_code);
133         }
134
135         return exit_code;
136 }
137
138
139 SystemcallPrivate::SystemcallPrivate(const std::string& of) : 
140                                 proc_(new QProcess), outindex_(0), errindex_(0),
141                                 outfile(of), showout_(false), showerr_(false), process_events(false)
142 {
143         if (!outfile.empty()) {
144                 // Check whether we have to simply throw away the output.
145                 if (outfile != os::nulldev())
146                         proc_->setStandardOutputFile(toqstr(outfile));
147         } else if (os::is_terminal(os::STDOUT))
148                 showout();
149         if (os::is_terminal(os::STDERR))
150                 showerr();
151
152         connect(proc_, SIGNAL(readyReadStandardOutput()), SLOT(stdOut()));
153         connect(proc_, SIGNAL(readyReadStandardError()), SLOT(stdErr()));
154         connect(proc_, SIGNAL(error(QProcess::ProcessError)), SLOT(processError(QProcess::ProcessError)));
155         connect(proc_, SIGNAL(started()), this, SLOT(processStarted()));
156         connect(proc_, SIGNAL(finished(int, QProcess::ExitStatus)), SLOT(processFinished(int, QProcess::ExitStatus)));
157 }
158
159
160
161 void SystemcallPrivate::startProcess(const QString& cmd)
162 {
163         if (proc_) {
164                 state = SystemcallPrivate::Starting;
165                 proc_->start(cmd);
166         }
167 }
168
169
170 void SystemcallPrivate::processEvents()
171 {
172         if(process_events) {
173                 //static int count = 0; qDebug() << count++ << ": waitAndProcessEvents";
174                 QCoreApplication::processEvents(QEventLoop::AllEvents);
175         }
176 }
177
178
179 void SystemcallPrivate::waitAndProcessEvents()
180 {
181         Sleep::millisec(100);
182         processEvents();
183 }
184
185
186 bool SystemcallPrivate::waitWhile(State waitwhile, bool proc_events, int timeout)
187 {
188         if (!proc_)
189                 return false;
190
191         process_events = proc_events;
192
193         // Block GUI while waiting,
194         // relay on QProcess' wait functions
195         if (!process_events) {
196                 if (waitwhile == Starting)
197                         return proc_->waitForStarted(timeout);
198                 if (waitwhile == Running)
199                         return proc_->waitForFinished(timeout);
200                 return false;
201         }
202
203         // process events while waiting, no timeout
204         if (timeout == -1) {
205                 while (state == waitwhile && state != Error) {
206                         waitAndProcessEvents();
207                 }
208                 return state != Error;
209         } 
210
211         // process events while waiting whith timeout
212         QTime timer;
213         timer.start();
214         while (state == waitwhile && state != Error && timer.elapsed() < timeout) {
215                 waitAndProcessEvents();
216         }
217         return (state != Error) && (timer.elapsed() < timeout);
218 }
219
220
221 SystemcallPrivate::~SystemcallPrivate()
222 {
223         flush();
224
225         if (outindex_) {
226                 outdata_[outindex_] = '\0';
227                 outindex_ = 0;
228                 cout << outdata_;
229         }
230         cout.flush();
231         if (errindex_) {
232                 errdata_[errindex_] = '\0';
233                 errindex_ = 0;
234                 cerr << errdata_;
235         }
236         cerr.flush();
237
238         killProcess();
239 }
240
241
242 void SystemcallPrivate::flush()
243 {
244         if (proc_) {
245                 // If the output has been redirected, we write it all at once.
246                 // Even if we are not running in a terminal, the output could go
247                 // to some log file, for example ~/.xsession-errors on *nix.
248                 if (!os::is_terminal(os::STDOUT) && outfile.empty())
249                         cout << fromqstr(QString::fromLocal8Bit(
250                                                 proc_->readAllStandardOutput().data()));
251                 if (!os::is_terminal(os::STDERR))
252                         cerr << fromqstr(QString::fromLocal8Bit(
253                                                 proc_->readAllStandardError().data()));
254         }
255 }
256
257
258 void SystemcallPrivate::stdOut()
259 {
260         if (proc_ && showout_) {
261                 char c;
262                 proc_->setReadChannel(QProcess::StandardOutput);
263                 while (proc_->getChar(&c)) {
264                         outdata_[outindex_++] = c;
265                         if (c == '\n' || outindex_ + 1 == bufsize_) {
266                                 outdata_[outindex_] = '\0';
267                                 outindex_ = 0;
268                                 cout << outdata_;
269                         }
270                 }
271         }
272         processEvents();
273 }
274
275
276 void SystemcallPrivate::stdErr()
277 {
278         if (proc_ && showerr_) {
279                 char c;
280                 proc_->setReadChannel(QProcess::StandardError);
281                 while (proc_->getChar(&c)) {
282                         errdata_[errindex_++] = c;
283                         if (c == '\n' || errindex_ + 1 == bufsize_) {
284                                 errdata_[errindex_] = '\0';
285                                 errindex_ = 0;
286                                 cerr << errdata_;
287                         }
288                 }
289         }
290         processEvents();
291 }
292
293
294 void SystemcallPrivate::processStarted()
295 {
296         state = Running;
297         // why do we get two started signals?
298         //disconnect(proc_, SIGNAL(started()), this, SLOT(processStarted()));
299 }
300
301
302 void SystemcallPrivate::processFinished(int, QProcess::ExitStatus)
303 {
304         state = Finished;
305 }
306
307
308 void SystemcallPrivate::processError(QProcess::ProcessError)
309 {
310         state = Error;
311 }
312
313
314 QString SystemcallPrivate::errorMessage() const 
315 {
316         if (!proc_)
317                 return "No QProcess available";
318
319         QString message;
320         switch (proc_->error()) {
321                 case QProcess::FailedToStart:
322                         message = "The process failed to start. Either the invoked program is missing, "
323                                       "or you may have insufficient permissions to invoke the program.";
324                         break;
325                 case QProcess::Crashed:
326                         message = "The process crashed some time after starting successfully.";
327                         break;
328                 case QProcess::Timedout:
329                         message = "The process timed out. It might be restarted automatically.";
330                         break;
331                 case QProcess::WriteError:
332                         message = "An error occurred when attempting to write to the process-> For example, "
333                                       "the process may not be running, or it may have closed its input channel.";
334                         break;
335                 case QProcess::ReadError:
336                         message = "An error occurred when attempting to read from the process-> For example, "
337                                       "the process may not be running.";
338                         break;
339                 case QProcess::UnknownError:
340                 default:
341                         message = "An unknown error occured.";
342                         break;
343         }
344         return message;
345 }
346
347
348 QString SystemcallPrivate::exitStatusMessage() const
349 {
350         if (!proc_)
351                 return "No QProcess available";
352
353         QString message;
354         switch (proc_->exitStatus()) {
355                 case QProcess::NormalExit:
356                         message = "The process exited normally.";
357                         break;
358                 case QProcess::CrashExit:
359                         message = "The process crashed.";
360                         break;
361                 default:
362                         message = "Unknown exit state.";
363                         break;
364         }
365         return message;
366 }
367
368
369 int SystemcallPrivate::exitCode()
370 {
371         if (!proc_)
372                 return -1;
373
374         return proc_->exitCode();
375 }
376
377
378 QProcess* SystemcallPrivate::releaseProcess()
379 {
380         QProcess* released = proc_;
381         proc_ = 0;
382         return released;
383 }
384
385
386 void SystemcallPrivate::killProcess()
387 {
388         killProcess(proc_);
389 }
390
391
392 void SystemcallPrivate::killProcess(QProcess * p)
393 {
394         if (p) {
395                 p->disconnect();
396                 p->closeReadChannel(QProcess::StandardOutput);
397                 p->closeReadChannel(QProcess::StandardError);
398                 p->close();
399                 delete p;
400         }
401 }
402
403
404
405 #include "moc_SystemcallPrivate.cpp"
406 #endif
407
408 } // namespace support
409 } // namespace lyx