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