QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
qgsrunprocess.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsrunprocess.cpp
3
4 A class that runs an external program
5
6 -------------------
7 begin : Jan 2005
8 copyright : (C) 2005 by Gavin Macaulay
9 email : gavin at macaulay dot co dot nz
10 ***************************************************************************/
11
12/***************************************************************************
13 * *
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 2 of the License, or *
17 * (at your option) any later version. *
18 * *
19 ***************************************************************************/
20
21#include "qgsrunprocess.h"
22
23#include "qgslogger.h"
24#include "qgsmessageoutput.h"
25#include "qgsfeedback.h"
26#include "qgsapplication.h"
27#include "qgis.h"
28#include <QProcess>
29#include <QTextCodec>
30#include <QMessageBox>
31#include <QApplication>
32
33#if QT_CONFIG(process)
34QgsRunProcess::QgsRunProcess( const QString &action, bool capture )
35
36{
37 // Make up a string from the command and arguments that we'll use
38 // for display purposes
39 QgsDebugMsgLevel( "Running command: " + action, 2 );
40
41 mCommand = action;
42
43 QStringList arguments = QProcess::splitCommand( action );
44 const QString command = arguments.value( 0 );
45 if ( !arguments.isEmpty() )
46 arguments.removeFirst();
47
48 mProcess = new QProcess;
49
50 if ( capture )
51 {
52 connect( mProcess, &QProcess::errorOccurred, this, &QgsRunProcess::processError );
53 connect( mProcess, &QProcess::readyReadStandardOutput, this, &QgsRunProcess::stdoutAvailable );
54 connect( mProcess, &QProcess::readyReadStandardError, this, &QgsRunProcess::stderrAvailable );
55 // We only care if the process has finished if we are capturing
56 // the output from the process, hence this connect() call is
57 // inside the capture if() statement.
58 connect( mProcess, static_cast < void ( QProcess::* )( int, QProcess::ExitStatus ) >( &QProcess::finished ), this, &QgsRunProcess::processExit );
59
60 // Use QgsMessageOutput for displaying output to user
61 // It will delete itself when the dialog box is closed.
63 mOutput->setTitle( action );
64 mOutput->setMessage( tr( "<b>Starting %1…</b>" ).arg( action ), QgsMessageOutput::MessageHtml );
65 mOutput->showMessage( false ); // non-blocking
66
67 // get notification of delete if it's derived from QObject
68 QObject *mOutputObj = dynamic_cast<QObject *>( mOutput );
69 if ( mOutputObj )
70 {
71 connect( mOutputObj, &QObject::destroyed, this, &QgsRunProcess::dialogGone );
72 }
73
74 // start the process!
75 mProcess->start( command, arguments );
76 }
77 else
78 {
79 if ( ! QProcess::startDetached( command, arguments ) ) // let the program run by itself
80 {
81 QMessageBox::critical( nullptr, tr( "Action" ),
82 tr( "Unable to run command\n%1" ).arg( action ),
83 QMessageBox::Ok, Qt::NoButton );
84 }
85 // We're not capturing the output from the process, so we don't
86 // need to exist anymore.
87 die();
88 }
89}
90
91QgsRunProcess::~QgsRunProcess()
92{
93 delete mProcess;
94}
95
96void QgsRunProcess::die()
97{
98 // safe way to do "delete this" for QObjects
99 deleteLater();
100}
101
102void QgsRunProcess::stdoutAvailable()
103{
104 const QByteArray bytes( mProcess->readAllStandardOutput() );
105 QTextCodec *codec = QTextCodec::codecForLocale();
106 const QString line( codec->toUnicode( bytes ) );
107
108 // Add the new output to the dialog box
109 mOutput->appendMessage( line );
110}
111
112void QgsRunProcess::stderrAvailable()
113{
114 const QByteArray bytes( mProcess->readAllStandardOutput() );
115 QTextCodec *codec = QTextCodec::codecForLocale();
116 const QString line( codec->toUnicode( bytes ) );
117
118 // Add the new output to the dialog box, but color it red
119 mOutput->appendMessage( "<font color=red>" + line + "</font>" );
120}
121
122void QgsRunProcess::processExit( int, QProcess::ExitStatus )
123{
124 // Because we catch the dialog box going (the dialogGone()
125 // function), and delete this instance, control will only pass to
126 // this function if the dialog box still exists when the process
127 // exits, so it's always safe to use the pointer to the dialog box
128 // (unless it was never created in the first case, which is what the
129 // test against 0 is for).
130
131 if ( mOutput )
132 {
133 mOutput->appendMessage( "<b>" + tr( "Done" ) + "</b>" );
134 }
135
136 // Since the dialog box takes care of deleting itself, and the
137 // process has gone, there's no need for this instance to stay
138 // around, so we disappear too.
139 die();
140}
141
142void QgsRunProcess::dialogGone()
143{
144 // The dialog has gone, so the user is no longer interested in the
145 // output from the process. Since the process will run happily
146 // without the QProcess object, this instance and its data can then
147 // go too, but disconnect the signals to prevent further functions in this
148 // class being called after it has been deleted (Qt seems not to be
149 // disconnecting them itself)
150
151 mOutput = nullptr;
152
153 disconnect( mProcess, &QProcess::errorOccurred, this, &QgsRunProcess::processError );
154 disconnect( mProcess, &QProcess::readyReadStandardOutput, this, &QgsRunProcess::stdoutAvailable );
155 disconnect( mProcess, &QProcess::readyReadStandardError, this, &QgsRunProcess::stderrAvailable );
156 disconnect( mProcess, static_cast < void ( QProcess::* )( int, QProcess::ExitStatus ) >( &QProcess::finished ), this, &QgsRunProcess::processExit );
157
158 die();
159}
160
161void QgsRunProcess::processError( QProcess::ProcessError err )
162{
163 if ( err == QProcess::FailedToStart )
164 {
165 QgsMessageOutput *output = mOutput ? mOutput : QgsMessageOutput::createMessageOutput();
166 output->setMessage( tr( "Unable to run command %1" ).arg( mCommand ), QgsMessageOutput::MessageText );
167 // Didn't work, so no need to hang around
168 die();
169 }
170 else
171 {
172 QgsDebugError( "Got error: " + QString( "%d" ).arg( err ) );
173 }
174}
175
176QStringList QgsRunProcess::splitCommand( const QString &command )
177{
178 return QProcess::splitCommand( command );
179}
180#else
181QgsRunProcess::QgsRunProcess( const QString &action, bool )
182{
183 Q_UNUSED( action )
184 QgsDebugError( "Skipping command: " + action );
185}
186
187QgsRunProcess::~QgsRunProcess()
188{
189}
190
191QStringList QgsRunProcess::splitCommand( const QString & )
192{
193 return QStringList();
194}
195#endif
196
197
198//
199// QgsBlockingProcess
200//
201
202#if QT_CONFIG(process)
203QgsBlockingProcess::QgsBlockingProcess( const QString &process, const QStringList &arguments )
204 : QObject()
205 , mProcess( process )
206 , mArguments( arguments )
207{
208
209}
210
211int QgsBlockingProcess::run( QgsFeedback *feedback )
212{
213 const bool requestMadeFromMainThread = QThread::currentThread() == QCoreApplication::instance()->thread();
214
215 int result = 0;
216 QProcess::ExitStatus exitStatus = QProcess::NormalExit;
217 QProcess::ProcessError error = QProcess::UnknownError;
218
219 const std::function<void()> runFunction = [ this, &result, &exitStatus, &error, feedback]()
220 {
221 // this function will always be run in worker threads -- either the blocking call is being made in a worker thread,
222 // or the blocking call has been made from the main thread and we've fired up a new thread for this function
223 Q_ASSERT( QThread::currentThread() != QgsApplication::instance()->thread() );
224
225 QProcess p;
226 const QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
227 p.setProcessEnvironment( env );
228
229 QEventLoop loop;
230 // connecting to aboutToQuit avoids an on-going process to remain stalled
231 // when QThreadPool::globalInstance()->waitForDone()
232 // is called at process termination
233 connect( qApp, &QCoreApplication::aboutToQuit, &loop, &QEventLoop::quit, Qt::DirectConnection );
234
235 if ( feedback )
236 QObject::connect( feedback, &QgsFeedback::canceled, &p, [ &p]
237 {
238#ifdef Q_OS_WIN
239 // From the qt docs:
240 // "Console applications on Windows that do not run an event loop, or whose
241 // event loop does not handle the WM_CLOSE message, can only be terminated by calling kill()."
242 p.kill();
243#else
244 p.terminate();
245#endif
246 } );
247 connect( &p, qOverload< int, QProcess::ExitStatus >( &QProcess::finished ), this, [&loop, &result, &exitStatus]( int res, QProcess::ExitStatus st )
248 {
249 result = res;
250 exitStatus = st;
251 loop.quit();
252 }, Qt::DirectConnection );
253
254 connect( &p, &QProcess::readyReadStandardOutput, &p, [&p, this]
255 {
256 const QByteArray ba = p.readAllStandardOutput();
257 mStdoutHandler( ba );
258 } );
259 connect( &p, &QProcess::readyReadStandardError, &p, [&p, this]
260 {
261 const QByteArray ba = p.readAllStandardError();
262 mStderrHandler( ba );
263 } );
264 p.start( mProcess, mArguments, QProcess::Unbuffered | QProcess::ReadWrite );
265 if ( !p.waitForStarted() )
266 {
267 result = 1;
268 exitStatus = QProcess::NormalExit;
269 error = p.error();
270 }
271 else
272 {
273 loop.exec();
274 }
275
276 mStdoutHandler( p.readAllStandardOutput() );
277 mStderrHandler( p.readAllStandardError() );
278 };
279
280 if ( requestMadeFromMainThread )
281 {
282 std::unique_ptr<ProcessThread> processThread = std::make_unique<ProcessThread>( runFunction );
283 processThread->start();
284 // wait for thread to gracefully exit
285 processThread->wait();
286 }
287 else
288 {
289 runFunction();
290 }
291
292 mExitStatus = exitStatus;
293 mProcessError = error;
294 return result;
295}
296
297QProcess::ExitStatus QgsBlockingProcess::exitStatus() const
298{
299 return mExitStatus;
300};
301
302QProcess::ProcessError QgsBlockingProcess::processError() const
303{
304 return mProcessError;
305};
306#endif // QT_CONFIG(process)
static QgsApplication * instance()
Returns the singleton instance of the QgsApplication.
Base class for feedback objects to be used for cancellation of something running in a worker thread.
Definition: qgsfeedback.h:44
void canceled()
Internal routines can connect to this signal if they use event loop.
Interface for showing messages from QGIS in GUI independent way.
static QgsMessageOutput * createMessageOutput()
function that returns new class derived from QgsMessageOutput (don't forget to delete it then if show...
virtual void setMessage(const QString &message, MessageType msgType)=0
Sets message, it won't be displayed until.
static QStringList splitCommand(const QString &command)
Splits the string command into a list of tokens, and returns the list.
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
#define QgsDebugError(str)
Definition: qgslogger.h:38