File indexing completed on 2024-12-08 09:42:56

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 #if KPACKAGE_BUILD_DEPRECATED_SINCE(5, 86)
0048 void PackageLoader::setPackageLoader(PackageLoader *loader)
0049 {
0050     if (!s_packageTrader) {
0051         s_packageTrader = loader;
0052     }
0053 }
0054 #endif
0055 
0056 PackageLoader *PackageLoader::self()
0057 {
0058     if (!s_packageTrader) {
0059         // we have been called before any PackageLoader was set, so just use the default
0060         // implementation. this prevents plugins from nefariously injecting their own
0061         // plugin loader if the app doesn't
0062         s_packageTrader = new PackageLoader;
0063         s_packageTrader->d->isDefaultLoader = true;
0064     }
0065 
0066     return s_packageTrader;
0067 }
0068 
0069 Package PackageLoader::loadPackage(const QString &packageFormat, const QString &packagePath)
0070 {
0071 #if KPACKAGE_BUILD_DEPRECATED_SINCE(5, 86)
0072     if (!d->isDefaultLoader) {
0073         Package p = internalLoadPackage(packageFormat);
0074         if (p.hasValidStructure()) {
0075             if (!packagePath.isEmpty()) {
0076                 p.setPath(packagePath);
0077             }
0078             return p;
0079         }
0080     }
0081 #endif
0082 
0083     if (packageFormat.isEmpty()) {
0084         return Package();
0085     }
0086 
0087     PackageStructure *structure = loadPackageStructure(packageFormat);
0088 
0089     if (structure) {
0090         Package p(structure);
0091         if (!packagePath.isEmpty()) {
0092             p.setPath(packagePath);
0093         }
0094         return p;
0095     }
0096 
0097 #ifndef NDEBUG
0098     // qCDebug(KPACKAGE_LOG) << "Couldn't load Package for" << packageFormat << "! reason given: " << error;
0099 #endif
0100 
0101     return Package();
0102 }
0103 
0104 QList<KPluginMetaData> PackageLoader::listPackages(const QString &packageFormat, const QString &packageRoot)
0105 {
0106     // Note: Use QDateTime::currentSecsSinceEpoch() once we can depend on Qt 5.8
0107     const qint64 now = qRound64(QDateTime::currentMSecsSinceEpoch() / 1000.0);
0108     bool useRuntimeCache = true;
0109     if (now - d->pluginCacheAge > d->maxCacheAge && d->pluginCacheAge != 0) {
0110         // cache is old and we're not within a few seconds of startup anymore
0111         useRuntimeCache = false;
0112         d->pluginCache.clear();
0113     }
0114 
0115     const QString cacheKey = QStringLiteral("%1.%2").arg(packageFormat, packageRoot);
0116     if (useRuntimeCache) {
0117         auto it = d->pluginCache.constFind(cacheKey);
0118         if (it != d->pluginCache.constEnd()) {
0119             return *it;
0120         }
0121     }
0122     if (d->pluginCacheAge == 0) {
0123         d->pluginCacheAge = now;
0124     }
0125 
0126     QList<KPluginMetaData> lst;
0127 
0128     // has been a root specified?
0129     QString actualRoot = packageRoot;
0130 
0131     // try to take it from the package structure
0132     if (actualRoot.isEmpty()) {
0133         PackageStructure *structure = d->structures.value(packageFormat).data();
0134         if (!structure) {
0135             if (packageFormat == QStringLiteral("KPackage/Generic")) {
0136                 structure = new GenericPackage();
0137             } else if (packageFormat == QStringLiteral("KPackage/GenericQML")) {
0138                 structure = new GenericQMLPackage();
0139             }
0140         }
0141 
0142         if (!structure) {
0143             structure = loadPackageStructure(packageFormat);
0144         }
0145 
0146         if (structure) {
0147             d->structures.insert(packageFormat, structure);
0148             Package p(structure);
0149             actualRoot = p.defaultPackageRoot();
0150         }
0151     }
0152 
0153     if (actualRoot.isEmpty()) {
0154         actualRoot = packageFormat;
0155     }
0156 
0157     QSet<QString> uniqueIds;
0158     QStringList paths;
0159     if (QDir::isAbsolutePath(actualRoot)) {
0160         paths = QStringList(actualRoot);
0161     } else {
0162         const auto listPath = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation);
0163         for (const QString &path : listPath) {
0164             paths += path + QLatin1Char('/') + actualRoot;
0165         }
0166     }
0167 
0168     for (auto const &plugindir : std::as_const(paths)) {
0169         const QDirIterator::IteratorFlags flags = QDirIterator::Subdirectories;
0170         const QStringList nameFilters = {QStringLiteral("metadata.json"), QStringLiteral("metadata.desktop")};
0171 
0172         QDirIterator it(plugindir, nameFilters, QDir::Files, flags);
0173         QSet<QString> dirs;
0174         while (it.hasNext()) {
0175             it.next();
0176 
0177             const QString dir = it.fileInfo().absoluteDir().path();
0178 
0179             if (dirs.contains(dir)) {
0180                 continue;
0181             }
0182             dirs << dir;
0183 
0184             const QString metadataPath = it.fileInfo().absoluteFilePath();
0185             KPluginMetaData info;
0186 #if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 92)
0187             if (metadataPath.endsWith(QLatin1String(".desktop"))) {
0188                 info = KPluginMetaData::fromDesktopFile(metadataPath);
0189             } else {
0190                 info = KPluginMetaData::fromJsonFile(metadataPath);
0191             }
0192 #else
0193             info = KPluginMetaData::fromJsonFile(metadataPath);
0194 #endif
0195 
0196             if (!info.isValid() || uniqueIds.contains(info.pluginId())) {
0197                 continue;
0198             }
0199 
0200             const QStringList kpackageTypes = readKPackageTypes(info);
0201             if (packageFormat.isEmpty() || kpackageTypes.isEmpty() || kpackageTypes.contains(packageFormat)) {
0202                 uniqueIds << info.pluginId();
0203                 lst << info;
0204             }
0205         }
0206     }
0207 
0208     if (useRuntimeCache) {
0209         d->pluginCache.insert(cacheKey, lst);
0210     }
0211     return lst;
0212 }
0213 
0214 QList<KPluginMetaData>
0215 PackageLoader::findPackages(const QString &packageFormat, const QString &packageRoot, std::function<bool(const KPluginMetaData &)> filter)
0216 {
0217     QList<KPluginMetaData> lst;
0218     const auto lstPlugins = listPackages(packageFormat, packageRoot);
0219     for (auto const &plugin : lstPlugins) {
0220         if (!filter || filter(plugin)) {
0221             lst << plugin;
0222         }
0223     }
0224     return lst;
0225 }
0226 
0227 KPackage::PackageStructure *PackageLoader::loadPackageStructure(const QString &packageFormat)
0228 {
0229     PackageStructure *structure = d->structures.value(packageFormat).data();
0230     if (!structure) {
0231         if (packageFormat == QStringLiteral("KPackage/Generic")) {
0232             structure = new GenericPackage();
0233             d->structures.insert(packageFormat, structure);
0234         } else if (packageFormat == QStringLiteral("KPackage/GenericQML")) {
0235             structure = new GenericQMLPackage();
0236             d->structures.insert(packageFormat, structure);
0237         }
0238     }
0239 
0240     if (structure) {
0241         return structure;
0242     }
0243 
0244     const KPluginMetaData metaData = structureForKPackageType(packageFormat);
0245 
0246     QString error;
0247     if (!metaData.isValid()) {
0248         qCWarning(KPACKAGE_LOG) << "Invalid metadata for package structure" << packageFormat;
0249         return nullptr;
0250     }
0251 
0252     auto result = KPluginFactory::instantiatePlugin<PackageStructure>(metaData, nullptr, {metaData.rawData().toVariantMap()});
0253 
0254     if (!result) {
0255         qCWarning(KPACKAGE_LOG) << i18n("Could not load installer for package of type %1. Error reported was: %2", packageFormat, result.errorString);
0256         return nullptr;
0257     }
0258 
0259     structure = result.plugin;
0260 
0261     d->structures.insert(packageFormat, structure);
0262 
0263     return structure;
0264 }
0265 
0266 void PackageLoader::addKnownPackageStructure(const QString &packageFormat, KPackage::PackageStructure *structure)
0267 {
0268     d->structures.insert(packageFormat, structure);
0269 }
0270 
0271 #if KPACKAGE_BUILD_DEPRECATED_SINCE(5, 86)
0272 Package PackageLoader::internalLoadPackage(const QString &name)
0273 {
0274     Q_UNUSED(name);
0275     return Package();
0276 }
0277 #endif
0278 
0279 } // KPackage Namespace