| 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 <KPluginMetaData> |
| 8 | #include <QApplication> |
| 9 | #include <QCborMap> |
| 10 | #include <QCborValue> |
| 11 | #include <QCommandLineParser> |
| 12 | #include <QFileInfo> |
| 13 | #include <QJsonDocument> |
| 14 | #include <QLocalSocket> |
| 15 | #include <QMetaMethod> |
| 16 | |
| 17 | #include "helper.h" |
| 18 | #include "purpose_external_process_debug.h" |
| 19 | #include <purpose/configuration.h> |
| 20 | #include <purpose/job.h> |
| 21 | |
| 22 | static QString pluginType; |
| 23 | static KPluginMetaData md; |
| 24 | |
| 25 | class Communication : public QObject |
| 26 | { |
| 27 | Q_OBJECT |
| 28 | public: |
| 29 | Communication(const QString &serverName) |
| 30 | { |
| 31 | int pcIdx = metaObject()->indexOfSlot(slot: "propertyChanged()" ); |
| 32 | Q_ASSERT(pcIdx >= 0); |
| 33 | const QMetaMethod propertyChangedMethod = metaObject()->method(index: pcIdx); |
| 34 | |
| 35 | m_socket.setServerName(serverName); |
| 36 | m_socket.connectToServer(openMode: QIODevice::ReadWrite); |
| 37 | connect(sender: &m_socket, SIGNAL(error(QLocalSocket::LocalSocketError)), receiver: this, SLOT(error())); |
| 38 | |
| 39 | bool b = m_socket.waitForConnected(); |
| 40 | Q_ASSERT(b); |
| 41 | |
| 42 | b = m_socket.waitForReadyRead(); |
| 43 | Q_ASSERT(b); |
| 44 | |
| 45 | Q_ASSERT(m_socket.canReadLine()); |
| 46 | QByteArray byteLine = m_socket.readLine(); |
| 47 | byteLine.chop(n: 1); // Drop \n |
| 48 | const qint64 bytes = byteLine.toLongLong(); |
| 49 | // QByteArray and QJsonDocument::from* can only handle int size. |
| 50 | // If the payload is bigger we are screwed. |
| 51 | Q_ASSERT(bytes <= std::numeric_limits<int>::max()); |
| 52 | |
| 53 | QByteArray dataArray; |
| 54 | dataArray.resize(size: bytes); |
| 55 | int pos = 0; |
| 56 | bool couldRead = false; |
| 57 | while ((pos < bytes) && (couldRead = (m_socket.bytesAvailable() || m_socket.waitForReadyRead()))) { |
| 58 | pos += m_socket.read(data: dataArray.data() + pos, maxlen: qMin(a: m_socket.bytesAvailable(), b: bytes - pos)); |
| 59 | } |
| 60 | Q_ASSERT(couldRead); // false if we hit a timeout before read-end. |
| 61 | Q_ASSERT(pos == bytes); |
| 62 | |
| 63 | Purpose::Configuration config(QCborValue::fromCbor(ba: dataArray).toMap().toJsonObject(), pluginType, md); |
| 64 | config.setUseSeparateProcess(false); |
| 65 | |
| 66 | Q_ASSERT(config.isReady()); |
| 67 | |
| 68 | m_job = config.createJob(); |
| 69 | m_job->start(); |
| 70 | |
| 71 | const QMetaObject *m = m_job->metaObject(); |
| 72 | for (int i = 0, c = m->propertyCount(); i < c; ++i) { |
| 73 | QMetaProperty prop = m->property(index: i); |
| 74 | if (prop.hasNotifySignal() && prop.isReadable()) { |
| 75 | connect(sender: m_job, signal: prop.notifySignal(), receiver: this, method: propertyChangedMethod, type: Qt::UniqueConnection); |
| 76 | } |
| 77 | } |
| 78 | } |
| 79 | |
| 80 | private Q_SLOTS: |
| 81 | void error() |
| 82 | { |
| 83 | qCWarning(PURPOSE_EXTERNAL_PROCESS_LOG) << "socket error:" << m_socket.error(); |
| 84 | } |
| 85 | |
| 86 | void propertyChanged() |
| 87 | { |
| 88 | const int idx = senderSignalIndex(); |
| 89 | |
| 90 | const QMetaObject *m = m_job->metaObject(); |
| 91 | QJsonObject toSend; |
| 92 | for (int i = 0, c = m->propertyCount(); i < c; ++i) { |
| 93 | QMetaProperty prop = m->property(index: i); |
| 94 | if (prop.notifySignalIndex() == idx) { |
| 95 | toSend[QString::fromLatin1(ba: prop.name())] = fromVariant(var: prop.read(obj: m_job)); |
| 96 | } |
| 97 | } |
| 98 | send(object: toSend); |
| 99 | } |
| 100 | |
| 101 | static QJsonValue fromVariant(const QVariant &var) |
| 102 | { |
| 103 | if (var.canConvert<QJsonObject>()) { |
| 104 | return var.toJsonObject(); |
| 105 | } else { |
| 106 | return QJsonValue::fromVariant(variant: var); |
| 107 | } |
| 108 | } |
| 109 | |
| 110 | private: |
| 111 | void send(const QJsonObject &object) |
| 112 | { |
| 113 | const QByteArray data = QJsonDocument(object).toJson(format: QJsonDocument::Compact) + '\n'; |
| 114 | // qCDebug(PURPOSE_EXTERNAL_PROCESS_LOG) << "sending..." << data; |
| 115 | m_socket.write(data); |
| 116 | } |
| 117 | |
| 118 | Purpose::Job *m_job = nullptr; |
| 119 | QLocalSocket m_socket; |
| 120 | }; |
| 121 | |
| 122 | int main(int argc, char **argv) |
| 123 | { |
| 124 | #pragma message("warning: make QGuiApplication, consider QCoreApplication?") |
| 125 | QApplication app(argc, argv); |
| 126 | |
| 127 | QString serverName; |
| 128 | |
| 129 | { |
| 130 | QCommandLineParser parser; |
| 131 | parser.addOption(commandLineOption: QCommandLineOption(QStringLiteral("server" ), QStringLiteral("Named socket to connect to" ), QStringLiteral("name" ))); |
| 132 | parser.addOption(commandLineOption: QCommandLineOption(QStringLiteral("pluginPath" ), QStringLiteral("Chosen plugin" ), QStringLiteral("path" ))); |
| 133 | parser.addOption(commandLineOption: QCommandLineOption(QStringLiteral("pluginType" ), QStringLiteral("Plugin type name " ), QStringLiteral("path" ))); |
| 134 | parser.addHelpOption(); |
| 135 | |
| 136 | parser.process(app); |
| 137 | |
| 138 | serverName = parser.value(QStringLiteral("server" )); |
| 139 | pluginType = parser.value(QStringLiteral("pluginType" )); |
| 140 | |
| 141 | const QString path = parser.value(QStringLiteral("pluginPath" )); |
| 142 | if (path.endsWith(s: QLatin1String("/metadata.json" ))) { |
| 143 | QFileInfo fi(path); |
| 144 | md = Purpose::createMetaData(file: path); |
| 145 | } else { |
| 146 | md = KPluginMetaData(path); |
| 147 | Q_ASSERT(md.isValid()); |
| 148 | } |
| 149 | } |
| 150 | |
| 151 | Communication c(serverName); |
| 152 | |
| 153 | return app.exec(); |
| 154 | } |
| 155 | |
| 156 | #include "purposeprocess_main.moc" |
| 157 | |