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 | |
19 | using namespace Purpose; |
20 | |
21 | ProcessJob::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 | |
52 | ProcessJob::~ProcessJob() |
53 | { |
54 | m_process->kill(); |
55 | delete m_process; |
56 | } |
57 | |
58 | void 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 | |
72 | void 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 | |
99 | void 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 | |
109 | void 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 | |