File indexing completed on 2024-04-28 04:00:30

0001 /*
0002     SPDX-FileCopyrightText: 2015 Aleix Pol Gonzalez <aleixpol@blue-systems.com>
0003 
0004     SPDX-License-Identifier: LGPL-2.1-or-later
0005 */
0006 
0007 #include "processjob.h"
0008 #include "cmake-paths.h"
0009 #include "purpose_external_process_debug.h"
0010 #include <QCborValue>
0011 #include <QFile>
0012 #include <QFileInfo>
0013 #include <QJsonDocument>
0014 #include <QLibrary>
0015 #include <QMetaMethod>
0016 #include <QRandomGenerator>
0017 #include <QStandardPaths>
0018 
0019 using namespace Purpose;
0020 
0021 ProcessJob::ProcessJob(const QString &pluginPath, const QString &pluginType, const QJsonObject &data, QObject *parent)
0022     : Job(parent)
0023     , m_process(new QProcess(this))
0024     , m_pluginPath(pluginPath)
0025     , m_pluginType(pluginType)
0026     , m_data(data)
0027     , m_localSocket(nullptr)
0028 {
0029     if (QLibrary::isLibrary(pluginPath)) {
0030         QString exec = QStandardPaths::findExecutable(QStringLiteral("purposeprocess"), QStringList(QStringLiteral(KDE_INSTALL_FULL_LIBEXECDIR_KF)));
0031         Q_ASSERT(!exec.isEmpty());
0032         m_process->setProgram(exec);
0033     } else {
0034         Q_ASSERT(QFile::exists(pluginPath));
0035         Q_ASSERT(QFileInfo(pluginPath).permission(QFile::ExeOther | QFile::ExeGroup | QFile::ExeUser));
0036         m_process->setProgram(pluginPath);
0037     }
0038     m_process->setProcessChannelMode(QProcess::ForwardedChannels);
0039 
0040     connect(static_cast<QProcess *>(m_process), &QProcess::errorOccurred, this, [](QProcess::ProcessError error) {
0041         qCWarning(PURPOSE_EXTERNAL_PROCESS_LOG) << "error!" << error;
0042     });
0043     connect(static_cast<QProcess *>(m_process), &QProcess::stateChanged, this, &ProcessJob::processStateChanged);
0044 
0045     m_socket.setMaxPendingConnections(1);
0046     m_socket.setSocketOptions(QLocalServer::UserAccessOption);
0047     bool b = m_socket.listen(QStringLiteral("randomname-%1").arg(QRandomGenerator::global()->generate()));
0048     Q_ASSERT(b);
0049     connect(&m_socket, &QLocalServer::newConnection, this, &ProcessJob::writeSocket);
0050 }
0051 
0052 ProcessJob::~ProcessJob()
0053 {
0054     m_process->kill();
0055     delete m_process;
0056 }
0057 
0058 void ProcessJob::writeSocket()
0059 {
0060     m_localSocket = m_socket.nextPendingConnection();
0061     connect(static_cast<QIODevice *>(m_localSocket), &QIODevice::readyRead, this, &ProcessJob::readSocket);
0062 
0063     m_socket.removeServer(m_socket.serverName());
0064 
0065     const QByteArray data = QCborValue::fromJsonValue(m_data).toCbor();
0066     m_localSocket->write(QByteArray::number(data.size()) + '\n');
0067     const auto ret = m_localSocket->write(data);
0068     Q_ASSERT(ret == data.size());
0069     m_localSocket->flush();
0070 }
0071 
0072 void ProcessJob::readSocket()
0073 {
0074     QJsonParseError error;
0075     while (m_localSocket && m_localSocket->canReadLine()) {
0076         const QByteArray json = m_localSocket->readLine();
0077 
0078         const QJsonObject object = QJsonDocument::fromJson(json, &error).object();
0079         if (error.error != QJsonParseError::NoError) {
0080             qCWarning(PURPOSE_EXTERNAL_PROCESS_LOG) << "error!" << error.errorString() << json;
0081             continue;
0082         }
0083 
0084         for (auto it = object.constBegin(), itEnd = object.constEnd(); it != itEnd; ++it) {
0085             const QByteArray propName = it.key().toLatin1();
0086             if (propName == "percent") {
0087                 setPercent(it->toInt());
0088             } else if (propName == "error") {
0089                 setError(it->toInt());
0090             } else if (propName == "errorText") {
0091                 setErrorText(it->toString());
0092             } else if (propName == "output") {
0093                 setOutput(it->toObject());
0094             }
0095         }
0096     }
0097 }
0098 
0099 void ProcessJob::start()
0100 {
0101     m_process->setArguments(
0102         {QStringLiteral("--server"), m_socket.fullServerName(), QStringLiteral("--pluginType"), m_pluginType, QStringLiteral("--pluginPath"), m_pluginPath});
0103 
0104     qCDebug(PURPOSE_EXTERNAL_PROCESS_LOG) << "launching..." << m_process->program() << m_process->arguments().join(QLatin1Char(' ')).constData();
0105 
0106     m_process->start();
0107 }
0108 
0109 void Purpose::ProcessJob::processStateChanged(QProcess::ProcessState state)
0110 {
0111     if (state == QProcess::NotRunning) {
0112         Q_ASSERT(m_process->exitCode() != 0 || m_localSocket);
0113         if (m_process->exitCode() != 0) {
0114             qCWarning(PURPOSE_EXTERNAL_PROCESS_LOG) << "process exited with code:" << m_process->exitCode();
0115         }
0116 
0117         do {
0118             readSocket();
0119         } while (m_localSocket->waitForReadyRead());
0120         emitResult();
0121     }
0122 }
0123 
0124 #include "moc_processjob.cpp"