1 /*
2 SPDX-FileCopyrightText: 2010 Ryan Rix <ry@n.rix.si>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6
7 #include "packageloader.h"
8 #include "private/packageloader_p.h"
9 #include "private/utils.h"
10
11 #include "kpackage_debug.h"
12 #include <QCoreApplication>
13 #include <QDateTime>
14 #include <QDirIterator>
15 #include <QStandardPaths>
16 #include <QVector>
17
18 #include <KLocalizedString>
19 #include <KPluginFactory>
20
21 #include "config-package.h"
22
23 #include "package.h"
24 #include "packagestructure.h"
25 #include "private/packagejobthread_p.h"
26 #include "private/packages_p.h"
27
28 namespace KPackage
29 {
30 static PackageLoader *s_packageTrader = nullptr;
31
32 QSet<QString> PackageLoaderPrivate::s_customCategories;
33
knownCategories()34 QSet<QString> PackageLoaderPrivate::knownCategories()
35 {
36 // this is to trick the translation tools into making the correct
37 // strings for translation
38 QSet<QString> categories = s_customCategories;
39 // clang-format off
40 categories << QStringLiteral(I18N_NOOP("Accessibility")).toLower()
41 << QStringLiteral(I18N_NOOP("Application Launchers")).toLower()
42 << QStringLiteral(I18N_NOOP("Astronomy")).toLower()
43 << QStringLiteral(I18N_NOOP("Date and Time")).toLower()
44 << QStringLiteral(I18N_NOOP("Development Tools")).toLower()
45 << QStringLiteral(I18N_NOOP("Education")).toLower()
46 << QStringLiteral(I18N_NOOP("Environment and Weather")).toLower()
47 << QStringLiteral(I18N_NOOP("Examples")).toLower()
48 << QStringLiteral(I18N_NOOP("File System")).toLower()
49 << QStringLiteral(I18N_NOOP("Fun and Games")).toLower()
50 << QStringLiteral(I18N_NOOP("Graphics")).toLower()
51 << QStringLiteral(I18N_NOOP("Language")).toLower()
52 << QStringLiteral(I18N_NOOP("Mapping")).toLower()
53 << QStringLiteral(I18N_NOOP("Miscellaneous")).toLower()
54 << QStringLiteral(I18N_NOOP("Multimedia")).toLower()
55 << QStringLiteral(I18N_NOOP("Online Services")).toLower()
56 << QStringLiteral(I18N_NOOP("Productivity")).toLower()
57 << QStringLiteral(I18N_NOOP("System Information")).toLower()
58 << QStringLiteral(I18N_NOOP("Utilities")).toLower()
59 << QStringLiteral(I18N_NOOP("Windows and Tasks")).toLower();
60 // clang-format on
61 return categories;
62 }
63
PackageLoader()64 PackageLoader::PackageLoader()
65 : d(new PackageLoaderPrivate)
66 {
67 }
68
~PackageLoader()69 PackageLoader::~PackageLoader()
70 {
71 for (auto wp : std::as_const(d->structures)) {
72 delete wp.data();
73 }
74 delete d;
75 }
76
77 #if KPACKAGE_BUILD_DEPRECATED_SINCE(5, 86)
setPackageLoader(PackageLoader * loader)78 void PackageLoader::setPackageLoader(PackageLoader *loader)
79 {
80 if (!s_packageTrader) {
81 s_packageTrader = loader;
82 }
83 }
84 #endif
85
self()86 PackageLoader *PackageLoader::self()
87 {
88 if (!s_packageTrader) {
89 // we have been called before any PackageLoader was set, so just use the default
90 // implementation. this prevents plugins from nefariously injecting their own
91 // plugin loader if the app doesn't
92 s_packageTrader = new PackageLoader;
93 s_packageTrader->d->isDefaultLoader = true;
94 }
95
96 return s_packageTrader;
97 }
98
loadPackage(const QString & packageFormat,const QString & packagePath)99 Package PackageLoader::loadPackage(const QString &packageFormat, const QString &packagePath)
100 {
101 #if KPACKAGE_BUILD_DEPRECATED_SINCE(5, 86)
102 if (!d->isDefaultLoader) {
103 Package p = internalLoadPackage(packageFormat);
104 if (p.hasValidStructure()) {
105 if (!packagePath.isEmpty()) {
106 p.setPath(packagePath);
107 }
108 return p;
109 }
110 }
111 #endif
112
113 if (packageFormat.isEmpty()) {
114 return Package();
115 }
116
117 PackageStructure *structure = loadPackageStructure(packageFormat);
118
119 if (structure) {
120 Package p(structure);
121 if (!packagePath.isEmpty()) {
122 p.setPath(packagePath);
123 }
124 return p;
125 }
126
127 #ifndef NDEBUG
128 // qCDebug(KPACKAGE_LOG) << "Couldn't load Package for" << packageFormat << "! reason given: " << error;
129 #endif
130
131 return Package();
132 }
133
listPackages(const QString & packageFormat,const QString & packageRoot)134 QList<KPluginMetaData> PackageLoader::listPackages(const QString &packageFormat, const QString &packageRoot)
135 {
136 // Note: Use QDateTime::currentSecsSinceEpoch() once we can depend on Qt 5.8
137 const qint64 now = qRound64(QDateTime::currentMSecsSinceEpoch() / 1000.0);
138 bool useRuntimeCache = true;
139 if (now - d->pluginCacheAge > d->maxCacheAge && d->pluginCacheAge != 0) {
140 // cache is old and we're not within a few seconds of startup anymore
141 useRuntimeCache = false;
142 d->pluginCache.clear();
143 }
144
145 const QString cacheKey = QStringLiteral("%1.%2").arg(packageFormat, packageRoot);
146 if (useRuntimeCache) {
147 auto it = d->pluginCache.constFind(cacheKey);
148 if (it != d->pluginCache.constEnd()) {
149 return *it;
150 }
151 }
152 if (d->pluginCacheAge == 0) {
153 d->pluginCacheAge = now;
154 }
155
156 QList<KPluginMetaData> lst;
157
158 // has been a root specified?
159 QString actualRoot = packageRoot;
160
161 // try to take it from the package structure
162 if (actualRoot.isEmpty()) {
163 PackageStructure *structure = d->structures.value(packageFormat).data();
164 if (!structure) {
165 if (packageFormat == QStringLiteral("KPackage/Generic")) {
166 structure = new GenericPackage();
167 } else if (packageFormat == QStringLiteral("KPackage/GenericQML")) {
168 structure = new GenericQMLPackage();
169 }
170 }
171
172 if (!structure) {
173 structure = loadPackageStructure(packageFormat);
174 }
175
176 if (structure) {
177 d->structures.insert(packageFormat, structure);
178 Package p(structure);
179 actualRoot = p.defaultPackageRoot();
180 }
181 }
182
183 if (actualRoot.isEmpty()) {
184 actualRoot = packageFormat;
185 }
186
187 QSet<QString> uniqueIds;
188 QStringList paths;
189 if (QDir::isAbsolutePath(actualRoot)) {
190 paths = QStringList(actualRoot);
191 } else {
192 const auto listPath = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation);
193 for (const QString &path : listPath) {
194 paths += path + QLatin1Char('/') + actualRoot;
195 }
196 }
197
198 for (auto const &plugindir : std::as_const(paths)) {
199 const QDirIterator::IteratorFlags flags = QDirIterator::Subdirectories;
200 const QStringList nameFilters = {QStringLiteral("metadata.json"), QStringLiteral("metadata.desktop")};
201
202 QDirIterator it(plugindir, nameFilters, QDir::Files, flags);
203 QSet<QString> dirs;
204 while (it.hasNext()) {
205 it.next();
206
207 const QString dir = it.fileInfo().absoluteDir().path();
208
209 if (dirs.contains(dir)) {
210 continue;
211 }
212 dirs << dir;
213
214 const QString metadataPath = it.fileInfo().absoluteFilePath();
215 const KPluginMetaData info(metadataPath);
216
217 if (!info.isValid() || uniqueIds.contains(info.pluginId())) {
218 continue;
219 }
220
221 const QStringList kpackageTypes = readKPackageTypes(info);
222 if (packageFormat.isEmpty() || kpackageTypes.isEmpty() || kpackageTypes.contains(packageFormat)) {
223 uniqueIds << info.pluginId();
224 lst << info;
225 }
226 }
227 }
228
229 if (useRuntimeCache) {
230 d->pluginCache.insert(cacheKey, lst);
231 }
232 return lst;
233 }
234
235 QList<KPluginMetaData>
findPackages(const QString & packageFormat,const QString & packageRoot,std::function<bool (const KPluginMetaData &)> filter)236 PackageLoader::findPackages(const QString &packageFormat, const QString &packageRoot, std::function<bool(const KPluginMetaData &)> filter)
237 {
238 QList<KPluginMetaData> lst;
239 const auto lstPlugins = listPackages(packageFormat, packageRoot);
240 for (auto const &plugin : lstPlugins) {
241 if (!filter || filter(plugin)) {
242 lst << plugin;
243 }
244 }
245 return lst;
246 }
247
loadPackageStructure(const QString & packageFormat)248 KPackage::PackageStructure *PackageLoader::loadPackageStructure(const QString &packageFormat)
249 {
250 PackageStructure *structure = d->structures.value(packageFormat).data();
251 if (!structure) {
252 if (packageFormat == QStringLiteral("KPackage/Generic")) {
253 structure = new GenericPackage();
254 d->structures.insert(packageFormat, structure);
255 } else if (packageFormat == QStringLiteral("KPackage/GenericQML")) {
256 structure = new GenericQMLPackage();
257 d->structures.insert(packageFormat, structure);
258 }
259 }
260
261 if (structure) {
262 return structure;
263 }
264
265 const KPluginMetaData metaData = structureForKPackageType(packageFormat);
266
267 QString error;
268 if (!metaData.isValid()) {
269 qCWarning(KPACKAGE_LOG) << "Invalid metadata for package structure" << packageFormat;
270 return nullptr;
271 }
272
273 auto result = KPluginFactory::instantiatePlugin<PackageStructure>(metaData, nullptr, {metaData.rawData().toVariantMap()});
274
275 if (!result) {
276 qCWarning(KPACKAGE_LOG) << i18n("Could not load installer for package of type %1. Error reported was: %2", packageFormat, result.errorString);
277 return nullptr;
278 }
279
280 structure = result.plugin;
281
282 d->structures.insert(packageFormat, structure);
283
284 return structure;
285 }
286
addKnownPackageStructure(const QString & packageFormat,KPackage::PackageStructure * structure)287 void PackageLoader::addKnownPackageStructure(const QString &packageFormat, KPackage::PackageStructure *structure)
288 {
289 d->structures.insert(packageFormat, structure);
290 }
291
292 #if KPACKAGE_BUILD_DEPRECATED_SINCE(5, 86)
internalLoadPackage(const QString & name)293 Package PackageLoader::internalLoadPackage(const QString &name)
294 {
295 Q_UNUSED(name);
296 return Package();
297 }
298 #endif
299
300 } // KPackage Namespace
301