File indexing completed on 2024-04-14 03:54:16

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 <QList>
0016 #include <QStandardPaths>
0017 
0018 #include <KLazyLocalizedString>
0019 #include <KPluginFactory>
0020 #include <KPluginMetaData>
0021 #include <unordered_set>
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 PackageLoader::PackageLoader()
0033     : d(new PackageLoaderPrivate)
0034 {
0035 }
0036 
0037 PackageLoader::~PackageLoader()
0038 {
0039     for (auto wp : std::as_const(d->structures)) {
0040         delete wp.data();
0041     }
0042     delete d;
0043 }
0044 
0045 PackageLoader *PackageLoader::self()
0046 {
0047     static PackageLoader *s_packageTrader = new PackageLoader;
0048     return s_packageTrader;
0049 }
0050 
0051 Package PackageLoader::loadPackage(const QString &packageFormat, const QString &packagePath)
0052 {
0053     if (packageFormat.isEmpty()) {
0054         return Package();
0055     }
0056 
0057     if (PackageStructure *structure = loadPackageStructure(packageFormat)) {
0058         Package p(structure);
0059         if (!packagePath.isEmpty()) {
0060             p.setPath(packagePath);
0061         }
0062         return p;
0063     }
0064 
0065     return Package();
0066 }
0067 
0068 QList<Package> PackageLoader::listKPackages(const QString &packageFormat, const QString &packageRoot)
0069 {
0070     QList<Package> lst;
0071 
0072     // has been a root specified?
0073     QString actualRoot = packageRoot;
0074 
0075     PackageStructure *structure = d->structures.value(packageFormat).data();
0076     // try to take it from the package structure
0077     if (actualRoot.isEmpty()) {
0078         if (!structure) {
0079             if (packageFormat == QLatin1String("KPackage/Generic")) {
0080                 structure = new GenericPackage();
0081             } else if (packageFormat == QLatin1String("KPackage/GenericQML")) {
0082                 structure = new GenericQMLPackage();
0083             } else {
0084                 structure = loadPackageStructure(packageFormat);
0085             }
0086         }
0087 
0088         if (structure) {
0089             d->structures.insert(packageFormat, structure);
0090             actualRoot = Package(structure).defaultPackageRoot();
0091         }
0092     }
0093 
0094     if (actualRoot.isEmpty()) {
0095         actualRoot = packageFormat;
0096     }
0097 
0098     QStringList paths;
0099     if (QDir::isAbsolutePath(actualRoot)) {
0100         paths = QStringList(actualRoot);
0101     } else {
0102         const auto listPath = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation);
0103         for (const QString &path : listPath) {
0104             paths += path + QLatin1Char('/') + actualRoot;
0105         }
0106     }
0107 
0108     for (auto const &plugindir : std::as_const(paths)) {
0109         QDirIterator it(plugindir, QDir::Dirs | QDir::NoDotAndDotDot);
0110         std::unordered_set<QString> dirs;
0111         while (it.hasNext()) {
0112             it.next();
0113 
0114             const QString dir = it.filePath();
0115             if (!dirs.insert(it.fileInfo().fileName()).second) {
0116                 continue;
0117             }
0118             Package package(structure);
0119             package.setPath(dir);
0120             if (package.isValid()) {
0121                 // Ignore packages with empty metadata here
0122                 if (packageFormat.isEmpty() || !package.metadata().isValid() || readKPackageType(package.metadata()) == packageFormat) {
0123                     lst << package;
0124                 } else {
0125                     qInfo() << "KPackage in" << package.path() << readKPackageType(package.metadata()) << "does not match requested format" << packageFormat;
0126                 }
0127             }
0128         }
0129     }
0130     return lst;
0131 }
0132 QList<KPluginMetaData> PackageLoader::listPackages(const QString &packageFormat, const QString &packageRoot)
0133 {
0134     // Note: Use QDateTime::currentSecsSinceEpoch() once we can depend on Qt 5.8
0135     const qint64 now = qRound64(QDateTime::currentMSecsSinceEpoch() / 1000.0);
0136     bool useRuntimeCache = true;
0137     if (now - d->pluginCacheAge > d->maxCacheAge && d->pluginCacheAge != 0) {
0138         // cache is old and we're not within a few seconds of startup anymore
0139         useRuntimeCache = false;
0140         d->pluginCache.clear();
0141     }
0142 
0143     const QString cacheKey = packageFormat + QLatin1Char('.') + packageRoot;
0144     if (useRuntimeCache) {
0145         auto it = d->pluginCache.constFind(cacheKey);
0146         if (it != d->pluginCache.constEnd()) {
0147             return *it;
0148         }
0149     }
0150     if (d->pluginCacheAge == 0) {
0151         d->pluginCacheAge = now;
0152     }
0153 
0154     QList<KPluginMetaData> lst;
0155 
0156     // has been a root specified?
0157     QString actualRoot = packageRoot;
0158 
0159     // try to take it from the package structure
0160     if (actualRoot.isEmpty()) {
0161         PackageStructure *structure = d->structures.value(packageFormat).data();
0162         if (!structure) {
0163             if (packageFormat == QLatin1String("KPackage/Generic")) {
0164                 structure = new GenericPackage();
0165             } else if (packageFormat == QLatin1String("KPackage/GenericQML")) {
0166                 structure = new GenericQMLPackage();
0167             } else {
0168                 structure = loadPackageStructure(packageFormat);
0169             }
0170         }
0171 
0172         if (structure) {
0173             d->structures.insert(packageFormat, structure);
0174             Package p(structure);
0175             actualRoot = p.defaultPackageRoot();
0176         }
0177     }
0178 
0179     if (actualRoot.isEmpty()) {
0180         actualRoot = packageFormat;
0181     }
0182 
0183     QSet<QString> uniqueIds;
0184     QStringList paths;
0185     if (QDir::isAbsolutePath(actualRoot)) {
0186         paths = QStringList(actualRoot);
0187     } else {
0188         const auto listPath = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation);
0189         for (const QString &path : listPath) {
0190             paths += path + QLatin1Char('/') + actualRoot;
0191         }
0192     }
0193 
0194     for (auto const &plugindir : std::as_const(paths)) {
0195         QDirIterator it(plugindir, QStringList{QStringLiteral("metadata.json")}, QDir::Files, QDirIterator::Subdirectories);
0196         std::unordered_set<QString> dirs;
0197         while (it.hasNext()) {
0198             it.next();
0199 
0200             const QString dir = it.fileInfo().absoluteDir().path();
0201             if (!dirs.insert(dir).second) {
0202                 continue;
0203             }
0204 
0205             const QString metadataPath = it.fileInfo().absoluteFilePath();
0206             KPluginMetaData info = KPluginMetaData::fromJsonFile(metadataPath);
0207 
0208             if (!info.isValid() || uniqueIds.contains(info.pluginId())) {
0209                 continue;
0210             }
0211 
0212             if (packageFormat.isEmpty() || readKPackageType(info) == packageFormat) {
0213                 uniqueIds << info.pluginId();
0214                 lst << info;
0215             } else {
0216                 qInfo() << "KPackageStructure of" << info << "does not match requested format" << packageFormat;
0217             }
0218         }
0219     }
0220 
0221     if (useRuntimeCache) {
0222         d->pluginCache.insert(cacheKey, lst);
0223     }
0224     return lst;
0225 }
0226 
0227 QList<KPluginMetaData> PackageLoader::listPackagesMetadata(const QString &packageFormat, const QString &packageRoot)
0228 {
0229     return listPackages(packageFormat, packageRoot);
0230 }
0231 
0232 QList<KPluginMetaData>
0233 PackageLoader::findPackages(const QString &packageFormat, const QString &packageRoot, std::function<bool(const KPluginMetaData &)> filter)
0234 {
0235     QList<KPluginMetaData> lst;
0236     const auto lstPlugins = listPackages(packageFormat, packageRoot);
0237     for (auto const &plugin : lstPlugins) {
0238         if (!filter || filter(plugin)) {
0239             lst << plugin;
0240         }
0241     }
0242     return lst;
0243 }
0244 
0245 KPackage::PackageStructure *PackageLoader::loadPackageStructure(const QString &packageFormat)
0246 {
0247     PackageStructure *structure = d->structures.value(packageFormat).data();
0248     if (!structure) {
0249         if (packageFormat == QLatin1String("KPackage/Generic")) {
0250             structure = new GenericPackage();
0251             d->structures.insert(packageFormat, structure);
0252         } else if (packageFormat == QLatin1String("KPackage/GenericQML")) {
0253             structure = new GenericQMLPackage();
0254             d->structures.insert(packageFormat, structure);
0255         }
0256     }
0257 
0258     if (structure) {
0259         return structure;
0260     }
0261 
0262     const KPluginMetaData metaData = structureForKPackageType(packageFormat);
0263     if (!metaData.isValid()) {
0264         qCWarning(KPACKAGE_LOG) << "Invalid metadata for package structure" << packageFormat;
0265         return nullptr;
0266     }
0267 
0268     auto result = KPluginFactory::instantiatePlugin<PackageStructure>(metaData);
0269     if (!result) {
0270         qCWarning(KPACKAGE_LOG).noquote() << "Could not load installer for package of type" << packageFormat << "Error reported was: " << result.errorString;
0271         return nullptr;
0272     }
0273 
0274     structure = result.plugin;
0275 
0276     d->structures.insert(packageFormat, structure);
0277 
0278     return structure;
0279 }
0280 
0281 void PackageLoader::addKnownPackageStructure(const QString &packageFormat, KPackage::PackageStructure *structure)
0282 {
0283     d->structures.insert(packageFormat, structure);
0284 }
0285 
0286 void PackageLoader::invalidateCache()
0287 {
0288     self()->d->maxCacheAge = -1;
0289 }
0290 
0291 } // KPackage Namespace