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