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