KPackage

packageloader.cpp
1/*
2 SPDX-FileCopyrightText: 2010 Ryan Rix <ry@n.rix.si>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "packageloader.h"
8#include "private/packageloader_p.h"
9#include "private/utils.h"
10
11#include "kpackage_debug.h"
12#include <QCoreApplication>
13#include <QDateTime>
14#include <QDirIterator>
15#include <QList>
16#include <QStandardPaths>
17
18#include <KLazyLocalizedString>
19#include <KPluginFactory>
20#include <KPluginMetaData>
21#include <unordered_set>
22
23#include "config-package.h"
24
25#include "package.h"
26#include "packagestructure.h"
27#include "private/packagejobthread_p.h"
28#include "private/packages_p.h"
29
30namespace KPackage
31{
32PackageLoader::PackageLoader()
33 : d(new PackageLoaderPrivate)
34{
35}
36
37PackageLoader::~PackageLoader()
38{
39 for (auto wp : std::as_const(d->structures)) {
40 delete wp.data();
41 }
42 delete d;
43}
44
45PackageLoader *PackageLoader::self()
46{
47 static PackageLoader *s_packageTrader = new PackageLoader;
48 return s_packageTrader;
49}
50
51Package PackageLoader::loadPackage(const QString &packageFormat, const QString &packagePath)
52{
53 if (packageFormat.isEmpty()) {
54 return Package();
55 }
56
57 if (PackageStructure *structure = loadPackageStructure(packageFormat)) {
58 Package p(structure);
59 if (!packagePath.isEmpty()) {
60 p.setPath(packagePath);
61 }
62 return p;
63 }
64
65 return Package();
66}
67
68QList<Package> PackageLoader::listKPackages(const QString &packageFormat, const QString &packageRoot)
69{
71
72 // has been a root specified?
73 QString actualRoot = packageRoot;
74
75 PackageStructure *structure = d->structures.value(packageFormat).data();
76 // try to take it from the package structure
77 if (actualRoot.isEmpty()) {
78 if (!structure) {
79 if (packageFormat == QLatin1String("KPackage/Generic")) {
80 structure = new GenericPackage();
81 } else if (packageFormat == QLatin1String("KPackage/GenericQML")) {
82 structure = new GenericQMLPackage();
83 } else {
84 structure = loadPackageStructure(packageFormat);
85 }
86 }
87
88 if (structure) {
89 d->structures.insert(packageFormat, structure);
90 actualRoot = Package(structure).defaultPackageRoot();
91 }
92 }
93
94 if (actualRoot.isEmpty()) {
95 actualRoot = packageFormat;
96 }
97
98 QStringList paths;
99 if (QDir::isAbsolutePath(actualRoot)) {
100 paths = QStringList(actualRoot);
101 } else {
103 for (const QString &path : listPath) {
104 paths += path + QLatin1Char('/') + actualRoot;
105 }
106 }
107
108 for (auto const &plugindir : std::as_const(paths)) {
110 std::unordered_set<QString> dirs;
111 while (it.hasNext()) {
112 it.next();
113
114 const QString dir = it.filePath();
115 if (!dirs.insert(it.fileInfo().fileName()).second) {
116 continue;
117 }
118 Package package(structure);
119 package.setPath(dir);
120 if (package.isValid()) {
121 // Ignore packages with empty metadata here
122 if (packageFormat.isEmpty() || !package.metadata().isValid() || readKPackageType(package.metadata()) == packageFormat) {
123 lst << package;
124 } else {
125 qInfo() << "KPackage in" << package.path() << readKPackageType(package.metadata()) << "does not match requested format" << packageFormat;
126 }
127 }
128 }
129 }
130 return lst;
131}
132QList<KPluginMetaData> PackageLoader::listPackages(const QString &packageFormat, const QString &packageRoot)
133{
134 // Note: Use QDateTime::currentSecsSinceEpoch() once we can depend on Qt 5.8
135 const qint64 now = qRound64(QDateTime::currentMSecsSinceEpoch() / 1000.0);
136 bool useRuntimeCache = true;
137 if (now - d->pluginCacheAge > d->maxCacheAge && d->pluginCacheAge != 0) {
138 // cache is old and we're not within a few seconds of startup anymore
139 useRuntimeCache = false;
140 d->pluginCache.clear();
141 }
142
143 const QString cacheKey = packageFormat + QLatin1Char('.') + packageRoot;
144 if (useRuntimeCache) {
145 auto it = d->pluginCache.constFind(cacheKey);
146 if (it != d->pluginCache.constEnd()) {
147 return *it;
148 }
149 }
150 if (d->pluginCacheAge == 0) {
151 d->pluginCacheAge = now;
152 }
153
155
156 // has been a root specified?
157 QString actualRoot = packageRoot;
158
159 // try to take it from the package structure
160 if (actualRoot.isEmpty()) {
161 PackageStructure *structure = d->structures.value(packageFormat).data();
162 if (!structure) {
163 if (packageFormat == QLatin1String("KPackage/Generic")) {
164 structure = new GenericPackage();
165 } else if (packageFormat == QLatin1String("KPackage/GenericQML")) {
166 structure = new GenericQMLPackage();
167 } else {
168 structure = loadPackageStructure(packageFormat);
169 }
170 }
171
172 if (structure) {
173 d->structures.insert(packageFormat, structure);
174 Package p(structure);
175 actualRoot = p.defaultPackageRoot();
176 }
177 }
178
179 if (actualRoot.isEmpty()) {
180 actualRoot = packageFormat;
181 }
182
183 QSet<QString> uniqueIds;
184 QStringList paths;
185 if (QDir::isAbsolutePath(actualRoot)) {
186 paths = QStringList(actualRoot);
187 } else {
189 for (const QString &path : listPath) {
190 paths += path + QLatin1Char('/') + actualRoot;
191 }
192 }
193
194 for (auto const &plugindir : std::as_const(paths)) {
195 QDirIterator it(plugindir, QStringList{QStringLiteral("metadata.json")}, QDir::Files, QDirIterator::Subdirectories);
196 std::unordered_set<QString> dirs;
197 while (it.hasNext()) {
198 it.next();
199
200 const QString dir = it.fileInfo().absoluteDir().path();
201 if (!dirs.insert(dir).second) {
202 continue;
203 }
204
205 const QString metadataPath = it.fileInfo().absoluteFilePath();
207
208 if (!info.isValid() || uniqueIds.contains(info.pluginId())) {
209 continue;
210 }
211
212 if (packageFormat.isEmpty() || readKPackageType(info) == packageFormat) {
213 uniqueIds << info.pluginId();
214 lst << info;
215 } else {
216 qInfo() << "KPackageStructure of" << info << "does not match requested format" << packageFormat;
217 }
218 }
219 }
220
221 if (useRuntimeCache) {
222 d->pluginCache.insert(cacheKey, lst);
223 }
224 return lst;
225}
226
227QList<KPluginMetaData> PackageLoader::listPackagesMetadata(const QString &packageFormat, const QString &packageRoot)
228{
229 return listPackages(packageFormat, packageRoot);
230}
231
233PackageLoader::findPackages(const QString &packageFormat, const QString &packageRoot, std::function<bool(const KPluginMetaData &)> filter)
234{
236 const auto lstPlugins = listPackages(packageFormat, packageRoot);
237 for (auto const &plugin : lstPlugins) {
238 if (!filter || filter(plugin)) {
239 lst << plugin;
240 }
241 }
242 return lst;
243}
244
245KPackage::PackageStructure *PackageLoader::loadPackageStructure(const QString &packageFormat)
246{
247 PackageStructure *structure = d->structures.value(packageFormat).data();
248 if (!structure) {
249 if (packageFormat == QLatin1String("KPackage/Generic")) {
250 structure = new GenericPackage();
251 d->structures.insert(packageFormat, structure);
252 } else if (packageFormat == QLatin1String("KPackage/GenericQML")) {
253 structure = new GenericQMLPackage();
254 d->structures.insert(packageFormat, structure);
255 }
256 }
257
258 if (structure) {
259 return structure;
260 }
261
262 const KPluginMetaData metaData = structureForKPackageType(packageFormat);
263 if (!metaData.isValid()) {
264 qCWarning(KPACKAGE_LOG) << "Invalid metadata for package structure" << packageFormat;
265 return nullptr;
266 }
267
268 auto result = KPluginFactory::instantiatePlugin<PackageStructure>(metaData);
269 if (!result) {
270 qCWarning(KPACKAGE_LOG).noquote() << "Could not load installer for package of type" << packageFormat << "Error reported was: " << result.errorString;
271 return nullptr;
272 }
273
274 structure = result.plugin;
275
276 d->structures.insert(packageFormat, structure);
277
278 return structure;
279}
280
281void PackageLoader::addKnownPackageStructure(const QString &packageFormat, KPackage::PackageStructure *structure)
282{
283 d->structures.insert(packageFormat, structure);
284}
285
286void PackageLoader::invalidateCache()
287{
288 self()->d->maxCacheAge = -1;
289}
290
291} // KPackage Namespace
This is an abstract base class which defines an interface to which the package loading logic can comm...
This class is used to define the filesystem structure of a package type.
object representing an installed package
Definition package.h:63
void setPath(const QString &path)
Sets the path to the root of this package.
Definition package.cpp:439
QString defaultPackageRoot() const
Definition package.cpp:128
bool isValid() const
Definition package.cpp:66
const QString path() const
Definition package.cpp:558
KPluginMetaData metadata() const
Definition package.cpp:179
QString pluginId() const
bool isValid() const
static KPluginMetaData fromJsonFile(const QString &jsonFile)
qint64 currentMSecsSinceEpoch()
bool isAbsolutePath(const QString &path)
QFileInfo fileInfo() const const
QString filePath() const const
bool hasNext() const const
QString next()
QString fileName() const const
bool contains(const QSet< T > &other) const const
QStringList standardLocations(StandardLocation type)
QString & insert(qsizetype position, QChar ch)
bool isEmpty() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:16:48 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.