File indexing completed on 2024-09-08 03:40:00
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