File indexing completed on 2023-11-26 10:45:17

0001 /*
0002     SPDX-FileCopyrightText: 2010 Ryan Rix <ry@n.rix.si>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "packageloader.h"
0008 #include "private/packageloader_p.h"
0009 #include "private/utils.h"
0010 
0011 #include "kpackage_debug.h"
0012 #include <QCoreApplication>
0013 #include <QDateTime>
0014 #include <QDirIterator>
0015 #include <QStandardPaths>
0016 #include <QVector>
0017 
0018 #include <KLazyLocalizedString>
0019 #include <KLocalizedString>
0020 #include <KPluginFactory>
0021 #include <KPluginMetaData>
0022 
0023 #include "config-package.h"
0024 
0025 #include "package.h"
0026 #include "packagestructure.h"
0027 #include "private/packagejobthread_p.h"
0028 #include "private/packages_p.h"
0029 
0030 namespace KPackage
0031 {
0032 static PackageLoader *s_packageTrader = nullptr;
0033 
0034 PackageLoader::PackageLoader()
0035     : d(new PackageLoaderPrivate)
0036 {
0037 }
0038 
0039 PackageLoader::~PackageLoader()
0040 {
0041     for (auto wp : std::as_const(d->structures)) {
0042         delete wp.data();
0043     }
0044     delete d;
0045 }
0046 
0047 PackageLoader *PackageLoader::self()
0048 {
0049     if (!s_packageTrader) {
0050         // we have been called before any PackageLoader was set, so just use the default
0051         // implementation. this prevents plugins from nefariously injecting their own
0052         // plugin loader if the app doesn't
0053         s_packageTrader = new PackageLoader;
0054         s_packageTrader->d->isDefaultLoader = true;
0055     }
0056 
0057     return s_packageTrader;
0058 }
0059 
0060 Package PackageLoader::loadPackage(const QString &packageFormat, const QString &packagePath)
0061 {
0062     if (packageFormat.isEmpty()) {
0063         return Package();
0064     }
0065 
0066     PackageStructure *structure = loadPackageStructure(packageFormat);
0067 
0068     if (structure) {
0069         Package p(structure);
0070         if (!packagePath.isEmpty()) {
0071             p.setPath(packagePath);
0072         }
0073         return p;
0074     }
0075 
0076 #ifndef NDEBUG
0077     // qCDebug(KPACKAGE_LOG) << "Couldn't load Package for" << packageFormat << "! reason given: " << error;
0078 #endif
0079 
0080     return Package();
0081 }
0082 
0083 QList<KPluginMetaData> PackageLoader::listPackages(const QString &packageFormat, const QString &packageRoot)
0084 {
0085     // Note: Use QDateTime::currentSecsSinceEpoch() once we can depend on Qt 5.8
0086     const qint64 now = qRound64(QDateTime::currentMSecsSinceEpoch() / 1000.0);
0087     bool useRuntimeCache = true;
0088     if (now - d->pluginCacheAge > d->maxCacheAge && d->pluginCacheAge != 0) {
0089         // cache is old and we're not within a few seconds of startup anymore
0090         useRuntimeCache = false;
0091         d->pluginCache.clear();
0092     }
0093 
0094     const QString cacheKey = QStringLiteral("%1.%2").arg(packageFormat, packageRoot);
0095     if (useRuntimeCache) {
0096         auto it = d->pluginCache.constFind(cacheKey);
0097         if (it != d->pluginCache.constEnd()) {
0098             return *it;
0099         }
0100     }
0101     if (d->pluginCacheAge == 0) {
0102         d->pluginCacheAge = now;
0103     }
0104 
0105     QList<KPluginMetaData> lst;
0106 
0107     // has been a root specified?
0108     QString actualRoot = packageRoot;
0109 
0110     // try to take it from the package structure
0111     if (actualRoot.isEmpty()) {
0112         PackageStructure *structure = d->structures.value(packageFormat).data();
0113         if (!structure) {
0114             if (packageFormat == QStringLiteral("KPackage/Generic")) {
0115                 structure = new GenericPackage();
0116             } else if (packageFormat == QStringLiteral("KPackage/GenericQML")) {
0117                 structure = new GenericQMLPackage();
0118             }
0119         }
0120 
0121         if (!structure) {
0122             structure = loadPackageStructure(packageFormat);
0123         }
0124 
0125         if (structure) {
0126             d->structures.insert(packageFormat, structure);
0127             Package p(structure);
0128             actualRoot = p.defaultPackageRoot();
0129         }
0130     }
0131 
0132     if (actualRoot.isEmpty()) {
0133         actualRoot = packageFormat;
0134     }
0135 
0136     QSet<QString> uniqueIds;
0137     QStringList paths;
0138     if (QDir::isAbsolutePath(actualRoot)) {
0139         paths = QStringList(actualRoot);
0140     } else {
0141         const auto listPath = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation);
0142         for (const QString &path : listPath) {
0143             paths += path + QLatin1Char('/') + actualRoot;
0144         }
0145     }
0146 
0147     for (auto const &plugindir : std::as_const(paths)) {
0148         const QDirIterator::IteratorFlags flags = QDirIterator::Subdirectories;
0149         const QStringList nameFilters = {QStringLiteral("metadata.json")};
0150 
0151         QDirIterator it(plugindir, nameFilters, QDir::Files, flags);
0152         QSet<QString> dirs;
0153         while (it.hasNext()) {
0154             it.next();
0155 
0156             const QString dir = it.fileInfo().absoluteDir().path();
0157 
0158             if (dirs.contains(dir)) {
0159                 continue;
0160             }
0161             dirs << dir;
0162 
0163             const QString metadataPath = it.fileInfo().absoluteFilePath();
0164             KPluginMetaData info = KPluginMetaData::fromJsonFile(metadataPath);
0165 
0166             if (!info.isValid() || uniqueIds.contains(info.pluginId())) {
0167                 continue;
0168             }
0169 
0170             if (packageFormat.isEmpty() || readKPackageType(info) == packageFormat) {
0171                 uniqueIds << info.pluginId();
0172                 lst << info;
0173             } else {
0174                 qInfo() << "KPackageStructure of" << info << "does not match requested format" << packageFormat;
0175             }
0176         }
0177     }
0178 
0179     if (useRuntimeCache) {
0180         d->pluginCache.insert(cacheKey, lst);
0181     }
0182     return lst;
0183 }
0184 
0185 QList<KPluginMetaData>
0186 PackageLoader::findPackages(const QString &packageFormat, const QString &packageRoot, std::function<bool(const KPluginMetaData &)> filter)
0187 {
0188     QList<KPluginMetaData> lst;
0189     const auto lstPlugins = listPackages(packageFormat, packageRoot);
0190     for (auto const &plugin : lstPlugins) {
0191         if (!filter || filter(plugin)) {
0192             lst << plugin;
0193         }
0194     }
0195     return lst;
0196 }
0197 
0198 KPackage::PackageStructure *PackageLoader::loadPackageStructure(const QString &packageFormat)
0199 {
0200     PackageStructure *structure = d->structures.value(packageFormat).data();
0201     if (!structure) {
0202         if (packageFormat == QStringLiteral("KPackage/Generic")) {
0203             structure = new GenericPackage();
0204             d->structures.insert(packageFormat, structure);
0205         } else if (packageFormat == QStringLiteral("KPackage/GenericQML")) {
0206             structure = new GenericQMLPackage();
0207             d->structures.insert(packageFormat, structure);
0208         }
0209     }
0210 
0211     if (structure) {
0212         return structure;
0213     }
0214 
0215     const KPluginMetaData metaData = structureForKPackageType(packageFormat);
0216     if (!metaData.isValid()) {
0217         qCWarning(KPACKAGE_LOG) << "Invalid metadata for package structure" << packageFormat;
0218         return nullptr;
0219     }
0220 
0221     auto result = KPluginFactory::instantiatePlugin<PackageStructure>(metaData);
0222     if (!result) {
0223         qCWarning(KPACKAGE_LOG) << i18n("Could not load installer for package of type %1. Error reported was: %2", packageFormat, result.errorString);
0224         return nullptr;
0225     }
0226 
0227     structure = result.plugin;
0228 
0229     d->structures.insert(packageFormat, structure);
0230 
0231     return structure;
0232 }
0233 
0234 void PackageLoader::addKnownPackageStructure(const QString &packageFormat, KPackage::PackageStructure *structure)
0235 {
0236     d->structures.insert(packageFormat, structure);
0237 }
0238 
0239 void PackageLoader::invalidateCache()
0240 {
0241     self()->d->maxCacheAge = -1;
0242 }
0243 
0244 } // KPackage Namespace