diff --git a/src/externalprocess/processjob.cpp b/src/externalprocess/processjob.cpp index a789b65..6457ffa 100644 --- a/src/externalprocess/processjob.cpp +++ b/src/externalprocess/processjob.cpp @@ -1,137 +1,137 @@ /* Copyright 2015 Aleix Pol Gonzalez This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "processjob.h" #include "cmake-paths.h" #include "purpose_external_process_debug.h" #include #include #include #include #include #include #include #include +#include using namespace Purpose; ProcessJob::ProcessJob(const QString &pluginPath, const QString &pluginType, const QJsonObject& data, QObject* parent) : Job(parent) , m_process(new QProcess(this)) , m_pluginPath(pluginPath) , m_pluginType(pluginType) , m_data(data) , m_localSocket(nullptr) { if (QLibrary::isLibrary(pluginPath)) { QString exec = QStandardPaths::findExecutable(QStringLiteral("purposeprocess"), QStringList(QStringLiteral(CMAKE_INSTALL_FULL_LIBEXECDIR_KF5))); Q_ASSERT(!exec.isEmpty()); m_process->setProgram(exec); } else { Q_ASSERT(QFile::exists(pluginPath)); Q_ASSERT(QFileInfo(pluginPath).permission(QFile::ExeOther | QFile::ExeGroup | QFile::ExeUser)); m_process->setProgram(pluginPath); } m_process->setProcessChannelMode(QProcess::ForwardedChannels); connect(static_cast(m_process), &QProcess::errorOccurred, this, [](QProcess::ProcessError error) { qCWarning(PURPOSE_EXTERNAL_PROCESS_LOG) << "error!" << error; } ); connect(static_cast(m_process), &QProcess::stateChanged, this, &ProcessJob::processStateChanged); m_socket.setMaxPendingConnections(1); m_socket.setSocketOptions(QLocalServer::UserAccessOption); bool b = m_socket.listen(QStringLiteral("randomname-%1").arg(KRandom::random())); Q_ASSERT(b); connect(&m_socket, &QLocalServer::newConnection, this, &ProcessJob::writeSocket); } ProcessJob::~ProcessJob() { m_process->kill(); delete m_process; } void ProcessJob::writeSocket() { m_localSocket = m_socket.nextPendingConnection(); connect(static_cast(m_localSocket), &QIODevice::readyRead, this, &ProcessJob::readSocket); m_socket.removeServer(m_socket.serverName()); - const QJsonDocument doc(m_data); - const QByteArray data = doc.toBinaryData(); + const QByteArray data = QCborValue::fromJsonValue(m_data).toCbor(); m_localSocket->write(QByteArray::number(data.size()) + '\n'); const auto ret = m_localSocket->write(data); Q_ASSERT(ret == data.size()); m_localSocket->flush(); } void ProcessJob::readSocket() { QJsonParseError error; while(m_localSocket && m_localSocket->canReadLine()) { const QByteArray json = m_localSocket->readLine(); const QJsonObject object = QJsonDocument::fromJson(json, &error).object(); if (error.error != QJsonParseError::NoError) { qCWarning(PURPOSE_EXTERNAL_PROCESS_LOG) << "error!" << error.errorString() << json; continue; } for(auto it=object.constBegin(), itEnd=object.constEnd(); it!=itEnd; ++it) { const QByteArray propName = it.key().toLatin1(); if (propName == "percent") { setPercent(it->toInt()); } else if (propName == "error") { setError(it->toInt()); } else if (propName == "errorText") { setErrorText(it->toString()); } else if (propName == "output") { setOutput(it->toObject()); } } } } void ProcessJob::start() { m_process->setArguments({ QStringLiteral("--server"), m_socket.fullServerName(), QStringLiteral("--pluginType"), m_pluginType, QStringLiteral("--pluginPath"), m_pluginPath }); qCDebug(PURPOSE_EXTERNAL_PROCESS_LOG) << "launching..." << m_process->program() << m_process->arguments().join(QLatin1Char(' ')).constData(); m_process->start(); } void Purpose::ProcessJob::processStateChanged(QProcess::ProcessState state) { if (state == QProcess::NotRunning) { Q_ASSERT(m_process->exitCode()!=0 || m_localSocket); if (m_process->exitCode()!=0) { qCWarning(PURPOSE_EXTERNAL_PROCESS_LOG) << "process exited with code:" << m_process->exitCode(); } do { readSocket(); } while (m_localSocket->waitForReadyRead()); emitResult(); } } diff --git a/src/externalprocess/purposeprocess_main.cpp b/src/externalprocess/purposeprocess_main.cpp index 2731615..1d4828e 100644 --- a/src/externalprocess/purposeprocess_main.cpp +++ b/src/externalprocess/purposeprocess_main.cpp @@ -1,166 +1,168 @@ /* Copyright 2015 Aleix Pol Gonzalez This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include #include #include #include #include #include #include +#include +#include #include "helper.h" #include "purpose_external_process_debug.h" #include #include static QString pluginType; static KPluginMetaData md; class Communication : public QObject { Q_OBJECT public: Communication(const QString &serverName) { int pcIdx = metaObject()->indexOfSlot("propertyChanged()"); Q_ASSERT(pcIdx>=0); const QMetaMethod propertyChangedMethod = metaObject()->method(pcIdx); m_socket.setServerName(serverName); m_socket.connectToServer(QIODevice::ReadWrite); connect(&m_socket, SIGNAL(error(QLocalSocket::LocalSocketError)), this, SLOT(error())); bool b = m_socket.waitForConnected(); Q_ASSERT(b); b = m_socket.waitForReadyRead(); Q_ASSERT(b); Q_ASSERT(m_socket.canReadLine()); QByteArray byteLine = m_socket.readLine(); byteLine.chop(1); // Drop \n const qint64 bytes = byteLine.toLongLong(); // QByteArray and QJsonDocument::from* can only handle int size. // If the payload is bigger we are screwed. Q_ASSERT(bytes <= std::numeric_limits::max()); QByteArray dataArray; dataArray.resize(bytes); int pos = 0; bool couldRead = false; while ((pos < bytes) && (couldRead = (m_socket.bytesAvailable() || m_socket.waitForReadyRead()))) { pos += m_socket.read(dataArray.data() + pos, qMin(m_socket.bytesAvailable(), bytes-pos)); } Q_ASSERT(couldRead); // false if we hit a timeout before read-end. Q_ASSERT(pos == bytes); - Purpose::Configuration config(QJsonDocument::fromBinaryData(dataArray).object(), pluginType, md); + Purpose::Configuration config(QCborValue::fromCbor(dataArray).toMap().toJsonObject(), pluginType, md); config.setUseSeparateProcess(false); Q_ASSERT(config.isReady()); m_job = config.createJob(); m_job->start(); const QMetaObject* m = m_job->metaObject(); for(int i = 0, c = m->propertyCount(); iproperty(i); if (prop.hasNotifySignal() && prop.isReadable()) { connect(m_job, prop.notifySignal(), this, propertyChangedMethod, Qt::UniqueConnection); } } } private Q_SLOTS: void error() { #if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0)) const auto socketError = m_socket.error(); #else const auto socketError = m_socket.socketError(); #endif qCWarning(PURPOSE_EXTERNAL_PROCESS_LOG) << "socket error:" << socketError; } void propertyChanged() { const int idx = senderSignalIndex(); const QMetaObject* m = m_job->metaObject(); QJsonObject toSend; for(int i = 0, c = m->propertyCount(); iproperty(i); if (prop.notifySignalIndex() == idx) { toSend[QString::fromLatin1(prop.name())] = fromVariant(prop.read(m_job)); } } send(toSend); } static QJsonValue fromVariant(const QVariant &var) { if (var.canConvert()) { return var.toJsonObject(); } else { return QJsonValue::fromVariant(var); } } private: void send(const QJsonObject &object) { const QByteArray data = QJsonDocument(object).toJson(QJsonDocument::Compact) + '\n'; // qCDebug(PURPOSE_EXTERNAL_PROCESS_LOG) << "sending..." << data; m_socket.write(data); } Purpose::Job* m_job = nullptr; QLocalSocket m_socket; }; int main(int argc, char** argv) { #pragma message("warning: make QGuiApplication, consider QCoreApplication?") QApplication app(argc, argv); QString serverName; { QCommandLineParser parser; parser.addOption(QCommandLineOption(QStringLiteral("server"), QStringLiteral("Named socket to connect to"), QStringLiteral("name"))); parser.addOption(QCommandLineOption(QStringLiteral("pluginPath"), QStringLiteral("Chosen plugin"), QStringLiteral("path"))); parser.addOption(QCommandLineOption(QStringLiteral("pluginType"), QStringLiteral("Plugin type name "), QStringLiteral("path"))); parser.addHelpOption(); parser.process(app); serverName = parser.value(QStringLiteral("server")); pluginType = parser.value(QStringLiteral("pluginType")); const QString path = parser.value(QStringLiteral("pluginPath")); if (path.endsWith(QLatin1String("/metadata.json"))) { QFileInfo fi(path); md = Purpose::createMetaData(path); } else { md = KPluginMetaData(path); Q_ASSERT(md.isValid()); } } Communication c(serverName); return app.exec(); } #include "purposeprocess_main.moc"