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