summaryrefslogtreecommitdiffstats
path: root/src/executor.cpp
blob: eab71ff88b82755e2a6e2e0c1474ff8761163558 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
#include "executor.h"
#include "debughelper.h"
#include <QCoreApplication>
#include <QDir>
#include <QTextCodec>

/**
 * @brief Executor::Executor executes external applications
 * @param parent
 */
Executor::Executor(QObject *parent) : QObject(parent), running(false) {
  connect(&m_process,
          static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(
              &QProcess::finished),
          this, static_cast<void (Executor::*)(int, QProcess::ExitStatus)>(
                    &Executor::finished));
  connect(&m_process, &QProcess::started, this, &Executor::starting);
}

/**
 * @brief Executor::executeNext consumes executable tasks from the queue
 */
void Executor::executeNext() {
  if (!running) {
    if (!m_execQueue.isEmpty()) {
      const execQueueItem &i = m_execQueue.head();
      running = true;
      if (!i.workingDir.isEmpty())
        m_process.setWorkingDirectory(i.workingDir);
      m_process.start(i.app, i.args);
      if (!i.input.isEmpty()) {
        m_process.waitForStarted(-1);
        QByteArray data = i.input.toUtf8();
        if (m_process.write(data) != data.length())
          dbg() << "Not all data written to process:" << i.id << " " << i.app;
      }
      m_process.closeWriteChannel();
    }
  }
}

/**
 * @brief Executor::execute execute an app
 * @param id
 * @param app
 * @param args
 * @param readStdout
 * @param readStderr
 */
void Executor::execute(int id, const QString &app, const QStringList &args,
                       bool readStdout, bool readStderr) {
  execute(id, QString(), app, args, QString(), readStdout, readStderr);
}

/**
 * @brief Executor::execute executes an app from a workDir
 * @param id
 * @param workDir
 * @param app
 * @param args
 * @param readStdout
 * @param readStderr
 */
void Executor::execute(int id, const QString &workDir, const QString &app,
                       const QStringList &args, bool readStdout,
                       bool readStderr) {
  execute(id, workDir, app, args, QString(), readStdout, readStderr);
}

/**
 * @brief Executor::execute an app, takes input and presents it as stdin
 * @param id
 * @param app
 * @param args
 * @param input
 * @param readStdout
 * @param readStderr
 */
void Executor::execute(int id, const QString &app, const QStringList &args,
                       QString input, bool readStdout, bool readStderr) {
  execute(id, QString(), app, args, input, readStdout, readStderr);
}

/**
 * @brief Executor::execute  executes an app from a workDir, takes input and
 * presents it as stdin
 * @param id
 * @param workDir
 * @param app
 * @param args
 * @param input
 * @param readStdout
 * @param readStderr
 */
void Executor::execute(int id, const QString &workDir, const QString &app,
                       const QStringList &args, QString input, bool readStdout,
                       bool readStderr) {
  // Happens a lot if e.g. git binary is not set.
  // This will result in bogus "QProcess::FailedToStart" messages,
  // also hiding legitimate errors from the gpg commands.
  if (app.isEmpty()) {
    dbg() << "Trying to execute nothing...";
    return;
  }
  QString appPath =
      QDir(QCoreApplication::applicationDirPath()).absoluteFilePath(app);
  m_execQueue.push_back(
      {id, appPath, args, input, readStdout, readStderr, workDir});
  executeNext();
}

/**
 * @brief Executor::executeBlocking blocking version of the executor,
 * takes input and presents it as stdin
 * @param app
 * @param args
 * @param input
 * @param process_out
 * @param process_err
 * @return
 *
 * TODO(bezet): it might make sense to throw here, a lot of possible errors
 */
int Executor::executeBlocking(QString app, const QStringList &args,
                              QString input, QString *process_out,
                              QString *process_err) {
  QProcess internal;
  internal.start(app, args);
  if (!input.isEmpty()) {
    QByteArray data = input.toUtf8();
    internal.waitForStarted(-1);
    if (internal.write(data) != data.length()) {
      dbg() << "Not all input written:" << app;
    }
    internal.closeWriteChannel();
  }
  internal.waitForFinished(-1);
  if (internal.exitStatus() == QProcess::NormalExit) {
    QTextCodec *codec = QTextCodec::codecForLocale();
    QString pout = codec->toUnicode(internal.readAllStandardOutput());
    QString perr = codec->toUnicode(internal.readAllStandardError());
    if (process_out != Q_NULLPTR)
      *process_out = pout;
    if (process_err != Q_NULLPTR)
      *process_err = perr;
    return internal.exitCode();
  } else {
    //  TODO(bezet): emit error() ?
    return -1; //    QProcess error code + qDebug error?
  }
}

/**
 * @brief Executor::executeBlocking blocking version of the executor
 * @param app
 * @param args
 * @param process_out
 * @param process_err
 * @return
 */
int Executor::executeBlocking(QString app, const QStringList &args,
                              QString *process_out, QString *process_err) {
  return executeBlocking(app, args, QString(), process_out, process_err);
}

/**
 * @brief Executor::setEnvironment set environment variables
 * for executor processes
 * @param env
 */
void Executor::setEnvironment(const QStringList &env) {
  m_process.setEnvironment(env);
}

/**
 * @brief Executor::cancelNext  cancels execution of first process in queue
 *                              if it's not already running
 *
 * @return  id of the cancelled process or -1 on error
 */
int Executor::cancelNext() {
  if (running || m_execQueue.isEmpty())
    return -1; //  TODO(bezet): definitely throw here
  return m_execQueue.dequeue().id;
}

/**
 * @brief Executor::finished called when an executed process finishes
 * @param exitCode
 * @param exitStatus
 */
void Executor::finished(int exitCode, QProcess::ExitStatus exitStatus) {
  execQueueItem i = m_execQueue.dequeue();
  running = false;
  if (exitStatus == QProcess::NormalExit) {
    QString output, err;
    QTextCodec *codec = QTextCodec::codecForLocale();
    if (i.readStdout)
      output = codec->toUnicode(m_process.readAllStandardOutput());
    if (i.readStderr or exitCode != 0) {
      err = codec->toUnicode(m_process.readAllStandardError());
      if (exitCode != 0)
        dbg() << exitCode << err;
    }
    emit finished(i.id, exitCode, output, err);
  }
  //	else: emit crashed with ID, which may give a chance to recover ?
  executeNext();
}