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