1 /******************************************************************************
2 This source file is part of the Avogadro project.
3 This source code is released under the 3-Clause BSD License, (see "LICENSE").
4 ******************************************************************************/
5
6 #include "pythonscript.h"
7
8 #include "avogadropython.h"
9
10 #include <QtCore/QDebug>
11 #include <QtCore/QLocale>
12 #include <QtCore/QProcess>
13 #include <QtCore/QSettings>
14
15 namespace Avogadro {
16 namespace QtGui {
17
PythonScript(const QString & scriptFilePath_,QObject * parent_)18 PythonScript::PythonScript(const QString& scriptFilePath_, QObject* parent_)
19 : QObject(parent_), m_debug(!qgetenv("AVO_PYTHON_SCRIPT_DEBUG").isEmpty()),
20 m_scriptFilePath(scriptFilePath_), m_process(nullptr)
21 {
22 setDefaultPythonInterpretor();
23 }
24
PythonScript(QObject * parent_)25 PythonScript::PythonScript(QObject* parent_)
26 : QObject(parent_), m_debug(!qgetenv("AVO_PYTHON_SCRIPT_DEBUG").isEmpty()),
27 m_process(nullptr)
28 {
29 setDefaultPythonInterpretor();
30 }
31
~PythonScript()32 PythonScript::~PythonScript() {}
33
setScriptFilePath(const QString & scriptFile)34 void PythonScript::setScriptFilePath(const QString& scriptFile)
35 {
36 m_scriptFilePath = scriptFile;
37 }
38
setDefaultPythonInterpretor()39 void PythonScript::setDefaultPythonInterpretor()
40 {
41 m_pythonInterpreter = qgetenv("AVO_PYTHON_INTERPRETER");
42 if (m_pythonInterpreter.isEmpty()) {
43 m_pythonInterpreter =
44 QSettings().value(QStringLiteral("interpreters/python")).toString();
45 }
46 if (m_pythonInterpreter.isEmpty())
47 m_pythonInterpreter = pythonInterpreterPath;
48 }
49
execute(const QStringList & args,const QByteArray & scriptStdin)50 QByteArray PythonScript::execute(const QStringList& args,
51 const QByteArray& scriptStdin)
52 {
53 clearErrors();
54 QProcess proc;
55
56 // Merge stdout and stderr
57 proc.setProcessChannelMode(QProcess::MergedChannels);
58
59 // Add debugging flag if needed.
60 QStringList realArgs(args);
61 if (m_debug)
62 realArgs.prepend(QStringLiteral("--debug"));
63
64 // Add the global language / locale to *all* calls
65 realArgs.append("--lang");
66 realArgs.append(QLocale::system().name());
67
68 // Start script
69 realArgs.prepend(m_scriptFilePath);
70 if (m_debug) {
71 qDebug() << "Executing" << m_pythonInterpreter
72 << realArgs.join(QStringLiteral(" ")) << "<" << scriptStdin;
73 }
74 proc.start(m_pythonInterpreter, realArgs);
75
76 // Write scriptStdin to the process's stdin
77 if (!scriptStdin.isNull()) {
78 if (!proc.waitForStarted(5000)) {
79 m_errors << tr("Error running script '%1 %2': Timed out waiting for "
80 "start (%3).")
81 .arg(m_pythonInterpreter,
82 realArgs.join(QStringLiteral(" ")),
83 processErrorString(proc));
84 return QByteArray();
85 }
86
87 qint64 len = proc.write(scriptStdin);
88 if (len != static_cast<qint64>(scriptStdin.size())) {
89 m_errors << tr("Error running script '%1 %2': failed to write to stdin "
90 "(len=%3, wrote %4 bytes, QProcess error: %5).")
91 .arg(m_pythonInterpreter)
92 .arg(realArgs.join(QStringLiteral(" ")))
93 .arg(scriptStdin.size())
94 .arg(len)
95 .arg(processErrorString(proc));
96 return QByteArray();
97 }
98 proc.closeWriteChannel();
99 }
100
101 if (!proc.waitForFinished(5000)) {
102 m_errors << tr("Error running script '%1 %2': Timed out waiting for "
103 "finish (%3).")
104 .arg(m_pythonInterpreter, realArgs.join(QStringLiteral(" ")),
105 processErrorString(proc));
106 return QByteArray();
107 }
108
109 if (proc.exitStatus() != QProcess::NormalExit || proc.exitCode() != 0) {
110 m_errors << tr("Error running script '%1 %2': Abnormal exit status %3 "
111 "(%4: %5)\n\nOutput:\n%6")
112 .arg(m_pythonInterpreter)
113 .arg(realArgs.join(QStringLiteral(" ")))
114 .arg(proc.exitCode())
115 .arg(processErrorString(proc))
116 .arg(proc.errorString())
117 .arg(QString(proc.readAll()));
118 return QByteArray();
119 }
120
121 QByteArray result(proc.readAll());
122
123 if (m_debug)
124 qDebug() << "Output:" << result;
125
126 return result;
127 }
128
asyncExecute(const QStringList & args,const QByteArray & scriptStdin)129 void PythonScript::asyncExecute(const QStringList& args,
130 const QByteArray& scriptStdin)
131 {
132 clearErrors();
133 if (m_process != nullptr) {
134 // bad news
135 m_process->terminate();
136 disconnect(m_process, SIGNAL(finished()), this, SLOT(processsFinished()));
137 m_process->deleteLater();
138 }
139 m_process = new QProcess(parent());
140
141 // Merge stdout and stderr
142 m_process->setProcessChannelMode(QProcess::MergedChannels);
143
144 // Add debugging flag if needed.
145 QStringList realArgs(args);
146 if (m_debug)
147 realArgs.prepend(QStringLiteral("--debug"));
148
149 // Add the global language / locale to *all* calls
150 realArgs.append("--lang");
151 realArgs.append(QLocale::system().name());
152
153 // Start script
154 realArgs.prepend(m_scriptFilePath);
155 if (m_debug) {
156 qDebug() << "Executing" << m_pythonInterpreter
157 << realArgs.join(QStringLiteral(" ")) << "<" << scriptStdin;
158 }
159 m_process->start(m_pythonInterpreter, realArgs);
160
161 // Write scriptStdin to the process's stdin
162 if (!scriptStdin.isNull()) {
163 if (!m_process->waitForStarted(5000)) {
164 m_errors << tr("Error running script '%1 %2': Timed out waiting for "
165 "start (%3).")
166 .arg(m_pythonInterpreter,
167 realArgs.join(QStringLiteral(" ")),
168 processErrorString(*m_process));
169 return;
170 }
171
172 qint64 len = m_process->write(scriptStdin);
173 if (len != static_cast<qint64>(scriptStdin.size())) {
174 m_errors << tr("Error running script '%1 %2': failed to write to stdin "
175 "(len=%3, wrote %4 bytes, QProcess error: %5).")
176 .arg(m_pythonInterpreter)
177 .arg(realArgs.join(QStringLiteral(" ")))
178 .arg(scriptStdin.size())
179 .arg(len)
180 .arg(processErrorString(*m_process));
181 return;
182 }
183 m_process->closeWriteChannel();
184 }
185
186 // let the script run
187 connect(m_process, SIGNAL(finished(int, QProcess::ExitStatus)), this,
188 SLOT(processFinished(int, QProcess::ExitStatus)));
189 }
190
processFinished(int exitCode,QProcess::ExitStatus exitStatus)191 void PythonScript::processFinished(int exitCode,
192 QProcess::ExitStatus exitStatus)
193 {
194 emit finished();
195 }
196
asyncResponse()197 QByteArray PythonScript::asyncResponse()
198 {
199 if (m_process == nullptr || m_process->state() == QProcess::Running) {
200 return QByteArray(); // wait
201 }
202
203 return m_process->readAll();
204 }
205
processErrorString(const QProcess & proc) const206 QString PythonScript::processErrorString(const QProcess& proc) const
207 {
208 QString result;
209 switch (proc.error()) {
210 case QProcess::FailedToStart:
211 result = tr("Script failed to start.");
212 break;
213 case QProcess::Crashed:
214 result = tr("Script crashed.");
215 break;
216 case QProcess::Timedout:
217 result = tr("Script timed out.");
218 break;
219 case QProcess::ReadError:
220 result = tr("Read error.");
221 break;
222 case QProcess::WriteError:
223 result = tr("Write error.");
224 break;
225 default:
226 case QProcess::UnknownError:
227 result = tr("Unknown error.");
228 break;
229 }
230 return result;
231 }
232
233 } // namespace QtGui
234 } // namespace Avogadro
235