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