Merged in f5soh/librepilot/update_credits (pull request #529)
[librepilot.git] / ground / gcs / src / libs / utils / synchronousprocess.cpp
blob0bbef1d0579871f2c95365ed2319a125042a2ea1
1 /**
2 ******************************************************************************
4 * @file synchronousprocess.cpp
5 * @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2010.
6 * Parts by Nokia Corporation (qt-info@nokia.com) Copyright (C) 2009.
7 * @brief
8 * @see The GNU Public License (GPL) Version 3
9 * @defgroup
10 * @{
12 *****************************************************************************/
14 * This program is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation; either version 3 of the License, or
17 * (at your option) any later version.
19 * This program is distributed in the hope that it will be useful, but
20 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
21 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
22 * for more details.
24 * You should have received a copy of the GNU General Public License along
25 * with this program; if not, write to the Free Software Foundation, Inc.,
26 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
29 #include "synchronousprocess.h"
31 #include <QtCore/QDebug>
32 #include <QtCore/QTimer>
33 #include <QtCore/QEventLoop>
34 #include <QtCore/QTextCodec>
35 #include <QtCore/QFileInfo>
36 #include <QtCore/QDir>
38 #include <QtWidgets/QApplication>
40 #include <limits.h>
42 enum { debug = 0 };
44 enum { defaultMaxHangTimerCount = 10 };
46 namespace Utils {
47 // ----------- SynchronousProcessResponse
48 SynchronousProcessResponse::SynchronousProcessResponse() :
49 result(StartFailed),
50 exitCode(-1)
53 void SynchronousProcessResponse::clear()
55 result = StartFailed;
56 exitCode = -1;
57 stdOut.clear();
58 stdErr.clear();
61 QTCREATOR_UTILS_EXPORT QDebug operator<<(QDebug str, const SynchronousProcessResponse & r)
63 QDebug nsp = str.nospace();
65 nsp << "SynchronousProcessResponse: result=" << r.result << " ex=" << r.exitCode << '\n'
66 << r.stdOut.size() << " bytes stdout, stderr=" << r.stdErr << '\n';
67 return str;
70 // Data for one channel buffer (stderr/stdout)
71 struct ChannelBuffer {
72 ChannelBuffer();
73 void clearForRun();
74 QByteArray linesRead();
76 QByteArray data;
77 bool firstData;
78 bool bufferedSignalsEnabled;
79 bool firstBuffer;
80 int bufferPos;
83 ChannelBuffer::ChannelBuffer() :
84 firstData(true),
85 bufferedSignalsEnabled(false),
86 firstBuffer(true),
87 bufferPos(0)
90 void ChannelBuffer::clearForRun()
92 firstData = true;
93 firstBuffer = true;
94 bufferPos = 0;
97 /* Check for complete lines read from the device and return them, moving the
98 * buffer position. This is based on the assumption that '\n' is the new line
99 * marker in any sane codec. */
100 QByteArray ChannelBuffer::linesRead()
102 // Any new lines?
103 const int lastLineIndex = data.lastIndexOf('\n');
105 if (lastLineIndex == -1 || lastLineIndex <= bufferPos) {
106 return QByteArray();
108 const int nextBufferPos = lastLineIndex + 1;
109 const QByteArray lines = data.mid(bufferPos, nextBufferPos - bufferPos);
110 bufferPos = nextBufferPos;
111 return lines;
114 // ----------- SynchronousProcessPrivate
115 struct SynchronousProcessPrivate {
116 SynchronousProcessPrivate();
117 void clearForRun();
119 QTextCodec *m_stdOutCodec;
120 QProcess m_process;
121 QTimer m_timer;
122 QEventLoop m_eventLoop;
123 SynchronousProcessResponse m_result;
124 int m_hangTimerCount;
125 int m_maxHangTimerCount;
126 bool m_startFailure;
128 ChannelBuffer m_stdOut;
129 ChannelBuffer m_stdErr;
132 SynchronousProcessPrivate::SynchronousProcessPrivate() :
133 m_stdOutCodec(0),
134 m_hangTimerCount(0),
135 m_maxHangTimerCount(defaultMaxHangTimerCount),
136 m_startFailure(false)
139 void SynchronousProcessPrivate::clearForRun()
141 m_hangTimerCount = 0;
142 m_stdOut.clearForRun();
143 m_stdErr.clearForRun();
144 m_result.clear();
145 m_startFailure = false;
148 // ----------- SynchronousProcess
149 SynchronousProcess::SynchronousProcess() :
150 m_d(new SynchronousProcessPrivate)
152 m_d->m_timer.setInterval(1000);
153 connect(&m_d->m_timer, SIGNAL(timeout()), this, SLOT(slotTimeout()));
154 connect(&m_d->m_process, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(finished(int, QProcess::ExitStatus)));
155 connect(&m_d->m_process, SIGNAL(error(QProcess::ProcessError)), this, SLOT(error(QProcess::ProcessError)));
156 connect(&m_d->m_process, SIGNAL(readyReadStandardOutput()),
157 this, SLOT(stdOutReady()));
158 connect(&m_d->m_process, SIGNAL(readyReadStandardError()),
159 this, SLOT(stdErrReady()));
162 SynchronousProcess::~SynchronousProcess()
164 delete m_d;
167 void SynchronousProcess::setTimeout(int timeoutMS)
169 if (timeoutMS >= 0) {
170 m_d->m_maxHangTimerCount = qMax(2, timeoutMS / 1000);
171 } else {
172 m_d->m_maxHangTimerCount = INT_MAX;
176 int SynchronousProcess::timeout() const
178 return m_d->m_maxHangTimerCount == INT_MAX ? -1 : 1000 * m_d->m_maxHangTimerCount;
181 void SynchronousProcess::setStdOutCodec(QTextCodec *c)
183 m_d->m_stdOutCodec = c;
186 QTextCodec *SynchronousProcess::stdOutCodec() const
188 return m_d->m_stdOutCodec;
191 bool SynchronousProcess::stdOutBufferedSignalsEnabled() const
193 return m_d->m_stdOut.bufferedSignalsEnabled;
196 void SynchronousProcess::setStdOutBufferedSignalsEnabled(bool v)
198 m_d->m_stdOut.bufferedSignalsEnabled = v;
201 bool SynchronousProcess::stdErrBufferedSignalsEnabled() const
203 return m_d->m_stdErr.bufferedSignalsEnabled;
206 void SynchronousProcess::setStdErrBufferedSignalsEnabled(bool v)
208 m_d->m_stdErr.bufferedSignalsEnabled = v;
211 QStringList SynchronousProcess::environment() const
213 return m_d->m_process.environment();
216 void SynchronousProcess::setEnvironment(const QStringList &e)
218 m_d->m_process.setEnvironment(e);
221 void SynchronousProcess::setWorkingDirectory(const QString &workingDirectory)
223 m_d->m_process.setWorkingDirectory(workingDirectory);
226 QString SynchronousProcess::workingDirectory() const
228 return m_d->m_process.workingDirectory();
231 QProcess::ProcessChannelMode SynchronousProcess::processChannelMode() const
233 return m_d->m_process.processChannelMode();
236 void SynchronousProcess::setProcessChannelMode(QProcess::ProcessChannelMode m)
238 m_d->m_process.setProcessChannelMode(m);
241 SynchronousProcessResponse SynchronousProcess::run(const QString &binary,
242 const QStringList &args)
244 if (debug) {
245 qDebug() << '>' << Q_FUNC_INFO << binary << args;
248 m_d->clearForRun();
250 // On Windows, start failure is triggered immediately if the
251 // executable cannot be found in the path. Do not start the
252 // event loop in that case.
253 m_d->m_process.start(binary, args, QIODevice::ReadOnly);
254 if (!m_d->m_startFailure) {
255 m_d->m_timer.start();
256 QApplication::setOverrideCursor(Qt::WaitCursor);
257 m_d->m_eventLoop.exec(QEventLoop::ExcludeUserInputEvents);
258 if (m_d->m_result.result == SynchronousProcessResponse::Finished || m_d->m_result.result == SynchronousProcessResponse::FinishedError) {
259 processStdOut(false);
260 processStdErr(false);
263 m_d->m_result.stdOut = convertStdOut(m_d->m_stdOut.data);
264 m_d->m_result.stdErr = convertStdErr(m_d->m_stdErr.data);
266 m_d->m_timer.stop();
267 QApplication::restoreOverrideCursor();
270 if (debug) {
271 qDebug() << '<' << Q_FUNC_INFO << binary << m_d->m_result;
273 return m_d->m_result;
276 void SynchronousProcess::slotTimeout()
278 if (++m_d->m_hangTimerCount > m_d->m_maxHangTimerCount) {
279 if (debug) {
280 qDebug() << Q_FUNC_INFO << "HANG detected, killing";
282 m_d->m_process.kill();
283 m_d->m_result.result = SynchronousProcessResponse::Hang;
284 } else {
285 if (debug) {
286 qDebug() << Q_FUNC_INFO << m_d->m_hangTimerCount;
291 void SynchronousProcess::finished(int exitCode, QProcess::ExitStatus e)
293 if (debug) {
294 qDebug() << Q_FUNC_INFO << exitCode << e;
296 m_d->m_hangTimerCount = 0;
297 switch (e) {
298 case QProcess::NormalExit:
299 m_d->m_result.result = exitCode ? SynchronousProcessResponse::FinishedError : SynchronousProcessResponse::Finished;
300 m_d->m_result.exitCode = exitCode;
301 break;
302 case QProcess::CrashExit:
303 // Was hang detected before and killed?
304 if (m_d->m_result.result != SynchronousProcessResponse::Hang) {
305 m_d->m_result.result = SynchronousProcessResponse::TerminatedAbnormally;
307 m_d->m_result.exitCode = -1;
308 break;
310 m_d->m_eventLoop.quit();
313 void SynchronousProcess::error(QProcess::ProcessError e)
315 m_d->m_hangTimerCount = 0;
316 if (debug) {
317 qDebug() << Q_FUNC_INFO << e;
319 // Was hang detected before and killed?
320 if (m_d->m_result.result != SynchronousProcessResponse::Hang) {
321 m_d->m_result.result = SynchronousProcessResponse::StartFailed;
323 m_d->m_startFailure = true;
324 m_d->m_eventLoop.quit();
327 void SynchronousProcess::stdOutReady()
329 m_d->m_hangTimerCount = 0;
330 processStdOut(true);
333 void SynchronousProcess::stdErrReady()
335 m_d->m_hangTimerCount = 0;
336 processStdErr(true);
339 QString SynchronousProcess::convertStdErr(const QByteArray &ba)
341 return QString::fromLocal8Bit(ba.constData(), ba.size()).remove(QLatin1Char('\r'));
344 QString SynchronousProcess::convertStdOut(const QByteArray &ba) const
346 QString stdOut = m_d->m_stdOutCodec ? m_d->m_stdOutCodec->toUnicode(ba) : QString::fromLocal8Bit(ba.constData(), ba.size());
348 return stdOut.remove(QLatin1Char('\r'));
351 void SynchronousProcess::processStdOut(bool emitSignals)
353 // Handle binary data
354 const QByteArray ba = m_d->m_process.readAllStandardOutput();
356 if (debug > 1) {
357 qDebug() << Q_FUNC_INFO << emitSignals << ba;
359 if (!ba.isEmpty()) {
360 m_d->m_stdOut.data += ba;
361 if (emitSignals) {
362 // Emit binary signals
363 emit stdOut(ba, m_d->m_stdOut.firstData);
364 m_d->m_stdOut.firstData = false;
365 // Buffered. Emit complete lines?
366 if (m_d->m_stdOut.bufferedSignalsEnabled) {
367 const QByteArray lines = m_d->m_stdOut.linesRead();
368 if (!lines.isEmpty()) {
369 emit stdOutBuffered(convertStdOut(lines), m_d->m_stdOut.firstBuffer);
370 m_d->m_stdOut.firstBuffer = false;
377 void SynchronousProcess::processStdErr(bool emitSignals)
379 // Handle binary data
380 const QByteArray ba = m_d->m_process.readAllStandardError();
382 if (debug > 1) {
383 qDebug() << Q_FUNC_INFO << emitSignals << ba;
385 if (!ba.isEmpty()) {
386 m_d->m_stdErr.data += ba;
387 if (emitSignals) {
388 // Emit binary signals
389 emit stdErr(ba, m_d->m_stdErr.firstData);
390 m_d->m_stdErr.firstData = false;
391 if (m_d->m_stdErr.bufferedSignalsEnabled) {
392 // Buffered. Emit complete lines?
393 const QByteArray lines = m_d->m_stdErr.linesRead();
394 if (!lines.isEmpty()) {
395 emit stdErrBuffered(convertStdErr(lines), m_d->m_stdErr.firstBuffer);
396 m_d->m_stdErr.firstBuffer = false;
403 // Path utilities
405 enum OS_Type { OS_Mac, OS_Windows, OS_Unix };
407 #ifdef Q_OS_WIN
408 static const OS_Type pathOS = OS_Windows;
409 #else
410 # ifdef Q_OS_MAC
411 static const OS_Type pathOS = OS_Mac;
412 # else
413 static const OS_Type pathOS = OS_Unix;
414 # endif
415 #endif
417 // Locate a binary in a directory, applying all kinds of
418 // extensions the operating system supports.
419 static QString checkBinary(const QDir &dir, const QString &binary)
421 // naive UNIX approach
422 const QFileInfo info(dir.filePath(binary));
424 if (info.isFile() && info.isExecutable()) {
425 return info.absoluteFilePath();
428 // Does the OS have some weird extension concept or does the
429 // binary have a 3 letter extension?
430 if (pathOS == OS_Unix) {
431 return QString();
433 const int dotIndex = binary.lastIndexOf(QLatin1Char('.'));
434 if (dotIndex != -1 && dotIndex == binary.size() - 4) {
435 return QString();
438 switch (pathOS) {
439 case OS_Unix:
440 break;
441 case OS_Windows:
443 static const char *windowsExtensions[] = { ".cmd", ".bat", ".exe", ".com" };
444 // Check the Windows extensions using the order
445 const int windowsExtensionCount = sizeof(windowsExtensions) / sizeof(const char *);
446 for (int e = 0; e < windowsExtensionCount; e++) {
447 const QFileInfo windowsBinary(dir.filePath(binary + QLatin1String(windowsExtensions[e])));
448 if (windowsBinary.isFile() && windowsBinary.isExecutable()) {
449 return windowsBinary.absoluteFilePath();
453 break;
454 case OS_Mac:
456 // Check for Mac app folders
457 const QFileInfo appFolder(dir.filePath(binary + QLatin1String(".app")));
458 if (appFolder.isDir()) {
459 QString macBinaryPath = appFolder.absoluteFilePath();
460 macBinaryPath += QLatin1String("/Contents/MacOS/");
461 macBinaryPath += binary;
462 const QFileInfo macBinary(macBinaryPath);
463 if (macBinary.isFile() && macBinary.isExecutable()) {
464 return macBinary.absoluteFilePath();
468 break;
470 return QString();
473 QString SynchronousProcess::locateBinary(const QString &path, const QString &binary)
475 // Absolute file?
476 const QFileInfo absInfo(binary);
478 if (absInfo.isAbsolute()) {
479 return checkBinary(absInfo.dir(), absInfo.fileName());
482 // Windows finds binaries in the current directory
483 if (pathOS == OS_Windows) {
484 const QString currentDirBinary = checkBinary(QDir::current(), binary);
485 if (!currentDirBinary.isEmpty()) {
486 return currentDirBinary;
490 const QStringList paths = path.split(pathSeparator());
491 if (paths.empty()) {
492 return QString();
494 const QStringList::const_iterator cend = paths.constEnd();
495 for (QStringList::const_iterator it = paths.constBegin(); it != cend; ++it) {
496 const QDir dir(*it);
497 const QString rc = checkBinary(dir, binary);
498 if (!rc.isEmpty()) {
499 return rc;
502 return QString();
505 QString SynchronousProcess::locateBinary(const QString &binary)
507 const QByteArray path = qgetenv("PATH");
509 return locateBinary(QString::fromLocal8Bit(path), binary);
512 QChar SynchronousProcess::pathSeparator()
514 if (pathOS == OS_Windows) {
515 return QLatin1Char(';');
517 return QLatin1Char(':');
519 } // namespace Utils