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 "purpose/configuration.h"
8#include "externalprocess/processjob.h"
9#include <QFileInfo>
10
11#include <KPluginFactory>
12#include <KPluginMetaData>
13
14#include <QDebug>
15#include <QDir>
16#include <QStandardPaths>
17#include <qregularexpression.h>
18
19#include "helper.h"
20#include "pluginbase.h"
21
22using namespace Purpose;
23
24class Purpose::ConfigurationPrivate
25{
26public:
27 QJsonObject m_inputData;
28 QString m_pluginTypeName;
29 QJsonObject m_pluginType;
30 const KPluginMetaData m_pluginData;
31 bool m_useSeparateProcess;
32
33 static void checkJobFinish(KJob *job)
34 {
35 const QStringList outputArgs = job->property(name: "outputArgs").toStringList();
36 const auto argsSet = QSet<QString>{outputArgs.cbegin(), outputArgs.cend()};
37
38 const QStringList outputKeys = job->property(name: "output").toJsonObject().keys();
39 const auto keysSet = QSet<QString>{outputKeys.cbegin(), outputKeys.cend()};
40
41 if (!keysSet.contains(other: argsSet) && job->error() == 0) {
42 qWarning() << "missing output values for" << job->metaObject()->className() << ". Expected: " << outputArgs.join(QStringLiteral(", "))
43 << ". Got: " << outputKeys.join(QStringLiteral(", "));
44 }
45 }
46
47 Purpose::Job *internalCreateJob(QObject *parent) const
48 {
49 if (m_useSeparateProcess) {
50 return new ProcessJob(m_pluginData.fileName(), m_pluginTypeName, m_inputData, parent);
51 } else {
52 return createJob(parent);
53 }
54 }
55
56 Purpose::Job *createJob(QObject *parent) const
57 {
58 if (m_pluginData.fileName().contains(s: QLatin1String("contents/code/main."))) {
59 return new ProcessJob(m_pluginData.fileName(), m_pluginTypeName, m_inputData, parent);
60 } else {
61 auto pluginResult = KPluginFactory::instantiatePlugin<QObject>(data: m_pluginData, parent, args: QVariantList());
62
63 if (!pluginResult) {
64 qWarning() << "Couldn't load plugin:" << m_pluginData.fileName() << pluginResult.errorString;
65 return nullptr;
66 }
67
68 Purpose::PluginBase *plugin = dynamic_cast<Purpose::PluginBase *>(pluginResult.plugin);
69 return plugin->createJob();
70 }
71 }
72};
73
74Configuration::Configuration(const QJsonObject &inputData, const QString &pluginTypeName, const KPluginMetaData &pluginInformation, QObject *parent)
75 : Configuration(inputData, pluginTypeName, QJsonObject(), pluginInformation, parent)
76{
77}
78
79Configuration::Configuration(const QJsonObject &inputData,
80 const QString &pluginTypeName,
81 const QJsonObject &pluginType,
82 const KPluginMetaData &pluginInformation,
83 QObject *parent)
84 : QObject(parent)
85 , d_ptr(new ConfigurationPrivate{.m_inputData: inputData, .m_pluginTypeName: pluginTypeName, .m_pluginType: pluginType, .m_pluginData: pluginInformation, .m_useSeparateProcess: !qEnvironmentVariableIsSet(varName: "KDE_PURPOSE_LOCAL_JOBS")})
86{
87}
88
89Configuration::~Configuration()
90{
91 delete d_ptr;
92}
93
94void Configuration::setData(const QJsonObject &data)
95{
96 Q_D(Configuration);
97
98 // qDebug() << "datachanged" << data;
99 if (d->m_inputData != data) {
100 d->m_inputData = data;
101 Q_EMIT dataChanged();
102 }
103}
104
105QJsonObject Configuration::data() const
106{
107 Q_D(const Configuration);
108 return d->m_inputData;
109}
110
111bool Configuration::isReady() const
112{
113 Q_D(const Configuration);
114 bool ok = true;
115 const auto arguments = neededArguments();
116 for (const QJsonValue &arg : arguments) {
117 if (!d->m_inputData.contains(key: arg.toString())) {
118 qDebug() << "missing mandatory argument" << arg.toString();
119 ok = false;
120 }
121 }
122 return ok;
123}
124
125QJsonArray Configuration::neededArguments() const
126{
127 Q_D(const Configuration);
128 QJsonArray ret = d->m_pluginType.value(key: QLatin1String("X-Purpose-InboundArguments")).toArray();
129 const QJsonArray arr = d->m_pluginData.rawData().value(key: QLatin1String("X-Purpose-Configuration")).toArray();
130 for (const QJsonValue &val : arr) {
131 ret += val;
132 }
133 return ret;
134}
135
136Purpose::Job *Configuration::createJob()
137{
138 if (!isReady()) {
139 return nullptr;
140 }
141
142 Q_D(const Configuration);
143
144 Purpose::Job *job = d->internalCreateJob(parent: this);
145 if (!job) {
146 return job;
147 }
148
149 job->setData(d->m_inputData);
150 job->setProperty(name: "outputArgs", value: d->m_pluginType.value(key: QLatin1String("X-Purpose-OutboundArguments")));
151
152 connect(sender: job, signal: &Purpose::Job::finished, slot: &ConfigurationPrivate::checkJobFinish);
153 return job;
154}
155
156QUrl Configuration::configSourceCode() const
157{
158 Q_D(const Configuration);
159 if (d->m_pluginData.fileName().contains(s: QLatin1String("contents/code/main."))) {
160 const QFileInfo fi(d->m_pluginData.fileName());
161 QDir conentsDir = fi.dir();
162 conentsDir.cdUp();
163 return QUrl::fromLocalFile(localfile: conentsDir.filePath(QStringLiteral("config/config.qml")));
164 } else {
165 const QString configFile =
166 QStandardPaths::locate(type: QStandardPaths::GenericDataLocation, QStringLiteral("kf6/purpose/%1_config.qml").arg(a: d->m_pluginData.pluginId()));
167 if (configFile.isEmpty()) {
168 return QUrl();
169 }
170
171 return QUrl::fromLocalFile(localfile: configFile);
172 }
173}
174
175bool Configuration::useSeparateProcess() const
176{
177 Q_D(const Configuration);
178 return d->m_useSeparateProcess;
179}
180
181void Configuration::setUseSeparateProcess(bool use)
182{
183 Q_D(Configuration);
184 d->m_useSeparateProcess = use;
185}
186
187QString Configuration::pluginTypeName() const
188{
189 Q_D(const Configuration);
190 KPluginMetaData md(d->m_pluginType, {});
191 return md.name();
192}
193
194QString Configuration::pluginName() const
195{
196 Q_D(const Configuration);
197 return d->m_pluginData.name();
198}
199
200#include "moc_configuration.cpp"
201

source code of purpose/src/configuration.cpp