LCOV - code coverage report
Current view: top level - src - executor.cpp (source / functions) Coverage Total Hit
Test: .lcov.total Lines: 27.7 % 83 23
Test Date: 2026-03-23 21:55:57 Functions: 25.0 % 12 3

            Line data    Source code
       1              : // SPDX-FileCopyrightText: 2016 Anne Jan Brouwer
       2              : // SPDX-License-Identifier: GPL-3.0-or-later
       3              : #include "executor.h"
       4              : #include <QCoreApplication>
       5              : #include <QDir>
       6              : #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
       7              : #include <QTextCodec>
       8              : #endif
       9              : #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
      10              : #include <QStringDecoder>
      11              : #include <utility>
      12              : #endif
      13              : 
      14              : #ifdef QT_DEBUG
      15              : #include "debughelper.h"
      16              : #endif
      17              : 
      18              : /**
      19              :  * @brief Executor::Executor executes external applications
      20              :  * @param parent
      21              :  */
      22           78 : Executor::Executor(QObject *parent) : QObject(parent), running(false) {
      23           78 :   connect(&m_process,
      24              :           static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(
      25              :               &QProcess::finished),
      26              :           this,
      27           78 :           static_cast<void (Executor::*)(int, QProcess::ExitStatus)>(
      28              :               &Executor::finished));
      29           78 :   connect(&m_process, &QProcess::started, this, &Executor::starting);
      30           78 : }
      31              : 
      32              : /**
      33              :  * @brief Executor::executeNext consumes executable tasks from the queue
      34              :  */
      35            0 : void Executor::executeNext() {
      36            0 :   if (!running) {
      37            0 :     if (!m_execQueue.isEmpty()) {
      38              :       const execQueueItem &i = m_execQueue.head();
      39            0 :       running = true;
      40            0 :       if (!i.workingDir.isEmpty()) {
      41            0 :         m_process.setWorkingDirectory(i.workingDir);
      42              :       }
      43            0 :       if (i.app.startsWith("wsl ")) {
      44              :         QStringList tmp = i.args;
      45              :         QString app = i.app;
      46            0 :         tmp.prepend(app.remove(0, 4));
      47            0 :         m_process.start("wsl", tmp);
      48              :       } else {
      49            0 :         m_process.start(i.app, i.args);
      50              :       }
      51            0 :       if (!i.input.isEmpty()) {
      52            0 :         m_process.waitForStarted(-1);
      53            0 :         QByteArray data = i.input.toUtf8();
      54            0 :         if (m_process.write(data) != data.length()) {
      55              : #ifdef QT_DEBUG
      56              :           dbg() << "Not all data written to process:" << i.id << " " << i.app;
      57              : #endif
      58              :         }
      59              :       }
      60            0 :       m_process.closeWriteChannel();
      61              :     }
      62              :   }
      63            0 : }
      64              : 
      65              : /**
      66              :  * @brief Executor::execute execute an app
      67              :  * @param id
      68              :  * @param app
      69              :  * @param args
      70              :  * @param readStdout
      71              :  * @param readStderr
      72              :  */
      73            0 : void Executor::execute(int id, const QString &app, const QStringList &args,
      74              :                        bool readStdout, bool readStderr) {
      75            0 :   execute(id, QString(), app, args, QString(), readStdout, readStderr);
      76            0 : }
      77              : 
      78              : /**
      79              :  * @brief Executor::execute executes an app from a workDir
      80              :  * @param id
      81              :  * @param workDir
      82              :  * @param app
      83              :  * @param args
      84              :  * @param readStdout
      85              :  * @param readStderr
      86              :  */
      87            0 : void Executor::execute(int id, const QString &workDir, const QString &app,
      88              :                        const QStringList &args, bool readStdout,
      89              :                        bool readStderr) {
      90            0 :   execute(id, workDir, app, args, QString(), readStdout, readStderr);
      91            0 : }
      92              : 
      93              : /**
      94              :  * @brief Executor::execute an app, takes input and presents it as stdin
      95              :  * @param id
      96              :  * @param app
      97              :  * @param args
      98              :  * @param input
      99              :  * @param readStdout
     100              :  * @param readStderr
     101              :  */
     102            0 : void Executor::execute(int id, const QString &app, const QStringList &args,
     103              :                        QString input, bool readStdout, bool readStderr) {
     104            0 :   execute(id, QString(), app, args, std::move(input), readStdout, readStderr);
     105            0 : }
     106              : 
     107              : /**
     108              :  * @brief Executor::execute  executes an app from a workDir, takes input and
     109              :  * presents it as stdin
     110              :  * @param id
     111              :  * @param workDir
     112              :  * @param app
     113              :  * @param args
     114              :  * @param input
     115              :  * @param readStdout
     116              :  * @param readStderr
     117              :  */
     118            0 : void Executor::execute(int id, const QString &workDir, const QString &app,
     119              :                        const QStringList &args, QString input, bool readStdout,
     120              :                        bool readStderr) {
     121              :   // Happens a lot if e.g. git binary is not set.
     122              :   // This will result in bogus "QProcess::FailedToStart" messages,
     123              :   // also hiding legitimate errors from the gpg commands.
     124            0 :   if (app.isEmpty()) {
     125              : #ifdef QT_DEBUG
     126              :     dbg() << "Trying to execute nothing...";
     127              : #endif
     128            0 :     return;
     129              :   }
     130              :   QString appPath = app;
     131            0 :   if (!appPath.startsWith("wsl ")) {
     132              :     appPath =
     133            0 :         QDir(QCoreApplication::applicationDirPath()).absoluteFilePath(app);
     134              :   }
     135            0 :   m_execQueue.push_back(
     136              :       {id, appPath, args, std::move(input), readStdout, readStderr, workDir});
     137            0 :   executeNext();
     138            0 : }
     139              : 
     140              : /**
     141              :  * @brief decodes the input into a string assuming UTF-8 encoding.
     142              :  * If this fails (which is likely if it is not actually UTF-8)
     143              :  * it will then fall back to Qt's decoding function, which
     144              :  * will try based on BOM and if that fails fall back to local encoding.
     145              :  * This should not be needed in Qt6
     146              :  *
     147              :  * @param in input data
     148              :  * @return Input bytes decoded to string
     149              :  */
     150          182 : static auto decodeAssumingUtf8(const QByteArray &in) -> QString {
     151              : #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
     152              :   QTextCodec *codec = QTextCodec::codecForName("UTF-8");
     153              :   QTextCodec::ConverterState state;
     154              :   QString out = codec->toUnicode(in.constData(), in.size(), &state);
     155              :   if (!state.invalidChars)
     156              :     return out;
     157              :   codec = QTextCodec::codecForUtfText(in);
     158              :   return codec->toUnicode(in);
     159              : #else
     160          182 :   auto converter = QStringDecoder(QStringDecoder::Utf8);
     161          182 :   return converter(in);
     162              : #endif
     163              : }
     164              : 
     165              : /**
     166              :  * @brief Executor::executeBlocking blocking version of the executor,
     167              :  * takes input and presents it as stdin
     168              :  * @param app
     169              :  * @param args
     170              :  * @param input
     171              :  * @param process_out
     172              :  * @param process_err
     173              :  * @return
     174              :  *
     175              :  * TODO(bezet): it might make sense to throw here, a lot of possible errors
     176              :  */
     177           91 : auto Executor::executeBlocking(QString app, const QStringList &args,
     178              :                                const QString &input, QString *process_out,
     179              :                                QString *process_err) -> int {
     180           91 :   QProcess internal;
     181          182 :   if (app.startsWith("wsl ")) {
     182              :     QStringList tmp = args;
     183            0 :     tmp.prepend(app.remove(0, 4));
     184            0 :     internal.start("wsl", tmp);
     185              :   } else {
     186           91 :     internal.start(app, args);
     187              :   }
     188           91 :   if (!input.isEmpty()) {
     189              :     QByteArray data = input.toUtf8();
     190            0 :     internal.waitForStarted(-1);
     191            0 :     if (internal.write(data) != data.length()) {
     192              : #ifdef QT_DEBUG
     193              :       dbg() << "Not all input written:" << app;
     194              : #endif
     195              :     }
     196            0 :     internal.closeWriteChannel();
     197              :   }
     198           91 :   internal.waitForFinished(-1);
     199           91 :   if (internal.exitStatus() == QProcess::NormalExit) {
     200           91 :     QString pout = decodeAssumingUtf8(internal.readAllStandardOutput());
     201           91 :     QString perr = decodeAssumingUtf8(internal.readAllStandardError());
     202           91 :     if (process_out != nullptr) {
     203           91 :       *process_out = pout;
     204              :     }
     205           91 :     if (process_err != nullptr) {
     206           26 :       *process_err = perr;
     207              :     }
     208           91 :     return internal.exitCode();
     209              :   }
     210              :   // TODO(bezet): emit error() ?
     211              :   return -1; //    QProcess error code + qDebug error?
     212           91 : }
     213              : 
     214              : /**
     215              :  * @brief Executor::executeBlocking blocking version of the executor
     216              :  * @param app
     217              :  * @param args
     218              :  * @param process_out
     219              :  * @param process_err
     220              :  * @return
     221              :  */
     222            0 : auto Executor::executeBlocking(QString app, const QStringList &args,
     223              :                                QString *process_out, QString *process_err)
     224              :     -> int {
     225            0 :   return executeBlocking(std::move(app), args, QString(), process_out,
     226            0 :                          process_err);
     227              : }
     228              : 
     229              : /**
     230              :  * @brief Executor::setEnvironment set environment variables
     231              :  * for executor processes
     232              :  * @param env
     233              :  */
     234            0 : void Executor::setEnvironment(const QStringList &env) {
     235            0 :   m_process.setEnvironment(env);
     236            0 : }
     237              : 
     238              : /**
     239              :  * @brief Executor::cancelNext  cancels execution of first process in queue
     240              :  *                              if it's not already running
     241              :  *
     242              :  * @return  id of the cancelled process or -1 on error
     243              :  */
     244            0 : auto Executor::cancelNext() -> int {
     245            0 :   if (running || m_execQueue.isEmpty()) {
     246            0 :     return -1; // TODO(bezet): definitely throw here
     247              :   }
     248            0 :   return m_execQueue.dequeue().id;
     249              : }
     250              : 
     251              : /**
     252              :  * @brief Executor::finished called when an executed process finishes
     253              :  * @param exitCode
     254              :  * @param exitStatus
     255              :  */
     256            0 : void Executor::finished(int exitCode, QProcess::ExitStatus exitStatus) {
     257              :   execQueueItem i = m_execQueue.dequeue();
     258            0 :   running = false;
     259            0 :   if (exitStatus == QProcess::NormalExit) {
     260            0 :     QString output;
     261            0 :     QString err;
     262            0 :     if (i.readStdout) {
     263            0 :       output = decodeAssumingUtf8(m_process.readAllStandardOutput());
     264              :     }
     265            0 :     if (i.readStderr || exitCode != 0) {
     266            0 :       err = decodeAssumingUtf8(m_process.readAllStandardError());
     267              :       if (exitCode != 0) {
     268              : #ifdef QT_DEBUG
     269              :         dbg() << exitCode << err;
     270              : #endif
     271              :       }
     272              :     }
     273            0 :     emit finished(i.id, exitCode, output, err);
     274              :   }
     275              :   //  else: emit crashed with ID, which may give a chance to recover ?
     276            0 :   executeNext();
     277            0 : }
        

Generated by: LCOV version 2.4-beta