1/*
2 SPDX-FileCopyrightText: 2015 Aleix Pol Gonzalez <aleixpol@blue-systems.com>
3
4 SPDX-License-Identifier: LGPL-2.1-or-later
5*/
6
7#include "processjob.h"
8#include "cmake-paths.h"
9#include "purpose_external_process_debug.h"
10#include <QCborValue>
11#include <QFile>
12#include <QFileInfo>
13#include <QJsonDocument>
14#include <QLibrary>
15#include <QMetaMethod>
16#include <QRandomGenerator>
17#include <QStandardPaths>
18
19using namespace Purpose;
20
21ProcessJob::ProcessJob(const QString &pluginPath, const QString &pluginType, const QJsonObject &data, QObject *parent)
22 : Job(parent)
23 , m_process(new QProcess(this))
24 , m_pluginPath(pluginPath)
25 , m_pluginType(pluginType)
26 , m_data(data)
27 , m_localSocket(nullptr)
28{
29 if (QLibrary::isLibrary(fileName: pluginPath)) {
30 QString exec = QStandardPaths::findExecutable(QStringLiteral("purposeprocess"), paths: QStringList(QStringLiteral(KDE_INSTALL_FULL_LIBEXECDIR_KF)));
31 Q_ASSERT(!exec.isEmpty());
32 m_process->setProgram(exec);
33 } else {
34 Q_ASSERT(QFile::exists(pluginPath));
35 Q_ASSERT(QFileInfo(pluginPath).permission(QFile::ExeOther | QFile::ExeGroup | QFile::ExeUser));
36 m_process->setProgram(pluginPath);
37 }
38 m_process->setProcessChannelMode(QProcess::ForwardedChannels);
39
40 connect(sender: static_cast<QProcess *>(m_process), signal: &QProcess::errorOccurred, context: this, slot: [](QProcess::ProcessError error) {
41 qCWarning(PURPOSE_EXTERNAL_PROCESS_LOG) << "error!" << error;
42 });
43 connect(sender: static_cast<QProcess *>(m_process), signal: &QProcess::stateChanged, context: this, slot: &ProcessJob::processStateChanged);
44
45 m_socket.setMaxPendingConnections(1);
46 m_socket.setSocketOptions(QLocalServer::UserAccessOption);
47 bool b = m_socket.listen(QStringLiteral("randomname-%1").arg(a: QRandomGenerator::global()->generate()));
48 Q_ASSERT(b);
49 connect(sender: &m_socket, signal: &QLocalServer::newConnection, context: this, slot: &ProcessJob::writeSocket);
50}
51
52ProcessJob::~ProcessJob()
53{
54 m_process->kill();
55 delete m_process;
56}
57
58void ProcessJob::writeSocket()
59{
60 m_localSocket = m_socket.nextPendingConnection();
61 connect(sender: static_cast<QIODevice *>(m_localSocket), signal: &QIODevice::readyRead, context: this, slot: &ProcessJob::readSocket);
62
63 m_socket.removeServer(name: m_socket.serverName());
64
65 const QByteArray data = QCborValue::fromJsonValue(v: m_data).toCbor();
66 m_localSocket->write(data: QByteArray::number(data.size()) + '\n');
67 const auto ret = m_localSocket->write(data);
68 Q_ASSERT(ret == data.size());
69 m_localSocket->flush();
70}
71
72void ProcessJob::readSocket()
73{
74 QJsonParseError error;
75 while (m_localSocket && m_localSocket->canReadLine()) {
76 const QByteArray json = m_localSocket->readLine();
77
78 const QJsonObject object = QJsonDocument::fromJson(json, error: &error).object();
79 if (error.error != QJsonParseError::NoError) {
80 qCWarning(PURPOSE_EXTERNAL_PROCESS_LOG) << "error!" << error.errorString() << json;
81 continue;
82 }
83
84 for (auto it = object.constBegin(), itEnd = object.constEnd(); it != itEnd; ++it) {
85 const QByteArray propName = it.key().toLatin1();
86 if (propName == "percent") {
87 setPercent(it->toInt());
88 } else if (propName == "error") {
89 setError(it->toInt());
90 } else if (propName == "errorText") {
91 setErrorText(it->toString());
92 } else if (propName == "output") {
93 setOutput(it->toObject());
94 }
95 }
96 }
97}
98
99void ProcessJob::start()
100{
101 m_process->setArguments(
102 {QStringLiteral("--server"), m_socket.fullServerName(), QStringLiteral("--pluginType"), m_pluginType, QStringLiteral("--pluginPath"), m_pluginPath});
103
104 qCDebug(PURPOSE_EXTERNAL_PROCESS_LOG) << "launching..." << m_process->program() << m_process->arguments().join(sep: QLatin1Char(' ')).constData();
105
106 m_process->start();
107}
108
109void Purpose::ProcessJob::processStateChanged(QProcess::ProcessState state)
110{
111 if (state == QProcess::NotRunning) {
112 Q_ASSERT(m_process->exitCode() != 0 || m_localSocket);
113 if (m_process->exitCode() != 0) {
114 qCWarning(PURPOSE_EXTERNAL_PROCESS_LOG) << "process exited with code:" << m_process->exitCode();
115 }
116
117 do {
118 readSocket();
119 } while (m_localSocket->waitForReadyRead());
120 emitResult();
121 }
122}
123
124#include "moc_processjob.cpp"
125

source code of purpose/src/externalprocess/processjob.cpp