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
22static QString pluginType;
23static KPluginMetaData md;
24
25class Communication : public QObject
26{
27 Q_OBJECT
28public:
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
80private 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
110private:
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
122int 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

source code of purpose/src/externalprocess/purposeprocess_main.cpp