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