LCOV - code coverage report
Current view: top level - tools/controller - controller.cpp (source / functions) Coverage Total Hit
Test: QtApplicationManager Lines: 73.9 % 717 530
Test Date: 2024-05-11 10:19:06 Functions: 73.3 % 45 33
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 39.3 % 1478 581

             Branch data     Line data    Source code
       1                 :             : // Copyright (C) 2021 The Qt Company Ltd.
       2                 :             : // Copyright (C) 2019 Luxoft Sweden AB
       3                 :             : // Copyright (C) 2018 Pelagicore AG
       4                 :             : // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
       5                 :             : 
       6                 :             : #include <QCoreApplication>
       7                 :             : #include <QCommandLineParser>
       8                 :             : #include <QCommandLineOption>
       9                 :             : #include <QStringList>
      10                 :             : #include <QDir>
      11                 :             : #include <QTemporaryFile>
      12                 :             : #include <QFileInfo>
      13                 :             : #include <QDBusConnection>
      14                 :             : #include <QDBusPendingReply>
      15                 :             : #include <QDBusServiceWatcher>
      16                 :             : #include <QDBusError>
      17                 :             : #include <QMetaObject>
      18                 :             : #include <QStringBuilder>
      19                 :             : #include <QRegularExpression>
      20                 :             : #include <QJsonDocument>
      21                 :             : #include <QLockFile>
      22                 :             : 
      23                 :             : #include <functional>
      24                 :             : 
      25                 :             : #include <QtAppManCommon/global.h>
      26                 :             : #include <QtAppManCommon/error.h>
      27                 :             : #include <QtAppManCommon/exception.h>
      28                 :             : #include <QtAppManCommon/unixsignalhandler.h>
      29                 :             : #include <QtAppManCommon/utilities.h>
      30                 :             : #include <QtAppManCommon/qtyaml.h>
      31                 :             : #include <QtAppManCommon/dbus-utilities.h>
      32                 :             : 
      33                 :             : #include "applicationmanager_interface.h"
      34                 :             : #include "packagemanager_interface.h"
      35                 :             : 
      36                 :             : #include "interrupthandler.h"
      37                 :             : 
      38                 :             : using namespace Qt::StringLiterals;
      39                 :             : 
      40                 :             : QT_USE_NAMESPACE_AM
      41                 :             : 
      42                 :             : class DBus : public QObject
      43                 :             : {
      44                 :             :     Q_OBJECT
      45                 :             : 
      46                 :             : public:
      47                 :          19 :     DBus()
      48         [ +  - ]:          19 :         : m_dbusService(u"io.qt.ApplicationManager"_s)
      49                 :             : 
      50                 :             :     {
      51         [ +  - ]:          19 :         registerDBusTypes();
      52                 :          19 :     }
      53                 :             : 
      54                 :          19 :     void setInstanceInfo(const QVariantMap &instanceInfo)
      55                 :             :     {
      56   [ +  -  +  - ]:          19 :         m_dbusAddresses = instanceInfo[u"dbus"_s].toMap();
      57                 :          19 :     }
      58                 :             : 
      59                 :          12 :     void connectToManager() noexcept(false)
      60                 :             :     {
      61         [ -  + ]:          12 :         if (m_manager)
      62                 :           0 :             return;
      63                 :             : 
      64         [ +  - ]:          12 :         auto conn = connectTo(u"io.qt.ApplicationManager"_s);
      65                 :          12 :         m_manager = tryConnectToDBusInterface<IoQtApplicationManagerInterface>(m_dbusService,
      66         [ +  - ]:          24 :                                                                                u"/ApplicationManager"_s,
      67         [ +  - ]:          12 :                                                                                conn.name(), this);
      68         [ -  + ]:          12 :         if (!m_manager) {
      69                 :           0 :             throw Exception("Could not connect to the io.qt.ApplicationManager D-Bus interface on %1")
      70                 :           0 :                 .arg(m_dbusName);
      71                 :             :         }
      72                 :          12 :     }
      73                 :             : 
      74                 :          10 :     void connectToPackager() noexcept(false)
      75                 :             :     {
      76         [ -  + ]:          10 :         if (m_packager)
      77                 :           0 :             return;
      78                 :             : 
      79         [ +  - ]:          10 :         auto conn = connectTo(u"io.qt.PackageManager"_s);
      80                 :          10 :         m_packager = tryConnectToDBusInterface<IoQtPackageManagerInterface>(m_dbusService,
      81         [ +  - ]:          20 :                                                                             u"/PackageManager"_s,
      82         [ +  - ]:          10 :                                                                             conn.name(), this);
      83         [ -  + ]:          10 :         if (!m_packager) {
      84                 :           0 :             throw Exception("Could not connect to the io.qt.PackageManager D-Bus interface on %1")
      85                 :           0 :                 .arg(m_dbusName);
      86                 :             :         }
      87                 :          10 :     }
      88                 :             : 
      89                 :             :     Q_SIGNAL void disconnected(QString reason);
      90                 :             : 
      91                 :             : private:
      92                 :             :     template<typename T>
      93                 :          22 :     static T *tryConnectToDBusInterface(const QString &service, const QString &path,
      94                 :             :                                         const QString &connectionName, QObject *parent)
      95                 :             :     {
      96                 :             :         // we are working with very small delays in the milli-second range here, so a linear factor
      97                 :             :         // to support valgrind would have to be very large and probably conflict with usage elsewhere
      98                 :             :         // in the codebase, where the ranges are normally in the seconds.
      99   [ +  -  +  -  :          22 :         static const int timeout = timeoutFactor() * timeoutFactor();
             +  -  +  - ]
     100                 :             : 
     101                 :          22 :         QDBusConnection conn(connectionName);
     102                 :             : 
     103   [ +  -  +  - ]:          22 :         if (!conn.isConnected())
     104                 :             :             return nullptr;
     105   [ +  -  +  -  :          22 :         if (!service.isEmpty() && conn.interface()) {
                   +  - ]
     106                 :             :             // the 'T' constructor can block up to 25sec (!), if the service is not registered!
     107   [ +  -  +  -  :          44 :             if (!conn.interface()->isServiceRegistered(service))
                   +  - ]
     108                 :             :                 return nullptr;
     109                 :             :         }
     110                 :             : 
     111                 :          22 :         QElapsedTimer timer;
     112                 :          22 :         timer.start();
     113                 :             : 
     114                 :             :         do {
     115   [ +  -  +  - ]:          22 :             T *iface = new T(service, path, conn, parent);
     116   [ +  -  +  -  :          44 :             if (!iface->lastError().isValid())
                   +  - ]
     117                 :          22 :                 return iface;
     118                 :           0 :             delete iface;
     119         [ #  # ]:           0 :             QThread::msleep(static_cast<unsigned long>(timeout));
     120         [ #  # ]:           0 :         } while (timer.elapsed() < (100 * timeout)); // 100msec base line
     121                 :             : 
     122                 :             :         return nullptr;
     123                 :          22 :     }
     124                 :             : 
     125                 :          22 :     QDBusConnection connectTo(const QString &iface) noexcept(false)
     126                 :             :     {
     127                 :          22 :         QDBusConnection conn(iface);
     128                 :             : 
     129   [ +  -  +  - ]:          44 :         QString dbus = m_dbusAddresses.value(iface).toString();
     130         [ -  + ]:          22 :         if (dbus == u"system") {
     131         [ #  # ]:           0 :             conn = QDBusConnection::systemBus();
     132                 :           0 :             m_dbusName = u"[system-bus]"_s;
     133         [ -  + ]:          22 :         } else if (dbus.isEmpty()) {
     134         [ #  # ]:           0 :             conn = QDBusConnection::sessionBus();
     135                 :           0 :             m_dbusName = u"[session-bus]"_s;
     136         [ -  + ]:          22 :         } else if (dbus.startsWith(u"p2p:")) {
     137         [ #  # ]:           0 :             const auto address = dbus.mid(4);
     138         [ #  # ]:           0 :             conn = QDBusConnection::connectToPeer(address, u"p2p"_s);
     139         [ #  # ]:           0 :             m_dbusName = u"[p2p] "_s + address;
     140                 :           0 :             m_dbusService.clear(); // no service names allowed on p2p busses
     141                 :           0 :         } else {
     142         [ +  - ]:          22 :             conn = QDBusConnection::connectToBus(dbus, u"custom"_s);
     143                 :          22 :             m_dbusName = dbus;
     144                 :             :         }
     145                 :             : 
     146   [ +  -  -  + ]:          22 :         if (!conn.isConnected()) {
     147                 :           0 :             throw Exception(Error::IO, "Could not connect to the application manager D-Bus interface %1 at %2: %3")
     148   [ #  #  #  # ]:           0 :                 .arg(iface, m_dbusName, conn.lastError().message());
     149                 :             :         }
     150                 :             : 
     151         [ +  - ]:          22 :         installDisconnectWatcher(conn, u"io.qt.ApplicationManager"_s);
     152                 :          22 :         return conn;
     153                 :          22 :     }
     154                 :             : 
     155                 :          22 :     void installDisconnectWatcher(const QDBusConnection &conn, const QString &serviceName)
     156                 :             :     {
     157         [ +  - ]:          22 :         if (m_disconnectedEmitted)
     158                 :             :             return;
     159                 :             : 
     160         [ +  + ]:          22 :         if (!m_connections.contains(conn.name())) {
     161         [ +  - ]:          19 :             auto *watcher = new QDBusServiceWatcher(serviceName, conn, QDBusServiceWatcher::WatchForOwnerChange, this);
     162                 :          19 :             connect(watcher, &QDBusServiceWatcher::serviceOwnerChanged,
     163                 :           0 :                     this, [this](const QString &, const QString &, const QString &) {
     164         [ #  # ]:           0 :                 disconnectDetected(u"owner changed"_s);
     165                 :           0 :             });
     166         [ +  - ]:          38 :             m_connections.append(conn.name());
     167                 :             :         }
     168                 :             : 
     169                 :             :         // serviceOwnerChanged does not work if the bus-daemon process dies (as is the case when
     170                 :             :         // the AM starts its own session bus in --dbus=auto mode and then later crashes, killing
     171                 :             :         // the bus-daemon with it).
     172                 :             :         // QDBusConnection::isConnected() does not have a change signal, so we have to poll.
     173         [ +  + ]:          22 :         if (!m_disconnectTimer) {
     174         [ +  - ]:          19 :             m_disconnectTimer = new QTimer(this);
     175                 :          19 :             connect(m_disconnectTimer, &QTimer::timeout, this, [this]() {
     176         [ #  # ]:           0 :                 for (const auto &name : std::as_const(m_connections)) {
     177   [ #  #  #  # ]:           0 :                     if (!QDBusConnection(name).isConnected()) {
     178         [ #  # ]:           0 :                         disconnectDetected(u"bus died"_s);
     179                 :           0 :                         break;
     180                 :             :                     }
     181                 :             :                 }
     182                 :           0 :             });
     183                 :          19 :             m_disconnectTimer->start(500);
     184                 :             :         }
     185                 :             :     }
     186                 :             : 
     187                 :           0 :     void disconnectDetected(const QString &reason)
     188                 :             :     {
     189         [ #  # ]:           0 :         if (!m_disconnectedEmitted) {
     190         [ #  # ]:           0 :             emit disconnected(reason);
     191                 :           0 :             m_disconnectedEmitted = true;
     192         [ #  # ]:           0 :             if (m_disconnectTimer)
     193                 :           0 :                 m_disconnectTimer->stop();
     194                 :             :         }
     195                 :           0 :     }
     196                 :             : 
     197                 :             : public:
     198                 :          20 :     IoQtPackageManagerInterface *packager() const
     199                 :             :     {
     200                 :          20 :         return m_packager;
     201                 :             :     }
     202                 :             : 
     203                 :           9 :     IoQtApplicationManagerInterface *manager() const
     204                 :             :     {
     205                 :           9 :         return m_manager;
     206                 :             :     }
     207                 :             : 
     208                 :             : private:
     209                 :             :     IoQtPackageManagerInterface *m_packager = nullptr;
     210                 :             :     IoQtApplicationManagerInterface *m_manager = nullptr;
     211                 :             :     QVariantMap m_dbusAddresses;
     212                 :             :     QString m_dbusName;
     213                 :             :     QString m_dbusService;
     214                 :             :     QStringList m_connections;
     215                 :             :     QTimer *m_disconnectTimer = nullptr;
     216                 :             :     bool m_disconnectedEmitted = false;
     217                 :             : };
     218                 :             : 
     219                 :          19 : Q_GLOBAL_STATIC(DBus, dbus)
     220                 :             : 
     221                 :             : 
     222                 :             : enum Command {
     223                 :             :     NoCommand,
     224                 :             :     StartApplication,
     225                 :             :     DebugApplication,
     226                 :             :     StopApplication,
     227                 :             :     StopAllApplications,
     228                 :             :     ListApplications,
     229                 :             :     ShowApplication,
     230                 :             :     ListPackages,
     231                 :             :     ShowPackage,
     232                 :             :     InstallPackage,
     233                 :             :     RemovePackage,
     234                 :             :     ListInstallationTasks,
     235                 :             :     CancelInstallationTask,
     236                 :             :     ListInstallationLocations,
     237                 :             :     ShowInstallationLocation,
     238                 :             :     ListInstances,
     239                 :             :     InjectIntentRequest,
     240                 :             : };
     241                 :             : 
     242                 :             : // REMEMBER to update the completion file util/bash/appman-prompt, if you apply changes below!
     243                 :             : static struct {
     244                 :             :     Command command;
     245                 :             :     const char *name;
     246                 :             :     const char *description;
     247                 :             : } commandTable[] = {
     248                 :             :     { StartApplication, "start-application", "Start an application." },
     249                 :             :     { DebugApplication, "debug-application", "Debug an application." },
     250                 :             :     { StopApplication,  "stop-application",  "Stop an application." },
     251                 :             :     { StopAllApplications,  "stop-all-applications",  "Stop all applications." },
     252                 :             :     { ListApplications, "list-applications", "List all installed applications." },
     253                 :             :     { ShowApplication,  "show-application",  "Show application meta-data." },
     254                 :             :     { ListPackages,     "list-packages",     "List all installed packages." },
     255                 :             :     { ShowPackage,      "show-package",      "Show package meta-data." },
     256                 :             :     { InstallPackage,   "install-package",   "Install a package." },
     257                 :             :     { RemovePackage,    "remove-package",    "Remove a package." },
     258                 :             :     { ListInstallationTasks,     "list-installation-tasks",     "List all active installation tasks." },
     259                 :             :     { CancelInstallationTask,    "cancel-installation-task",    "Cancel an active installation task." },
     260                 :             :     { ListInstallationLocations, "list-installation-locations", "List all installaton locations." },
     261                 :             :     { ShowInstallationLocation,  "show-installation-location",  "Show details for installation location." },
     262                 :             :     { ListInstances,    "list-instances",    "List all named application manager instances." },
     263                 :             :     { InjectIntentRequest,       "inject-intent-request",       "Inject an intent request for testing." },
     264                 :             : };
     265                 :             : 
     266                 :          21 : static Command command(QCommandLineParser &clp)
     267                 :             : {
     268         [ +  + ]:          21 :     if (!clp.positionalArguments().isEmpty()) {
     269         [ +  - ]:          20 :         QByteArray cmd = clp.positionalArguments().at(0).toLatin1();
     270                 :             : 
     271         [ +  - ]:         188 :         for (uint i = 0; i < sizeof(commandTable) / sizeof(commandTable[0]); ++i) {
     272         [ +  + ]:         188 :             if (cmd == commandTable[i].name) {
     273         [ +  - ]:          20 :                 clp.clearPositionalArguments();
     274   [ +  -  +  - ]:          40 :                 clp.addPositionalArgument(QString::fromLatin1(cmd),
     275         [ +  - ]:          40 :                                           QString::fromLatin1(commandTable[i].description),
     276         [ +  - ]:          20 :                                           QString::fromLatin1(cmd));
     277                 :          20 :                 return commandTable[i].command;
     278                 :             :             }
     279                 :             :         }
     280                 :          20 :     }
     281                 :             :     return NoCommand;
     282                 :             : }
     283                 :             : 
     284                 :             : static std::pair<QString, QMultiHash<QString, int>> runningInstanceIds();
     285                 :             : static QVariantMap resolveInstanceInfo(const QString &instanceId);
     286                 :             : 
     287                 :             : static void startOrDebugApplication(const QString &debugWrapper, const QString &appId,
     288                 :             :                                     const QMap<QString, int> &stdRedirections, bool restart,
     289                 :             :                                     const QString &documentUrl) noexcept(false);
     290                 :             : static void stopApplication(const QString &appId, bool forceKill = false) noexcept(false);
     291                 :             : static void stopAllApplications() noexcept(false);
     292                 :             : static void listApplications() noexcept(false);
     293                 :             : static void showApplication(const QString &appId, bool asJson = false) noexcept(false);
     294                 :             : static void listPackages() noexcept(false);
     295                 :             : static void showPackage(const QString &packageId, bool asJson = false) noexcept(false);
     296                 :             : static void installPackage(const QString &packageUrl, bool acknowledge) noexcept(false);
     297                 :             : static void removePackage(const QString &packageId, bool keepDocuments, bool force) noexcept(false);
     298                 :             : static void listInstallationTasks() noexcept(false);
     299                 :             : static void cancelInstallationTask(bool all, const QString &singleTaskId) noexcept(false);
     300                 :             : static void listInstallationLocations() noexcept(false);
     301                 :             : static void showInstallationLocation(bool asJson = false) noexcept(false);
     302                 :             : static void listInstances() noexcept(false);
     303                 :             : static void injectIntentRequest(const QString &intentId, bool isBroadcast,
     304                 :             :                                 const QString &applicationId, const QString &requestingApplicationId,
     305                 :             :                                 const QString &jsonParameters) noexcept(false);
     306                 :             : 
     307                 :             : 
     308                 :           0 : class ThrowingApplication : public QCoreApplication // clazy:exclude=missing-qobject-macro
     309                 :             : {
     310                 :             : public:
     311                 :          21 :     ThrowingApplication(int &argc, char **argv)
     312                 :          42 :         : QCoreApplication(argc, argv)
     313                 :             :     { }
     314                 :             : 
     315                 :          21 :     Exception *exception() const
     316                 :             :     {
     317                 :          21 :         return m_exception;
     318                 :             :     }
     319                 :             : 
     320                 :          20 :     template <typename T> void runLater(T slot)
     321                 :             :     {
     322                 :             :         // run the specified function as soon as the event loop is up and running
     323                 :          20 :         QMetaObject::invokeMethod(this, slot, Qt::QueuedConnection);
     324                 :          20 :     }
     325                 :             : 
     326                 :             : protected:
     327                 :         146 :     bool notify(QObject *o, QEvent *e) override
     328                 :             :     {
     329                 :         146 :         try {
     330         [ +  + ]:         146 :             return QCoreApplication::notify(o, e);
     331         [ -  + ]:           1 :         } catch (const Exception &e) {
     332         [ +  - ]:           1 :             m_exception = new Exception(e);
     333         [ +  - ]:           1 :             exit(3);
     334                 :           1 :             return true;
     335                 :           1 :         }
     336                 :             :     }
     337                 :             : private:
     338                 :             :     Exception *m_exception = nullptr;
     339                 :             : };
     340                 :             : 
     341                 :          21 : int main(int argc, char *argv[])
     342                 :             : {
     343         [ +  - ]:          21 :     QCoreApplication::setApplicationName(u"Qt Application Manager Controller"_s);
     344         [ +  - ]:          21 :     QCoreApplication::setOrganizationName(u"QtProject"_s);
     345         [ +  - ]:          21 :     QCoreApplication::setOrganizationDomain(u"qt-project.org"_s);
     346         [ +  - ]:          21 :     QCoreApplication::setApplicationVersion(QString::fromLatin1(QT_AM_VERSION_STR));
     347                 :             : 
     348                 :          21 :     ensureLibDBusIsAvailable(); // this needs to happen before the QCoreApplication constructor
     349                 :             : 
     350                 :          21 :     ThrowingApplication a(argc, argv);
     351                 :             : 
     352         [ +  - ]:          21 :     QByteArray desc = "\n\nAvailable commands are:\n";
     353                 :          21 :     size_t longestName = 0;
     354         [ +  + ]:         357 :     for (uint i = 0; i < sizeof(commandTable) / sizeof(commandTable[0]); ++i)
     355   [ +  -  +  + ]:         903 :         longestName = qMax(longestName, qstrlen(commandTable[i].name));
     356         [ +  + ]:         357 :     for (uint i = 0; i < sizeof(commandTable) / sizeof(commandTable[0]); ++i) {
     357         [ +  - ]:         336 :         desc += "  ";
     358         [ +  - ]:         336 :         desc += commandTable[i].name;
     359   [ +  -  +  - ]:        1008 :         desc += QByteArray(1 + int(longestName - qstrlen(commandTable[i].name)), ' ');
     360         [ +  - ]:         336 :         desc += commandTable[i].description;
     361         [ +  - ]:         336 :         desc += '\n';
     362                 :             :     }
     363                 :             : 
     364         [ +  - ]:          21 :     desc += "\nMore information about each command can be obtained by running\n" \
     365                 :          21 :             " appman-controller <command> --help";
     366                 :             : 
     367         [ +  - ]:          21 :     QCommandLineParser clp;
     368                 :             : 
     369   [ +  -  +  - ]:          21 :     clp.addOption({ { u"instance-id"_s }, u"Connect to the named instance."_s, u"instance-id"_s });
     370         [ +  - ]:          21 :     clp.addHelpOption();
     371         [ +  - ]:          21 :     clp.addVersionOption();
     372                 :             : 
     373         [ +  - ]:          42 :     clp.addPositionalArgument(u"command"_s, u"The command to execute."_s);
     374                 :             : 
     375                 :             :     // ignore unknown options for now -- the sub-commands may need them later
     376         [ +  - ]:          21 :     clp.setOptionsAfterPositionalArgumentsMode(QCommandLineParser::ParseAsPositionalArguments);
     377                 :             : 
     378                 :             :     // ignore the return value here, as we also accept options we don't know about yet.
     379                 :             :     // If an option is really not accepted by a command, the command specific parsing should report
     380                 :             :     // this.
     381         [ +  - ]:          21 :     clp.setOptionsAfterPositionalArgumentsMode(QCommandLineParser::ParseAsOptions);
     382   [ +  -  +  - ]:          21 :     clp.parse(QCoreApplication::arguments());
     383                 :             : 
     384                 :             : 
     385                 :             :     // REMEMBER to update the completion file util/bash/appman-prompt, if you apply changes below!
     386                 :          21 :     try {
     387         [ +  - ]:          21 :         auto cmd = command(clp);
     388         [ +  + ]:          21 :         if ((cmd != NoCommand) && (cmd != ListInstances))
     389   [ +  -  +  -  :          38 :             dbus()->setInstanceInfo(resolveInstanceInfo(clp.value(u"instance-id"_s)));
             +  -  +  - ]
     390                 :             : 
     391   [ +  +  +  +  :          21 :         switch (cmd) {
          +  +  +  +  +  
          +  +  +  +  +  
             +  +  +  - ]
     392                 :           1 :         case NoCommand:
     393   [ +  -  -  + ]:           1 :             if (clp.isSet(u"version"_s))
     394                 :           0 :                 clp.showVersion();
     395                 :             : 
     396   [ +  -  +  -  :           3 :             clp.setApplicationDescription(u"\n"_s + QCoreApplication::applicationName() + QString::fromLatin1(desc));
          +  -  +  -  +  
                      - ]
     397   [ +  -  -  + ]:           1 :             if (clp.isSet(u"help"_s))
     398                 :           0 :                 clp.showHelp(0);
     399                 :           1 :             clp.showHelp(1);
     400                 :           1 :             break;
     401                 :             : 
     402                 :           1 :         case StartApplication: {
     403   [ +  -  +  -  :           3 :             clp.addOption({ { u"i"_s, u"attach-stdin"_s }, u"Attach the app's stdin to the controller's stdin"_s });
             +  +  -  - ]
     404   [ +  -  +  -  :           3 :             clp.addOption({ { u"o"_s, u"attach-stdout"_s }, u"Attach the app's stdout to the controller's stdout"_s });
             +  +  -  - ]
     405   [ +  -  +  -  :           3 :             clp.addOption({ { u"e"_s, u"attach-stderr"_s }, u"Attach the app's stderr to the controller's stderr"_s });
             +  +  -  - ]
     406   [ +  -  +  -  :           3 :             clp.addOption({ { u"r"_s, u"restart"_s }, u"Before starting, stop the application if it is already running"_s });
             +  +  -  - ]
     407         [ +  - ]:           2 :             clp.addPositionalArgument(u"application-id"_s, u"The id of an installed application."_s);
     408         [ +  - ]:           2 :             clp.addPositionalArgument(u"document-url"_s,   u"The optional document-url."_s, u"[document-url]"_s);
     409         [ +  - ]:           1 :             clp.process(a);
     410                 :             : 
     411         [ +  - ]:           1 :             int args = int(clp.positionalArguments().size());
     412         [ -  + ]:           1 :             if ((args < 2) || (args > 3))
     413                 :           0 :                 clp.showHelp(1);
     414                 :             : 
     415                 :           1 :             QMap<QString, int> stdRedirections;
     416   [ +  -  -  + ]:           1 :             if (clp.isSet(u"attach-stdin"_s))
     417         [ #  # ]:           0 :                 stdRedirections[u"in"_s] = 0;
     418   [ +  -  -  + ]:           1 :             if (clp.isSet(u"attach-stdout"_s))
     419         [ #  # ]:           0 :                 stdRedirections[u"out"_s] = 1;
     420   [ +  -  -  + ]:           1 :             if (clp.isSet(u"attach-stderr"_s))
     421         [ #  # ]:           0 :                 stdRedirections[u"err"_s] = 2;
     422         [ +  - ]:           1 :             bool restart = clp.isSet(u"restart"_s);
     423                 :             : 
     424         [ +  - ]:           3 :             a.runLater(std::bind(startOrDebugApplication,
     425                 :           2 :                                  QString(),
     426                 :           2 :                                  clp.positionalArguments().at(1),
     427                 :             :                                  stdRedirections,
     428                 :             :                                  restart,
     429   [ -  +  -  -  :           3 :                                  args == 3 ? clp.positionalArguments().at(2) : QString()));
          +  -  -  +  -  
                      - ]
     430                 :           1 :             break;
     431                 :           1 :         }
     432                 :           1 :         case DebugApplication: {
     433   [ +  -  +  -  :           3 :             clp.addOption({ { u"i"_s, u"attach-stdin"_s }, u"Attach the app's stdin to the controller's stdin"_s });
             +  +  -  - ]
     434   [ +  -  +  -  :           3 :             clp.addOption({ { u"o"_s, u"attach-stdout"_s }, u"Attach the app's stdout to the controller's stdout"_s });
             +  +  -  - ]
     435   [ +  -  +  -  :           3 :             clp.addOption({ { u"e"_s, u"attach-stderr"_s }, u"Attach the app's stderr to the controller's stderr"_s });
             +  +  -  - ]
     436   [ +  -  +  -  :           3 :             clp.addOption({ { u"r"_s, u"restart"_s }, u"Before starting, stop the application if it is already running"_s });
             +  +  -  - ]
     437         [ +  - ]:           2 :             clp.addPositionalArgument(u"debug-wrapper"_s,  u"The debug-wrapper specification."_s);
     438         [ +  - ]:           2 :             clp.addPositionalArgument(u"application-id"_s, u"The id of an installed application."_s);
     439         [ +  - ]:           2 :             clp.addPositionalArgument(u"document-url"_s,   u"The optional document-url."_s, u"[document-url]"_s);
     440         [ +  - ]:           1 :             clp.process(a);
     441                 :             : 
     442         [ +  - ]:           1 :             int args = int(clp.positionalArguments().size());
     443         [ -  + ]:           1 :             if ((args < 3) || (args > 4))
     444                 :           0 :                 clp.showHelp(1);
     445                 :             : 
     446                 :           1 :             QMap<QString, int> stdRedirections;
     447   [ +  -  -  + ]:           1 :             if (clp.isSet(u"attach-stdin"_s))
     448         [ #  # ]:           0 :                 stdRedirections[u"in"_s] = 0;
     449   [ +  -  -  + ]:           1 :             if (clp.isSet(u"attach-stdout"_s))
     450         [ #  # ]:           0 :                 stdRedirections[u"out"_s] = 1;
     451   [ +  -  -  + ]:           1 :             if (clp.isSet(u"attach-stderr"_s))
     452         [ #  # ]:           0 :                 stdRedirections[u"err"_s] = 2;
     453         [ +  - ]:           1 :             bool restart = clp.isSet(u"restart"_s);
     454                 :             : 
     455         [ +  - ]:           3 :             a.runLater(std::bind(startOrDebugApplication,
     456                 :           2 :                                  clp.positionalArguments().at(1),
     457         [ +  - ]:           2 :                                  clp.positionalArguments().at(2),
     458                 :             :                                  stdRedirections,
     459                 :             :                                  restart,
     460   [ +  -  +  -  :           3 :                                  args == 3 ? clp.positionalArguments().at(2) : QString()));
          +  -  +  -  -  
                      - ]
     461                 :           1 :             break;
     462                 :           1 :         }
     463                 :           1 :         case StopAllApplications:
     464         [ +  - ]:           1 :             clp.process(a);
     465   [ +  -  -  + ]:           1 :             if (clp.positionalArguments().size() != 1)
     466                 :           0 :                 clp.showHelp(1);
     467                 :             : 
     468         [ +  - ]:           2 :             a.runLater(stopAllApplications);
     469                 :             :             break;
     470                 :             : 
     471                 :           1 :         case StopApplication:
     472   [ +  -  +  -  :           3 :             clp.addOption({ { u"f"_s, u"force"_s }, u"Force kill the application."_s });
             +  +  -  - ]
     473         [ +  - ]:           2 :             clp.addPositionalArgument(u"application-id"_s, u"The id of an installed application."_s);
     474         [ +  - ]:           1 :             clp.process(a);
     475                 :             : 
     476   [ +  -  -  + ]:           1 :             if (clp.positionalArguments().size() != 2)
     477                 :           0 :                 clp.showHelp(1);
     478                 :             : 
     479         [ +  - ]:           3 :             a.runLater(std::bind(stopApplication,
     480         [ +  - ]:           1 :                                  clp.positionalArguments().at(1),
     481         [ +  - ]:           2 :                                  clp.isSet(u"f"_s)));
     482                 :           1 :             break;
     483                 :             : 
     484                 :           1 :         case ListApplications:
     485         [ +  - ]:           1 :             clp.process(a);
     486         [ +  - ]:           2 :             a.runLater(listApplications);
     487                 :             :             break;
     488                 :             : 
     489                 :           1 :         case ShowApplication:
     490   [ +  -  +  - ]:           1 :             clp.addOption({ u"json"_s, u"Output in JSON format instead of YAML."_s });
     491         [ +  - ]:           2 :             clp.addPositionalArgument(u"application-id"_s, u"The id of an installed application."_s);
     492         [ +  - ]:           1 :             clp.process(a);
     493                 :             : 
     494   [ +  -  -  + ]:           1 :             if (clp.positionalArguments().size() != 2)
     495                 :           0 :                 clp.showHelp(1);
     496                 :             : 
     497         [ +  - ]:           3 :             a.runLater(std::bind(showApplication,
     498         [ +  - ]:           1 :                                  clp.positionalArguments().at(1),
     499         [ +  - ]:           2 :                                  clp.isSet(u"json"_s)));
     500                 :           1 :             break;
     501                 :             : 
     502                 :           1 :         case ListPackages:
     503         [ +  - ]:           1 :             clp.process(a);
     504         [ +  - ]:           2 :             a.runLater(listPackages);
     505                 :             :             break;
     506                 :             : 
     507                 :           1 :         case ShowPackage:
     508   [ +  -  +  - ]:           1 :             clp.addOption({ u"json"_s, u"Output in JSON format instead of YAML."_s });
     509         [ +  - ]:           2 :             clp.addPositionalArgument(u"package-id"_s, u"The id of an installed package."_s);
     510         [ +  - ]:           1 :             clp.process(a);
     511                 :             : 
     512   [ +  -  -  + ]:           1 :             if (clp.positionalArguments().size() != 2)
     513                 :           0 :                 clp.showHelp(1);
     514                 :             : 
     515         [ +  - ]:           3 :             a.runLater(std::bind(showPackage,
     516         [ +  - ]:           1 :                                  clp.positionalArguments().at(1),
     517         [ +  - ]:           2 :                                  clp.isSet(u"json"_s)));
     518                 :           1 :             break;
     519                 :             : 
     520                 :           2 :         case InstallPackage:
     521   [ +  -  +  -  :           6 :             clp.addOption({ { u"l"_s, u"location"_s }, u"Set a custom installation location (deprecated and ignored)."_s, u"installation-location"_s, u"internal-0"_s });
             +  +  -  - ]
     522   [ +  -  +  -  :           6 :             clp.addOption({ { u"a"_s, u"acknowledge"_s }, u"Automatically acknowledge the installation (unattended mode)."_s });
             +  +  -  - ]
     523         [ +  - ]:           4 :             clp.addPositionalArgument(u"package"_s, u"The file name of the package; can be - for stdin."_s);
     524         [ +  - ]:           2 :             clp.process(a);
     525                 :             : 
     526   [ +  -  -  + ]:           2 :             if (clp.positionalArguments().size() != 2)
     527                 :           0 :                 clp.showHelp(1);
     528   [ +  -  -  + ]:           2 :             if (clp.isSet(u"l"_s))
     529         [ #  # ]:           0 :                 fprintf(stderr, "Ignoring the deprecated -l option.\n");
     530                 :             : 
     531         [ +  - ]:           6 :             a.runLater(std::bind(installPackage,
     532         [ +  - ]:           2 :                                  clp.positionalArguments().at(1),
     533         [ +  - ]:           4 :                                  clp.isSet(u"a"_s)));
     534                 :           2 :             break;
     535                 :             : 
     536                 :           1 :         case RemovePackage:
     537   [ +  -  +  -  :           3 :             clp.addOption({ { u"f"_s, u"force"_s }, u"Force removal of package."_s });
             +  +  -  - ]
     538   [ +  -  +  -  :           3 :             clp.addOption({ { u"k"_s, u"keep-documents"_s }, u"Keep the document folder of the application."_s });
             +  +  -  - ]
     539         [ +  - ]:           2 :             clp.addPositionalArgument(u"package-id"_s, u"The id of an installed package."_s);
     540         [ +  - ]:           1 :             clp.process(a);
     541                 :             : 
     542   [ +  -  -  + ]:           1 :             if (clp.positionalArguments().size() != 2)
     543                 :           0 :                 clp.showHelp(1);
     544                 :             : 
     545         [ +  - ]:           3 :             a.runLater(std::bind(removePackage,
     546         [ +  - ]:           1 :                                  clp.positionalArguments().at(1),
     547         [ +  - ]:           2 :                                  clp.isSet(u"k"_s),
     548         [ +  - ]:           2 :                                  clp.isSet(u"f"_s)));
     549                 :           1 :             break;
     550                 :             : 
     551                 :           2 :         case ListInstallationTasks:
     552         [ +  - ]:           2 :             clp.process(a);
     553         [ +  - ]:           4 :             a.runLater(listInstallationTasks);
     554                 :             :             break;
     555                 :             : 
     556                 :           1 :         case CancelInstallationTask: {
     557         [ +  - ]:           2 :             clp.addPositionalArgument(u"task-id"_s, u"The id of an active installation task."_s);
     558   [ +  -  +  -  :           3 :             clp.addOption({ { u"a"_s, u"all"_s }, u"Cancel all active installation tasks."_s });
             +  +  -  - ]
     559         [ +  - ]:           1 :             clp.process(a);
     560                 :             : 
     561         [ +  - ]:           1 :             qsizetype args = clp.positionalArguments().size();
     562         [ +  - ]:           1 :             bool all = clp.isSet(u"a"_s);
     563   [ -  +  -  -  :           1 :             if (!(((args == 1) && all) || ((args == 2) && !all)))
             +  -  -  + ]
     564                 :           0 :                 clp.showHelp(1);
     565                 :             : 
     566         [ +  - ]:           2 :             a.runLater(std::bind(cancelInstallationTask,
     567                 :             :                                  all,
     568   [ +  -  +  -  :           3 :                                  args == 2 ? clp.positionalArguments().at(1) : QString()));
             +  -  -  - ]
     569                 :           1 :             break;
     570                 :             :         }
     571                 :           1 :         case ListInstallationLocations:
     572         [ +  - ]:           1 :             clp.process(a);
     573         [ +  - ]:           2 :             a.runLater(listInstallationLocations);
     574                 :             :             break;
     575                 :             : 
     576                 :           1 :         case ShowInstallationLocation:
     577         [ +  - ]:           2 :             clp.addPositionalArgument(u"installation-location"_s, u"The id of an installation location (deprecated and ignored)."_s);
     578   [ +  -  +  - ]:           1 :             clp.addOption({ u"json"_s, u"Output in JSON format instead of YAML."_s });
     579         [ +  - ]:           1 :             clp.process(a);
     580                 :             : 
     581   [ +  -  -  + ]:           1 :             if (clp.positionalArguments().size() > 2)
     582                 :           0 :                 clp.showHelp(1);
     583   [ +  -  +  - ]:           1 :             if (clp.positionalArguments().size() == 2)
     584         [ +  - ]:           1 :                 fprintf(stderr, "Ignoring the deprecated installation-location.\n");
     585                 :             : 
     586         [ +  - ]:           2 :             a.runLater(std::bind(showInstallationLocation,
     587   [ +  -  +  - ]:           2 :                                  clp.isSet(u"json"_s)));
     588                 :           1 :             break;
     589                 :             : 
     590                 :           1 :         case ListInstances:
     591         [ +  - ]:           1 :             clp.process(a);
     592         [ +  - ]:           2 :             a.runLater(listInstances);
     593                 :             :             break;
     594                 :             : 
     595                 :           3 :         case InjectIntentRequest:
     596         [ +  - ]:           6 :             clp.addPositionalArgument(u"intent-id"_s, u"The id of the intent."_s);
     597         [ +  - ]:           6 :             clp.addPositionalArgument(u"parameters"_s, u"The optional parameters for this request."_s, u"[json-parameters]"_s);
     598   [ +  -  +  - ]:           3 :             clp.addOption({ u"requesting-application-id"_s, u"Fake the requesting application id."_s, u"id"_s, u":sysui:"_s });
     599   [ +  -  +  - ]:           3 :             clp.addOption({ u"application-id"_s, u"Specify the handling application id."_s, u"id"_s });
     600   [ +  -  +  - ]:           3 :             clp.addOption({ u"broadcast"_s, u"Create a broadcast request."_s });
     601         [ +  - ]:           3 :             clp.process(a);
     602                 :             : 
     603         [ +  - ]:           3 :             bool isBroadcast = clp.isSet(u"broadcast"_s);
     604         [ +  - ]:          23 :             QString appId = clp.value(u"application-id"_s);
     605         [ +  - ]:           6 :             QString requestingAppId = clp.value(u"requesting-application-id"_s);
     606                 :             : 
     607   [ +  +  -  + ]:           3 :             if (!appId.isEmpty() && isBroadcast)
     608                 :           0 :                 throw Exception("You cannot use --application-id and --broadcast at the same time.");
     609                 :             : 
     610   [ +  -  -  + ]:           3 :             if (clp.positionalArguments().size() > 3)
     611                 :           0 :                 clp.showHelp(1);
     612                 :             : 
     613                 :           3 :             QString jsonParams;
     614   [ +  -  +  - ]:           3 :             if (clp.positionalArguments().size() == 3)
     615         [ +  - ]:           3 :                 jsonParams = clp.positionalArguments().at(2);
     616                 :             : 
     617   [ +  -  +  - ]:          12 :             a.runLater(std::bind(injectIntentRequest, clp.positionalArguments().at(1),
     618                 :             :                                  isBroadcast, requestingAppId, appId, jsonParams));
     619                 :           3 :             break;
     620                 :             :         }
     621                 :             : 
     622         [ +  - ]:          20 :         int result = a.exec();
     623         [ +  + ]:          20 :         if (a.exception())
     624                 :           1 :             throw *a.exception();
     625                 :             :         return result;
     626                 :             : 
     627         [ -  + ]:           1 :     } catch (const Exception &e) {
     628   [ +  -  +  - ]:           3 :         fprintf(stderr, "ERROR: %s\n", qPrintable(e.errorString()));
     629                 :           1 :         return int(e.errorCode());
     630                 :           1 :     }
     631                 :          20 : }
     632                 :             : 
     633                 :           2 : void startOrDebugApplication(const QString &debugWrapper, const QString &appId,
     634                 :             :                              const QMap<QString, int> &stdRedirections, bool restart,
     635                 :             :                              const QString &documentUrl = QString()) noexcept(false)
     636                 :             : {
     637                 :           2 :     dbus()->connectToManager();
     638                 :             : 
     639         [ -  + ]:           2 :     if (restart) {
     640                 :             :         bool isStopped = false;
     641                 :             : 
     642                 :             :         // pass 0: normal stop / pass 1: force kill
     643         [ #  # ]:           0 :         for (int pass = 0; !isStopped && (pass < 2); ++pass) {
     644                 :           0 :             stopApplication(appId, pass == 0 ? false : true);
     645                 :             : 
     646                 :             :             static const int checksPerSecond = 10;
     647                 :             : 
     648                 :             :             // check if application has quit for max. 3sec
     649         [ #  # ]:           0 :             for (int i = 0; !isStopped && (i < (3 * checksPerSecond)); ++i) {
     650                 :           0 :                 auto stateReply = dbus()->manager()->applicationRunState(appId);
     651         [ #  # ]:           0 :                 stateReply.waitForFinished();
     652   [ #  #  #  # ]:           0 :                 if (stateReply.isError())
     653   [ #  #  #  # ]:           0 :                     throw Exception(Error::IO, "failed to get the current run-state from application manager: %1").arg(stateReply.error().message());
     654                 :             : 
     655   [ #  #  #  # ]:           0 :                 if (stateReply.value() == 0 /* NotRunning */)
     656                 :             :                     isStopped = true;
     657                 :             :                 else
     658   [ #  #  #  # ]:           0 :                     QThread::currentThread()->msleep(1000 / checksPerSecond);
     659                 :           0 :             }
     660                 :             :         }
     661                 :             : 
     662         [ #  # ]:           0 :         if (!isStopped)
     663                 :           0 :             throw Exception("failed to stop application %1 before restarting it").arg(appId);
     664                 :             :     }
     665                 :             : 
     666                 :             :     // the async lambda below needs to share this variable
     667                 :           2 :     static bool isStarted = false;
     668                 :             : 
     669   [ -  +  -  - ]:           2 :     if (!stdRedirections.isEmpty()) {
     670                 :             :         // just bail out, if the AM or bus dies
     671                 :           0 :         QObject::connect(dbus(), &DBus::disconnected,
     672                 :           0 :                          qApp, [](const QString &reason) {
     673                 :           0 :             throw Exception(Error::IO, "application might not be running: lost connection to the D-Bus service (%1)").arg(reason);
     674                 :             :         });
     675                 :             : 
     676                 :             :         // in case application quits -> quit the controller
     677   [ #  #  #  # ]:           0 :         QObject::connect(dbus()->manager(), &IoQtApplicationManagerInterface::applicationRunStateChanged,
     678                 :           0 :                          qApp, [appId](const QString &id, uint runState) {
     679   [ #  #  #  #  :           0 :             if (isStarted && id == appId && runState == 0 /* NotRunning */) {
                   #  # ]
     680                 :           0 :                 auto getReply = dbus()->manager()->get(id);
     681         [ #  # ]:           0 :                 getReply.waitForFinished();
     682   [ #  #  #  # ]:           0 :                 if (getReply.isError())
     683   [ #  #  #  # ]:           0 :                     throw Exception(Error::IO, "failed to get exit code from application manager: %1").arg(getReply.error().message());
     684         [ #  # ]:           0 :                 fprintf(stdout, "\n --- application has quit ---\n\n");
     685         [ #  # ]:           0 :                 auto app = getReply.value();
     686   [ #  #  #  #  :           0 :                 qApp->exit(app.value(u"lastExitCode"_s, 1).toInt());
                   #  # ]
     687                 :           0 :             }
     688                 :           0 :         });
     689                 :             :     }
     690                 :             : 
     691         [ -  + ]:           2 :     bool isDebug = !debugWrapper.isEmpty();
     692         [ -  + ]:           2 :     bool hasRedirections = !stdRedirections.isEmpty();
     693                 :             : #if defined(Q_OS_WINDOWS)
     694                 :             :     if (hasRedirections) {
     695                 :             :         fprintf(stderr, "WARNING: Ignoring std-in/out/err redirections, as these are not supported on Windows.");
     696                 :             :         hasRedirections = false;
     697                 :             :     }
     698                 :             : #endif
     699                 :           2 :     QDBusPendingReply<bool> reply;
     700                 :             : 
     701         [ +  - ]:           2 :     if (!hasRedirections) {
     702   [ +  +  +  -  :           4 :         reply = isDebug ? dbus()->manager()->debugApplication(appId, debugWrapper, documentUrl)
          +  -  +  -  +  
                      - ]
     703                 :           2 :                         : dbus()->manager()->startApplication(appId, documentUrl);
     704                 :             :     } else {
     705                 :           0 :         UnixFdMap fdMap;
     706   [ #  #  #  #  :           0 :         for (auto it = stdRedirections.cbegin(); it != stdRedirections.cend(); ++it)
                   #  # ]
     707   [ #  #  #  # ]:           0 :             fdMap.insert(it.key(), QDBusUnixFileDescriptor(it.value()));
     708                 :             : 
     709   [ #  #  #  #  :           0 :         reply = isDebug ? dbus()->manager()->debugApplication(appId, debugWrapper, fdMap, documentUrl)
          #  #  #  #  #  
                      # ]
     710                 :           0 :                         : dbus()->manager()->startApplication(appId, fdMap, documentUrl);
     711                 :           0 :     }
     712                 :             : 
     713         [ +  - ]:           2 :     reply.waitForFinished();
     714   [ +  -  -  + ]:           2 :     if (reply.isError()) {
     715                 :           0 :         throw Exception(Error::IO, "failed to call %2Application via DBus: %1")
     716   [ #  #  #  #  :           0 :                 .arg(reply.error().message()).arg(isDebug ? "debug" : "start");
                   #  # ]
     717                 :             :     }
     718                 :             : 
     719         [ +  - ]:           2 :     isStarted = reply.value();
     720   [ -  +  -  -  :           2 :     if (stdRedirections.isEmpty() || !isStarted) {
                   -  - ]
     721   [ -  +  +  - ]:           2 :         qApp->exit(isStarted ? 0 : 2);
     722                 :             :     } else {
     723   [ #  #  #  # ]:           0 :         InterruptHandler::install([appId](int) {
     724                 :           0 :             auto stopReply = dbus()->manager()->stopApplication(appId, true);
     725         [ #  # ]:           0 :             stopReply.waitForFinished();
     726         [ #  # ]:           0 :             qApp->exit(1);
     727                 :           0 :         });
     728                 :             :     }
     729                 :           2 : }
     730                 :             : 
     731                 :           1 : void stopApplication(const QString &appId, bool forceKill) noexcept(false)
     732                 :             : {
     733                 :           1 :     dbus()->connectToManager();
     734                 :             : 
     735                 :           1 :     auto reply = dbus()->manager()->stopApplication(appId, forceKill);
     736         [ +  - ]:           1 :     reply.waitForFinished();
     737   [ +  -  -  + ]:           1 :     if (reply.isError())
     738   [ #  #  #  # ]:           0 :         throw Exception(Error::IO, "failed to call stopApplication via DBus: %1").arg(reply.error().message());
     739         [ +  - ]:           1 :     qApp->quit();
     740                 :           1 : }
     741                 :             : 
     742                 :           1 : void stopAllApplications() noexcept(false)
     743                 :             : {
     744                 :           1 :     dbus()->connectToManager();
     745                 :             : 
     746                 :           1 :     auto reply = dbus()->manager()->stopAllApplications();
     747         [ +  - ]:           1 :     reply.waitForFinished();
     748   [ +  -  -  + ]:           1 :     if (reply.isError())
     749   [ #  #  #  # ]:           0 :         throw Exception(Error::IO, "failed to call stopAllApplications via DBus: %1").arg(reply.error().message());
     750         [ +  - ]:           1 :     qApp->quit();
     751                 :           1 : }
     752                 :             : 
     753                 :           1 : void listApplications() noexcept(false)
     754                 :             : {
     755                 :           1 :     dbus()->connectToManager();
     756                 :             : 
     757                 :           1 :     auto reply = dbus()->manager()->applicationIds();
     758         [ +  - ]:           1 :     reply.waitForFinished();
     759   [ +  -  -  + ]:           1 :     if (reply.isError())
     760   [ #  #  #  # ]:           0 :         throw Exception(Error::IO, "failed to call applicationIds via DBus: %1").arg(reply.error().message());
     761                 :             : 
     762         [ +  - ]:           1 :     const auto applicationIds = reply.value();
     763         [ +  + ]:           3 :     for (const auto &applicationId : applicationIds)
     764   [ +  -  +  - ]:           6 :         fprintf(stdout, "%s\n", qPrintable(applicationId));
     765         [ +  - ]:           1 :     qApp->quit();
     766                 :           1 : }
     767                 :             : 
     768                 :           1 : void showApplication(const QString &appId, bool asJson) noexcept(false)
     769                 :             : {
     770                 :           1 :     dbus()->connectToManager();
     771                 :             : 
     772                 :           1 :     auto reply = dbus()->manager()->get(appId);
     773         [ +  - ]:           1 :     reply.waitForFinished();
     774   [ +  -  -  + ]:           1 :     if (reply.isError())
     775   [ #  #  #  # ]:           0 :         throw Exception(Error::IO, "failed to get application via DBus: %1").arg(reply.error().message());
     776                 :             : 
     777   [ +  -  +  - ]:           2 :     QVariant app = convertFromDBusVariant(reply.value());
     778   [ -  +  -  -  :           3 :     fprintf(stdout, "%s\n", asJson ? QJsonDocument::fromVariant(app).toJson().constData()
          -  -  -  -  +  
             -  -  +  -  
                      - ]
     779   [ +  -  +  -  :           7 :                                    : QtYaml::yamlFromVariantDocuments({ app }).constData());
          +  -  +  -  +  
          -  +  -  +  -  
          +  +  -  +  -  
          -  -  -  -  -  
             -  -  -  - ]
     780         [ +  - ]:           1 :     qApp->quit();
     781                 :           1 : }
     782                 :             : 
     783                 :           1 : void listPackages() noexcept(false)
     784                 :             : {
     785                 :           1 :     dbus()->connectToPackager();
     786                 :             : 
     787                 :           1 :     auto reply = dbus()->packager()->packageIds();
     788         [ +  - ]:           1 :     reply.waitForFinished();
     789   [ +  -  -  + ]:           1 :     if (reply.isError())
     790   [ #  #  #  # ]:           0 :         throw Exception(Error::IO, "failed to call packageIds via DBus: %1").arg(reply.error().message());
     791                 :             : 
     792         [ +  - ]:           1 :     const auto packageIds = reply.value();
     793         [ +  + ]:           2 :     for (const auto &packageId : packageIds)
     794   [ +  -  +  - ]:           3 :         fprintf(stdout, "%s\n", qPrintable(packageId));
     795         [ +  - ]:           1 :     qApp->quit();
     796                 :           1 : }
     797                 :             : 
     798                 :           1 : void showPackage(const QString &packageId, bool asJson) noexcept(false)
     799                 :             : {
     800                 :           1 :     dbus()->connectToPackager();
     801                 :             : 
     802                 :           1 :     auto reply = dbus()->packager()->get(packageId);
     803         [ +  - ]:           1 :     reply.waitForFinished();
     804   [ +  -  -  + ]:           1 :     if (reply.isError())
     805   [ #  #  #  # ]:           0 :         throw Exception(Error::IO, "failed to get package via DBus: %1").arg(reply.error().message());
     806                 :             : 
     807   [ +  -  +  - ]:           2 :     QVariant package = convertFromDBusVariant(reply.value());
     808   [ -  +  -  -  :           3 :     fprintf(stdout, "%s\n", asJson ? QJsonDocument::fromVariant(package).toJson().constData()
          -  -  -  -  +  
             -  -  +  -  
                      - ]
     809   [ +  -  +  -  :           7 :                                    : QtYaml::yamlFromVariantDocuments({ package }).constData());
          +  -  +  -  +  
          -  +  -  +  -  
          +  +  -  +  -  
          -  -  -  -  -  
             -  -  -  - ]
     810         [ +  - ]:           1 :     qApp->quit();
     811                 :           1 : }
     812                 :             : 
     813                 :           2 : void installPackage(const QString &package, bool acknowledge) noexcept(false)
     814                 :             : {
     815                 :           2 :     QString packageFile = package;
     816                 :             : 
     817         [ -  + ]:           2 :     if (package == u"-") { // sent via stdin
     818                 :           0 :         bool success = false;
     819                 :             : 
     820   [ #  #  #  # ]:           0 :         QTemporaryFile *tf = new QTemporaryFile(qApp);
     821         [ #  # ]:           0 :         QFile in;
     822                 :             : 
     823   [ #  #  #  #  :           0 :         if (tf->open() && in.open(stdin, QIODevice::ReadOnly)) {
             #  #  #  # ]
     824         [ #  # ]:           0 :             packageFile = tf->fileName();
     825                 :             : 
     826   [ #  #  #  #  :           0 :             while (!in.atEnd() && !tf->error())
             #  #  #  # ]
     827   [ #  #  #  # ]:           0 :                 tf->write(in.read(1024 * 1024 * 8));
     828                 :             : 
     829   [ #  #  #  #  :           0 :             success = in.atEnd() && !tf->error();
             #  #  #  # ]
     830         [ #  # ]:           0 :             tf->flush();
     831                 :             :         }
     832                 :             : 
     833         [ #  # ]:           0 :         if (!success)
     834                 :           0 :             throw Exception(Error::IO, "Could not copy from stdin to temporary file %1").arg(package);
     835                 :           0 :     }
     836                 :             : 
     837         [ +  - ]:           2 :     QFileInfo fi(packageFile);
     838   [ +  -  +  -  :           2 :     if (!fi.exists() || !fi.isReadable() || !fi.isFile())
          +  -  +  -  +  
                -  +  - ]
     839                 :           0 :         throw Exception(Error::IO, "Package file is not readable: %1").arg(packageFile);
     840                 :             : 
     841   [ +  -  +  - ]:           6 :     fprintf(stdout, "Starting installation of package %s ...\n", qPrintable(packageFile));
     842                 :             : 
     843   [ +  -  +  - ]:           2 :     dbus()->connectToManager();
     844   [ +  -  +  - ]:           2 :     dbus()->connectToPackager();
     845                 :             : 
     846                 :             :     // just bail out, if the AM or bus dies
     847   [ +  -  +  - ]:           2 :     QObject::connect(dbus(), &DBus::disconnected,
     848                 :           0 :                      qApp, [](const QString &reason) {
     849                 :           0 :         throw Exception(Error::IO, "package might not be installed: lost connection to the D-Bus service (%1)").arg(reason);
     850                 :             :     });
     851                 :             : 
     852                 :             :     // all the async lambdas below need to share this variable
     853   [ +  -  +  - ]:           2 :     static QString installationId;
     854                 :             : 
     855                 :             :     // as soon as we have the manifest available: get the app id and acknowledge the installation
     856         [ +  + ]:           2 :     if (acknowledge) {
     857   [ +  -  +  - ]:           1 :         QObject::connect(dbus()->packager(), &IoQtPackageManagerInterface::taskRequestingInstallationAcknowledge,
     858                 :           1 :                          qApp, [](const QString &taskId, const QVariantMap &metadata) {
     859         [ -  + ]:           1 :             if (taskId != installationId)
     860                 :           0 :                 return;
     861   [ +  -  +  - ]:           2 :             QString packageId = metadata.value(u"packageId"_s).toString();
     862         [ -  + ]:           1 :             if (packageId.isEmpty())
     863                 :           0 :                 throw Exception(Error::IO, "could not find a valid package id in the package");
     864   [ +  -  +  - ]:           3 :             fprintf(stdout, "Acknowledging package installation for '%s'...\n", qPrintable(packageId));
     865   [ +  -  +  - ]:           2 :             dbus()->packager()->acknowledgePackageInstallation(taskId);
     866                 :           1 :         });
     867                 :             :     }
     868                 :             : 
     869                 :             :     // on failure: quit
     870   [ +  -  +  - ]:           2 :     QObject::connect(dbus()->packager(), &IoQtPackageManagerInterface::taskFailed,
     871                 :           1 :                      qApp, [](const QString &taskId, int errorCode, const QString &errorString) {
     872         [ -  + ]:           1 :         if (taskId != installationId)
     873                 :           0 :             return;
     874                 :           1 :         throw Exception(Error::IO, "failed to install package: %1 (code: %2)").arg(errorString).arg(errorCode);
     875                 :             :     });
     876                 :             : 
     877                 :             :     // on success
     878   [ +  -  +  - ]:           2 :     QObject::connect(dbus()->packager(), &IoQtPackageManagerInterface::taskFinished,
     879                 :           1 :                      qApp, [](const QString &taskId) {
     880         [ +  - ]:           1 :         if (taskId != installationId)
     881                 :             :             return;
     882                 :           1 :         fprintf(stdout, "Package installation finished successfully.\n");
     883                 :           1 :         qApp->quit();
     884                 :             :     });
     885                 :             : 
     886                 :             :     // start the package installation
     887                 :             : 
     888   [ +  -  +  -  :           2 :     auto reply = dbus()->packager()->startPackageInstallation(fi.absoluteFilePath());
                   +  - ]
     889         [ +  - ]:           2 :     reply.waitForFinished();
     890   [ +  -  -  + ]:           2 :     if (reply.isError())
     891   [ #  #  #  # ]:           0 :         throw Exception(Error::IO, "failed to call startPackageInstallation via DBus: %1").arg(reply.error().message());
     892                 :             : 
     893         [ +  - ]:           2 :     installationId = reply.value();
     894         [ -  + ]:           2 :     if (installationId.isEmpty())
     895                 :           0 :         throw Exception(Error::IO, "startPackageInstallation returned an empty taskId");
     896                 :             : 
     897                 :             :     // cancel the job on Ctrl+C
     898                 :             : 
     899         [ +  - ]:           4 :     InterruptHandler::install([](int) {
     900                 :           0 :         fprintf(stdout, "Cancelling package installation.\n");
     901                 :           0 :         auto cancelReply = dbus()->packager()->cancelTask(installationId);
     902         [ #  # ]:           0 :         cancelReply.waitForFinished();
     903         [ #  # ]:           0 :         qApp->exit(1);
     904                 :           0 :     });
     905                 :           2 : }
     906                 :             : 
     907                 :           1 : void removePackage(const QString &packageId, bool keepDocuments, bool force) noexcept(false)
     908                 :             : {
     909         [ +  - ]:           3 :     fprintf(stdout, "Starting removal of package %s...\n", qPrintable(packageId));
     910                 :             : 
     911                 :           1 :     dbus()->connectToManager();
     912                 :           1 :     dbus()->connectToPackager();
     913                 :             : 
     914                 :             :     // just bail out, if the AM or bus dies
     915                 :           1 :     QObject::connect(dbus(), &DBus::disconnected,
     916                 :           0 :                      qApp, [](const QString &reason) {
     917                 :           0 :         throw Exception(Error::IO, "package might not be removed: lost connection to the D-Bus service (%1)").arg(reason);
     918                 :             :     });
     919                 :             : 
     920                 :             :     // both the async lambdas below need to share this variables
     921   [ +  -  +  - ]:           1 :     static QString installationId;
     922                 :             : 
     923                 :             :     // on failure: quit
     924                 :           1 :     QObject::connect(dbus()->packager(), &IoQtPackageManagerInterface::taskFailed,
     925                 :           0 :                      qApp, [](const QString &taskId, int errorCode, const QString &errorString) {
     926         [ #  # ]:           0 :         if (taskId != installationId)
     927                 :           0 :             return;
     928                 :           0 :         throw Exception(Error::IO, "failed to remove package: %1 (code: %2)").arg(errorString).arg(errorCode);
     929                 :             :     });
     930                 :             : 
     931                 :             :     // on success
     932                 :           1 :     QObject::connect(dbus()->packager(), &IoQtPackageManagerInterface::taskFinished,
     933                 :           1 :                      qApp, [](const QString &taskId) {
     934         [ +  - ]:           1 :         if (taskId != installationId)
     935                 :             :             return;
     936                 :           1 :         fprintf(stdout, "Package removal finished successfully.\n");
     937                 :           1 :         qApp->quit();
     938                 :             :     });
     939                 :             : 
     940                 :             :     // start the package removal
     941                 :           1 :     auto reply = dbus()->packager()->removePackage(packageId, keepDocuments, force);
     942         [ +  - ]:           1 :     reply.waitForFinished();
     943   [ +  -  -  + ]:           1 :     if (reply.isError())
     944   [ #  #  #  # ]:           0 :         throw Exception(Error::IO, "failed to call removePackage via DBus: %1").arg(reply.error().message());
     945                 :             : 
     946         [ +  - ]:           1 :     installationId = reply.value();
     947         [ -  + ]:           1 :     if (installationId.isEmpty())
     948                 :           0 :         throw Exception(Error::IO, "removePackage returned an empty taskId");
     949                 :           1 : }
     950                 :             : 
     951                 :           2 : void listInstallationTasks() noexcept(false)
     952                 :             : {
     953                 :           2 :     dbus()->connectToPackager();
     954                 :             : 
     955                 :           2 :     auto reply = dbus()->packager()->activeTaskIds();
     956         [ +  - ]:           2 :     reply.waitForFinished();
     957   [ +  -  -  + ]:           2 :     if (reply.isError())
     958   [ #  #  #  # ]:           0 :         throw Exception(Error::IO, "failed to call activeTaskIds via DBus: %1").arg(reply.error().message());
     959                 :             : 
     960         [ +  - ]:           2 :     const auto taskIds = reply.value();
     961         [ +  + ]:           3 :     for (const auto &taskId : taskIds)
     962   [ +  -  +  - ]:           3 :         fprintf(stdout, "%s\n", qPrintable(taskId));
     963         [ +  - ]:           2 :     qApp->quit();
     964                 :           2 : }
     965                 :             : 
     966                 :             : 
     967                 :           1 : void cancelInstallationTask(bool all, const QString &singleTaskId) noexcept(false)
     968                 :             : {
     969                 :           1 :     dbus()->connectToPackager();
     970                 :             : 
     971                 :             :     // just bail out, if the AM or bus dies
     972                 :           1 :     QObject::connect(dbus(), &DBus::disconnected,
     973                 :           0 :                      qApp, [](const QString &reason) {
     974                 :           0 :         throw Exception(Error::IO, "installation task(s) might not be canceled: lost connection to the D-Bus service (%1)").arg(reason);
     975                 :             :     });
     976                 :             : 
     977                 :             :     // both the async lambdas below need to share these variables
     978   [ +  -  +  - ]:           1 :     static QStringList cancelTaskIds;
     979                 :           1 :     static int result = 0;
     980                 :             : 
     981         [ -  + ]:           1 :     if (all) {
     982                 :           0 :         dbus()->connectToPackager();
     983                 :             : 
     984                 :           0 :         auto reply = dbus()->packager()->activeTaskIds();
     985         [ #  # ]:           0 :         reply.waitForFinished();
     986   [ #  #  #  # ]:           0 :         if (reply.isError())
     987   [ #  #  #  # ]:           0 :             throw Exception(Error::IO, "failed to call activeTaskIds via DBus: %1").arg(reply.error().message());
     988                 :             : 
     989         [ #  # ]:           0 :         const auto taskIds = reply.value();
     990                 :           0 :         cancelTaskIds.reserve(taskIds.size());
     991         [ #  # ]:           0 :         for (const auto &taskId : taskIds)
     992         [ #  # ]:           0 :             cancelTaskIds << taskId;
     993                 :           0 :     } else {
     994                 :           1 :         cancelTaskIds << singleTaskId;
     995                 :             :     }
     996                 :             : 
     997         [ -  + ]:           1 :     if (cancelTaskIds.isEmpty())
     998                 :           0 :         qApp->quit();
     999                 :             : 
    1000                 :             :     // on task failure
    1001                 :           1 :     QObject::connect(dbus()->packager(), &IoQtPackageManagerInterface::taskFailed,
    1002                 :           1 :                      qApp, [](const QString &taskId, int errorCode, const QString &errorString) {
    1003         [ +  - ]:           1 :         if (cancelTaskIds.removeOne(taskId)) {
    1004         [ -  + ]:           1 :             if (errorCode != int(Error::Canceled)) {
    1005         [ #  # ]:           0 :                 fprintf(stdout, "Could not cancel task %s anymore - the installation task already failed (%s).\n",
    1006   [ #  #  #  # ]:           0 :                         qPrintable(taskId), qPrintable(errorString));
    1007                 :           0 :                 result |= 2;
    1008                 :             :             } else {
    1009                 :           1 :                 fprintf(stdout, "Installation task was canceled successfully.\n");
    1010                 :             :             }
    1011         [ +  - ]:           1 :             if (cancelTaskIds.isEmpty())
    1012                 :           1 :                 qApp->exit(result);
    1013                 :             :         }
    1014                 :           1 :     });
    1015                 :             : 
    1016                 :             :     // on success
    1017                 :           1 :     QObject::connect(dbus()->packager(), &IoQtPackageManagerInterface::taskFinished,
    1018                 :           0 :                      qApp, [](const QString &taskId) {
    1019         [ #  # ]:           0 :         if (cancelTaskIds.removeOne(taskId)) {
    1020         [ #  # ]:           0 :             fprintf(stdout, "Could not cancel task %s anymore - the installation task already finished successfully.\n",
    1021         [ #  # ]:           0 :                     qPrintable(taskId));
    1022                 :           0 :             result |= 1;
    1023         [ #  # ]:           0 :             if (cancelTaskIds.isEmpty())
    1024                 :           0 :                 qApp->exit(result);
    1025                 :             :         }
    1026                 :           0 :     });
    1027                 :             : 
    1028         [ +  + ]:           2 :     for (const auto &cancelTaskId : std::as_const(cancelTaskIds)) {
    1029         [ +  - ]:           3 :         fprintf(stdout, "Canceling installation task %s...\n", qPrintable(cancelTaskId));
    1030                 :             : 
    1031                 :             :         // cancel the task
    1032                 :             : 
    1033                 :           1 :         auto reply = dbus()->packager()->cancelTask(cancelTaskId);
    1034         [ +  - ]:           1 :         reply.waitForFinished();
    1035   [ +  -  -  + ]:           1 :         if (reply.isError())
    1036   [ #  #  #  # ]:           0 :             throw Exception(Error::IO, "failed to call cancelTask via DBus: %1").arg(reply.error().message());
    1037                 :             : 
    1038   [ +  -  -  + ]:           1 :         if (!reply.value())
    1039                 :           0 :             throw Exception(Error::IO, "failed to cancel the installation task.");
    1040                 :           1 :     }
    1041                 :           1 : }
    1042                 :             : 
    1043                 :           1 : void listInstallationLocations() noexcept(false)
    1044                 :             : {
    1045                 :           1 :     dbus()->connectToPackager();
    1046                 :             : 
    1047                 :           1 :     auto installationLocation = dbus()->packager()->installationLocation();
    1048   [ +  -  +  - ]:           1 :     if (!installationLocation.isEmpty())
    1049         [ +  - ]:           1 :         fputs("internal-0\n", stdout);
    1050         [ +  - ]:           1 :     qApp->quit();
    1051                 :           1 : }
    1052                 :             : 
    1053                 :           1 : void showInstallationLocation(bool asJson) noexcept(false)
    1054                 :             : {
    1055                 :           1 :     dbus()->connectToPackager();
    1056                 :             : 
    1057                 :           1 :     auto installationLocation = dbus()->packager()->installationLocation();
    1058   [ -  +  -  -  :           3 :     fprintf(stdout, "%s\n", asJson ? QJsonDocument::fromVariant(installationLocation).toJson().constData()
          -  -  -  -  +  
          -  -  +  -  +  
             -  -  -  - ]
    1059   [ +  -  +  -  :           7 :                                    : QtYaml::yamlFromVariantDocuments({ installationLocation }).constData());
          +  -  +  -  +  
          -  +  -  +  +  
          -  +  -  -  -  
          -  -  -  -  -  
                   -  - ]
    1060         [ +  - ]:           1 :     qApp->quit();
    1061                 :           1 : }
    1062                 :             : 
    1063                 :          20 : static std::pair<QString, QMultiHash<QString, int>> runningInstanceIds()
    1064                 :             : {
    1065                 :          20 :     QMultiHash<QString, int> result;
    1066                 :             : 
    1067         [ +  - ]:          20 :     QString rtPath = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation);
    1068         [ -  + ]:          20 :     if (rtPath.isEmpty())
    1069         [ #  # ]:           0 :         rtPath = QDir::tempPath();
    1070         [ +  - ]:          20 :     QDir rtDir(rtPath);
    1071   [ +  -  -  + ]:          20 :     if (!rtDir.cd(u"qtapplicationmanager"_s))
    1072         [ #  # ]:           0 :         return { rtDir.path(), result };
    1073                 :             : 
    1074                 :          20 :     const QString suffix = u".lock"_s;
    1075   [ +  -  +  -  :          60 :     QDirIterator dit(rtDir.path(), { u'*' + suffix });
          +  -  +  +  -  
                      - ]
    1076   [ +  -  +  + ]:          40 :     while (dit.hasNext()) {
    1077         [ +  - ]:          20 :         QString path = dit.next();
    1078         [ +  - ]:          20 :         QString name = dit.fileName();
    1079         [ +  - ]:          20 :         name.chop(suffix.length());
    1080         [ +  - ]:          20 :         if (auto dashPos = name.lastIndexOf(u'-'); dashPos > 0) {
    1081                 :          20 :             bool counterOk = false;
    1082   [ +  -  +  - ]:          40 :             int counter = QStringView { name }.sliced(dashPos + 1).toInt(&counterOk);
    1083         [ +  - ]:          20 :             if (counterOk) {
    1084         [ +  - ]:          20 :                 QLockFile testLock(path);
    1085   [ +  -  -  + ]:          20 :                 if (testLock.tryLock(0))
    1086         [ #  # ]:           0 :                     testLock.unlock(); // stale lock
    1087   [ +  -  -  + ]:          20 :                 else if (testLock.error() != QLockFile::LockFailedError)
    1088   [ #  #  #  # ]:           0 :                     fprintf(stderr, "WARNING: unrecoverable, stale lock file: %s\n", qPrintable(path));
    1089                 :             :                 else
    1090         [ +  - ]:          40 :                     result.insert(name.left(dashPos), counter);
    1091                 :          20 :             }
    1092                 :             :         }
    1093                 :          20 :     }
    1094         [ +  - ]:          20 :     return { rtDir.path(), result };
    1095                 :          40 : }
    1096                 :             : 
    1097                 :          19 : static QVariantMap resolveInstanceInfo(const QString &instanceId)
    1098                 :             : {
    1099   [ +  -  +  - ]:          19 :     static const QString defaultInstanceId = u"appman"_s;
    1100   [ +  -  +  -  :          19 :     static QRegularExpression re(uR"(^(.+?)(?:-(\d+))?$)"_s);
                   +  - ]
    1101                 :             : 
    1102         [ +  + ]:          19 :     const auto [baseDir, running] = runningInstanceIds();
    1103         [ +  + ]:          19 :     QString iid = instanceId.isEmpty() ? defaultInstanceId : instanceId;
    1104                 :          19 :     QString result;
    1105                 :             : 
    1106                 :          19 :     try {
    1107                 :          19 :         QString id;
    1108                 :          19 :         int counter = -1;
    1109         [ +  - ]:          19 :         auto m = re.match(iid);
    1110   [ +  -  -  + ]:          19 :         if (!m.hasMatch())
    1111                 :           0 :             throw Exception("Invalid instance-id");
    1112         [ +  - ]:          19 :         id = m.captured(1);
    1113                 :          19 :         bool counterOk = true;
    1114   [ +  -  -  +  :          19 :         counter = m.hasCaptured(2) ? int(m.captured(2).toUInt(&counterOk)) : -1;
          -  -  -  -  -  
                      + ]
    1115         [ -  + ]:          19 :         if (!counterOk)
    1116                 :           0 :             throw Exception("Invalid instance-id");
    1117                 :             : 
    1118         [ -  + ]:          19 :         if (counter >= 0) {
    1119                 :             :             // fully qualified instance id: must match exactly
    1120         [ #  # ]:           0 :             if (running.contains(id, counter))
    1121                 :           0 :                 result = instanceId;
    1122         [ +  - ]:          19 :         } else if (running.count(id) == 1) {
    1123                 :             :             // id only: matches if there's exactly one instance with that name
    1124   [ +  -  +  -  :          19 :             result = id + u'-' + QString::number(running[id]);
                   +  - ]
    1125   [ #  #  #  # ]:           0 :         } else if (instanceId.isEmpty() && (running.count(id) == 0)
    1126   [ #  #  #  # ]:           0 :                    && (running.count() == 1)) {
    1127                 :             :             // no id: matches even a named instance, if that is the only instance running
    1128   [ #  #  #  #  :           0 :             result = running.constBegin().key() + u'-' + QString::number(running.constBegin().value());
                   #  # ]
    1129                 :             :         }
    1130                 :             : 
    1131         [ -  + ]:          19 :         if (result.isEmpty()) {
    1132                 :           0 :             throw Exception("Could not resolve the given instance-id (%1) to any running appman instance.\n  (did you start the appman with '--dbus none'?)")
    1133                 :           0 :                 .arg(instanceId);
    1134                 :             :         }
    1135         [ -  - ]:          19 :     } catch (const Exception &e) {
    1136                 :           0 :         QStringList allIds;
    1137         [ -  - ]:           0 :         for (auto it = running.cbegin(); it != running.cend(); ++it)
    1138   [ -  -  -  -  :           0 :             allIds.append(it.key() + u'-' + QString::number(it.value()));
                   -  - ]
    1139         [ -  - ]:           0 :         throw Exception(u"%1\n\nAvailable instances:\n  %2"_s.arg(e.errorString())
    1140   [ -  -  -  - ]:           0 :                         .arg(allIds.join(u"\n  ")));
    1141                 :           0 :     }
    1142                 :             : 
    1143   [ +  -  +  -  :          38 :     QFile infof(baseDir + u'/' + result + u".json"_s);
             +  -  +  - ]
    1144   [ +  -  -  + ]:          19 :     if (!infof.open(QIODevice::ReadOnly))
    1145                 :           0 :         throw Exception(infof, "Could not open instance info file");
    1146                 :             : 
    1147                 :          19 :     QJsonParseError jsonError;
    1148   [ +  -  +  - ]:          19 :     const auto json = QJsonDocument::fromJson(infof.readAll(), &jsonError);
    1149   [ +  -  -  + ]:          19 :     if (json.isNull()) {
    1150                 :           0 :         throw Exception("Failed to parse instance info file (%1) as JSON: %2")
    1151   [ #  #  #  # ]:           0 :             .arg(infof.fileName()).arg(jsonError.errorString());
    1152                 :             :     }
    1153   [ +  -  +  - ]:          19 :     return json.toVariant().toMap();
    1154                 :          38 : }
    1155                 :             : 
    1156                 :           1 : void listInstances()
    1157                 :             : {
    1158                 :           1 :     const auto [_, running] = runningInstanceIds();
    1159         [ +  + ]:           2 :     for (auto it = running.cbegin(); it != running.cend(); ++it) {
    1160         [ +  - ]:           1 :         auto &name = it.key();
    1161   [ +  -  +  - ]:           3 :         fprintf(stdout, "%s-%d\n", name.toLocal8Bit().constData(), it.value());
    1162                 :             :     }
    1163         [ +  - ]:           1 :     qApp->quit();
    1164                 :           1 : }
    1165                 :             : 
    1166                 :           3 : void injectIntentRequest(const QString &intentId, bool isBroadcast,
    1167                 :             :                          const QString &requestingApplicationId, const QString &applicationId,
    1168                 :             :                          const QString &jsonParameters) noexcept(false)
    1169                 :             : {
    1170                 :           3 :     dbus()->connectToManager();
    1171                 :             : 
    1172         [ +  + ]:           3 :     if (isBroadcast) {
    1173                 :           2 :         auto reply = dbus()->manager()->broadcastIntentRequestAs(requestingApplicationId,
    1174                 :             :                     intentId,
    1175                 :           2 :                     jsonParameters);
    1176         [ +  - ]:           2 :         reply.waitForFinished();
    1177   [ +  -  -  + ]:           2 :         if (reply.isError())
    1178   [ #  #  #  # ]:           0 :             throw Exception(Error::IO, "failed to call broadcastIntentRequest via DBus: %1").arg(reply.error().message());
    1179                 :           2 :     } else {
    1180                 :           1 :         auto reply = dbus()->manager()->sendIntentRequestAs(requestingApplicationId,
    1181                 :             :                     intentId,
    1182                 :             :                     applicationId,
    1183                 :           1 :                     jsonParameters);
    1184         [ +  - ]:           1 :         reply.waitForFinished();
    1185   [ +  -  -  + ]:           1 :         if (reply.isError())
    1186   [ #  #  #  # ]:           0 :             throw Exception(Error::IO, "failed to call sendIntentRequest via DBus: %1").arg(reply.error().message());
    1187         [ +  - ]:           1 :         const auto jsonResult = reply.value();
    1188   [ +  -  +  - ]:           3 :         fprintf(stdout, "%s\n", qPrintable(jsonResult));
    1189                 :           1 :     }
    1190                 :             : 
    1191                 :           3 :     qApp->quit();
    1192                 :           3 : }
    1193                 :             : 
    1194                 :             : #include "controller.moc"
        

Generated by: LCOV version 2.0-1