QGIS API Documentation 4.0.0-Norrköping (1ddcee3d0e4)
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 ), Qgis::StringFormat::Html );
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" ), tr( "Unable to run command\n%1" ).arg( action ), 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
95void QgsRunProcess::die()
96{
97 // safe way to do "delete this" for QObjects
98 deleteLater();
99}
100
101void QgsRunProcess::stdoutAvailable()
102{
103 const QByteArray bytes( mProcess->readAllStandardOutput() );
104 QTextCodec *codec = QTextCodec::codecForLocale();
105 const QString line( codec->toUnicode( bytes ) );
106
107 // Add the new output to the dialog box
108 mOutput->appendMessage( line );
109}
110
111void QgsRunProcess::stderrAvailable()
112{
113 const QByteArray bytes( mProcess->readAllStandardOutput() );
114 QTextCodec *codec = QTextCodec::codecForLocale();
115 const QString line( codec->toUnicode( bytes ) );
116
117 // Add the new output to the dialog box, but color it red
118 mOutput->appendMessage( "<font color=red>" + line + "</font>" );
119}
120
121void QgsRunProcess::processExit( int, QProcess::ExitStatus )
122{
123 // Because we catch the dialog box going (the dialogGone()
124 // function), and delete this instance, control will only pass to
125 // this function if the dialog box still exists when the process
126 // exits, so it's always safe to use the pointer to the dialog box
127 // (unless it was never created in the first case, which is what the
128 // test against 0 is for).
129
130 if ( mOutput )
131 {
132 mOutput->appendMessage( "<b>" + tr( "Done" ) + "</b>" );
133 }
134
135 // Since the dialog box takes care of deleting itself, and the
136 // process has gone, there's no need for this instance to stay
137 // around, so we disappear too.
138 die();
139}
140
141void QgsRunProcess::dialogGone()
142{
143 // The dialog has gone, so the user is no longer interested in the
144 // output from the process. Since the process will run happily
145 // without the QProcess object, this instance and its data can then
146 // go too, but disconnect the signals to prevent further functions in this
147 // class being called after it has been deleted (Qt seems not to be
148 // disconnecting them itself)
149
150 mOutput = nullptr;
151
152 disconnect( mProcess.get(), &QProcess::errorOccurred, this, &QgsRunProcess::processError );
153 disconnect( mProcess.get(), &QProcess::readyReadStandardOutput, this, &QgsRunProcess::stdoutAvailable );
154 disconnect( mProcess.get(), &QProcess::readyReadStandardError, this, &QgsRunProcess::stderrAvailable );
155 disconnect( mProcess.get(), static_cast< void ( QProcess::* )( int, QProcess::ExitStatus ) >( &QProcess::finished ), this, &QgsRunProcess::processExit );
156
157 die();
158}
159
160void QgsRunProcess::processError( QProcess::ProcessError err )
161{
162 if ( err == QProcess::FailedToStart )
163 {
164 QgsMessageOutput *output = mOutput ? mOutput : QgsMessageOutput::createMessageOutput();
165 output->setMessage( tr( "Unable to run command %1" ).arg( mCommand ), Qgis::StringFormat::PlainText );
166 // Didn't work, so no need to hang around
167 die();
168 }
169 else
170 {
171 QgsDebugError( "Got error: " + QString( "%d" ).arg( err ) );
172 }
173}
174
175QStringList QgsRunProcess::splitCommand( const QString &command )
176{
177 return QProcess::splitCommand( command );
178}
179#else
180QgsRunProcess::QgsRunProcess( const QString &action, bool )
181{
182 Q_UNUSED( action )
183 QgsDebugError( "Skipping command: " + action );
184}
185
186QgsRunProcess::~QgsRunProcess()
187{}
188
189QStringList QgsRunProcess::splitCommand( const QString & )
190{
191 return QStringList();
192}
193#endif
194
195
196//
197// QgsBlockingProcess
198//
199
200#if QT_CONFIG( process )
201QgsBlockingProcess::QgsBlockingProcess( const QString &process, const QStringList &arguments )
202 : QObject()
203 , mProcess( process )
204 , mArguments( arguments )
205{}
206
207int QgsBlockingProcess::run( QgsFeedback *feedback )
208{
209 const bool requestMadeFromMainThread = QThread::currentThread() == QCoreApplication::instance()->thread();
210
211 int result = 0;
212 QProcess::ExitStatus exitStatus = QProcess::NormalExit;
213 QProcess::ProcessError error = QProcess::UnknownError;
214
215 const std::function<void()> runFunction = [this, &result, &exitStatus, &error, feedback]() {
216 // this function will always be run in worker threads -- either the blocking call is being made in a worker thread,
217 // or the blocking call has been made from the main thread and we've fired up a new thread for this function
218 Q_ASSERT( QThread::currentThread() != QgsApplication::instance()->thread() );
219
220 QProcess p;
221 const QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
222 p.setProcessEnvironment( env );
223
224 QEventLoop loop;
225 // connecting to aboutToQuit avoids an on-going process to remain stalled
226 // when QThreadPool::globalInstance()->waitForDone()
227 // is called at process termination
228 connect( qApp, &QCoreApplication::aboutToQuit, &loop, &QEventLoop::quit, Qt::DirectConnection );
229
230 if ( feedback )
231 QObject::connect( feedback, &QgsFeedback::canceled, &p, [&p] {
232#ifdef Q_OS_WIN
233 // From the qt docs:
234 // "Console applications on Windows that do not run an event loop, or whose
235 // event loop does not handle the WM_CLOSE message, can only be terminated by calling kill()."
236 p.kill();
237#else
238 p.terminate();
239#endif
240 } );
241 connect(
242 &p,
243 qOverload< int, QProcess::ExitStatus >( &QProcess::finished ),
244 this,
245 [&loop, &result, &exitStatus]( int res, QProcess::ExitStatus st ) {
246 result = res;
247 exitStatus = st;
248 loop.quit();
249 },
250 Qt::DirectConnection
251 );
252
253 connect( &p, &QProcess::readyReadStandardOutput, &p, [&p, this] {
254 const QByteArray ba = p.readAllStandardOutput();
255 mStdoutHandler( ba );
256 } );
257 connect( &p, &QProcess::readyReadStandardError, &p, [&p, this] {
258 const QByteArray ba = p.readAllStandardError();
259 mStderrHandler( ba );
260 } );
261 p.start( mProcess, mArguments, QProcess::Unbuffered | QProcess::ReadWrite );
262 if ( !p.waitForStarted() )
263 {
264 result = 1;
265 exitStatus = QProcess::NormalExit;
266 error = p.error();
267 }
268 else
269 {
270 loop.exec();
271 }
272
273 mStdoutHandler( p.readAllStandardOutput() );
274 mStderrHandler( p.readAllStandardError() );
275 };
276
277 if ( requestMadeFromMainThread )
278 {
279 auto processThread = std::make_unique<ProcessThread>( runFunction );
280 processThread->start();
281 // wait for thread to gracefully exit
282 processThread->wait();
283 }
284 else
285 {
286 runFunction();
287 }
288
289 mExitStatus = exitStatus;
290 mProcessError = error;
291 return result;
292}
293
294QProcess::ExitStatus QgsBlockingProcess::exitStatus() const
295{
296 return mExitStatus;
297};
298
299QProcess::ProcessError QgsBlockingProcess::processError() const
300{
301 return mProcessError;
302};
303#endif // QT_CONFIG(process)
@ Html
HTML message.
Definition qgis.h:177
@ PlainText
Text message.
Definition qgis.h:176
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, Qgis::StringFormat format)=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:63
#define QgsDebugError(str)
Definition qgslogger.h:59