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 <KPluginMetaData>
0008 #include <QApplication>
0009 #include <QCborMap>
0010 #include <QCborValue>
0011 #include <QCommandLineParser>
0012 #include <QFileInfo>
0013 #include <QJsonDocument>
0014 #include <QLocalSocket>
0015 #include <QMetaMethod>
0016 
0017 #include "helper.h"
0018 #include "purpose_external_process_debug.h"
0019 #include <purpose/configuration.h>
0020 #include <purpose/job.h>
0021 
0022 static QString pluginType;
0023 static KPluginMetaData md;
0024 
0025 class Communication : public QObject
0026 {
0027     Q_OBJECT
0028 public:
0029     Communication(const QString &serverName)
0030     {
0031         int pcIdx = metaObject()->indexOfSlot("propertyChanged()");
0032         Q_ASSERT(pcIdx >= 0);
0033         const QMetaMethod propertyChangedMethod = metaObject()->method(pcIdx);
0034 
0035         m_socket.setServerName(serverName);
0036         m_socket.connectToServer(QIODevice::ReadWrite);
0037         connect(&m_socket, SIGNAL(error(QLocalSocket::LocalSocketError)), this, SLOT(error()));
0038 
0039         bool b = m_socket.waitForConnected();
0040         Q_ASSERT(b);
0041 
0042         b = m_socket.waitForReadyRead();
0043         Q_ASSERT(b);
0044 
0045         Q_ASSERT(m_socket.canReadLine());
0046         QByteArray byteLine = m_socket.readLine();
0047         byteLine.chop(1); // Drop \n
0048         const qint64 bytes = byteLine.toLongLong();
0049         // QByteArray and QJsonDocument::from* can only handle int size.
0050         // If the payload is bigger we are screwed.
0051         Q_ASSERT(bytes <= std::numeric_limits<int>::max());
0052 
0053         QByteArray dataArray;
0054         dataArray.resize(bytes);
0055         int pos = 0;
0056         bool couldRead = false;
0057         while ((pos < bytes) && (couldRead = (m_socket.bytesAvailable() || m_socket.waitForReadyRead()))) {
0058             pos += m_socket.read(dataArray.data() + pos, qMin(m_socket.bytesAvailable(), bytes - pos));
0059         }
0060         Q_ASSERT(couldRead); // false if we hit a timeout before read-end.
0061         Q_ASSERT(pos == bytes);
0062 
0063         Purpose::Configuration config(QCborValue::fromCbor(dataArray).toMap().toJsonObject(), pluginType, md);
0064         config.setUseSeparateProcess(false);
0065 
0066         Q_ASSERT(config.isReady());
0067 
0068         m_job = config.createJob();
0069         m_job->start();
0070 
0071         const QMetaObject *m = m_job->metaObject();
0072         for (int i = 0, c = m->propertyCount(); i < c; ++i) {
0073             QMetaProperty prop = m->property(i);
0074             if (prop.hasNotifySignal() && prop.isReadable()) {
0075                 connect(m_job, prop.notifySignal(), this, propertyChangedMethod, Qt::UniqueConnection);
0076             }
0077         }
0078     }
0079 
0080 private Q_SLOTS:
0081     void error()
0082     {
0083         qCWarning(PURPOSE_EXTERNAL_PROCESS_LOG) << "socket error:" << m_socket.error();
0084     }
0085 
0086     void propertyChanged()
0087     {
0088         const int idx = senderSignalIndex();
0089 
0090         const QMetaObject *m = m_job->metaObject();
0091         QJsonObject toSend;
0092         for (int i = 0, c = m->propertyCount(); i < c; ++i) {
0093             QMetaProperty prop = m->property(i);
0094             if (prop.notifySignalIndex() == idx) {
0095                 toSend[QString::fromLatin1(prop.name())] = fromVariant(prop.read(m_job));
0096             }
0097         }
0098         send(toSend);
0099     }
0100 
0101     static QJsonValue fromVariant(const QVariant &var)
0102     {
0103         if (var.canConvert<QJsonObject>()) {
0104             return var.toJsonObject();
0105         } else {
0106             return QJsonValue::fromVariant(var);
0107         }
0108     }
0109 
0110 private:
0111     void send(const QJsonObject &object)
0112     {
0113         const QByteArray data = QJsonDocument(object).toJson(QJsonDocument::Compact) + '\n';
0114         //         qCDebug(PURPOSE_EXTERNAL_PROCESS_LOG) << "sending..." << data;
0115         m_socket.write(data);
0116     }
0117 
0118     Purpose::Job *m_job = nullptr;
0119     QLocalSocket m_socket;
0120 };
0121 
0122 int main(int argc, char **argv)
0123 {
0124 #pragma message("warning: make QGuiApplication, consider QCoreApplication?")
0125     QApplication app(argc, argv);
0126 
0127     QString serverName;
0128 
0129     {
0130         QCommandLineParser parser;
0131         parser.addOption(QCommandLineOption(QStringLiteral("server"), QStringLiteral("Named socket to connect to"), QStringLiteral("name")));
0132         parser.addOption(QCommandLineOption(QStringLiteral("pluginPath"), QStringLiteral("Chosen plugin"), QStringLiteral("path")));
0133         parser.addOption(QCommandLineOption(QStringLiteral("pluginType"), QStringLiteral("Plugin type name "), QStringLiteral("path")));
0134         parser.addHelpOption();
0135 
0136         parser.process(app);
0137 
0138         serverName = parser.value(QStringLiteral("server"));
0139         pluginType = parser.value(QStringLiteral("pluginType"));
0140 
0141         const QString path = parser.value(QStringLiteral("pluginPath"));
0142         if (path.endsWith(QLatin1String("/metadata.json"))) {
0143             QFileInfo fi(path);
0144             md = Purpose::createMetaData(path);
0145         } else {
0146             md = KPluginMetaData(path);
0147             Q_ASSERT(md.isValid());
0148         }
0149     }
0150 
0151     Communication c(serverName);
0152 
0153     return app.exec();
0154 }
0155 
0156 #include "purposeprocess_main.moc"