| 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 | |