KPackage

packageloader.cpp
1 /*
2  SPDX-FileCopyrightText: 2010 Ryan Rix <[email protected]>
3 
4  SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 #include "packageloader.h"
8 #include "private/packageloader_p.h"
9 
10 #include <QStandardPaths>
11 #include <QDateTime>
12 #include <QDirIterator>
13 #include <QJsonDocument>
14 #include <QJsonArray>
15 #include <QCoreApplication>
16 #include "kpackage_debug.h"
17 
18 #include <KCompressionDevice>
19 #include <KLocalizedString>
20 #include <KPluginLoader>
21 #include <KPluginFactory>
22 
23 #include "config-package.h"
24 
25 #include "private/packages_p.h"
26 #include "package.h"
27 #include "packagestructure.h"
28 #include "private/packagejobthread_p.h"
29 
30 namespace KPackage
31 {
32 
33 static PackageLoader *s_packageTrader = nullptr;
34 
35 
36 QSet<QString> PackageLoaderPrivate::s_customCategories;
37 
38 QSet<QString> PackageLoaderPrivate::knownCategories()
39 {
40  // this is to trick the translation tools into making the correct
41  // strings for translation
42  QSet<QString> categories = s_customCategories;
43  categories << QStringLiteral(I18N_NOOP("Accessibility")).toLower()
44  << QStringLiteral(I18N_NOOP("Application Launchers")).toLower()
45  << QStringLiteral(I18N_NOOP("Astronomy")).toLower()
46  << QStringLiteral(I18N_NOOP("Date and Time")).toLower()
47  << QStringLiteral(I18N_NOOP("Development Tools")).toLower()
48  << QStringLiteral(I18N_NOOP("Education")).toLower()
49  << QStringLiteral(I18N_NOOP("Environment and Weather")).toLower()
50  << QStringLiteral(I18N_NOOP("Examples")).toLower()
51  << QStringLiteral(I18N_NOOP("File System")).toLower()
52  << QStringLiteral(I18N_NOOP("Fun and Games")).toLower()
53  << QStringLiteral(I18N_NOOP("Graphics")).toLower()
54  << QStringLiteral(I18N_NOOP("Language")).toLower()
55  << QStringLiteral(I18N_NOOP("Mapping")).toLower()
56  << QStringLiteral(I18N_NOOP("Miscellaneous")).toLower()
57  << QStringLiteral(I18N_NOOP("Multimedia")).toLower()
58  << QStringLiteral(I18N_NOOP("Online Services")).toLower()
59  << QStringLiteral(I18N_NOOP("Productivity")).toLower()
60  << QStringLiteral(I18N_NOOP("System Information")).toLower()
61  << QStringLiteral(I18N_NOOP("Utilities")).toLower()
62  << QStringLiteral(I18N_NOOP("Windows and Tasks")).toLower();
63  return categories;
64 }
65 
66 QString PackageLoaderPrivate::parentAppConstraint(const QString &parentApp)
67 {
68  if (parentApp.isEmpty()) {
70  if (!app) {
71  return QString();
72  }
73 
74  return QStringLiteral("((not exist [X-KDE-ParentApp] or [X-KDE-ParentApp] == '') or [X-KDE-ParentApp] == '%1')")
75  .arg(app->applicationName());
76  }
77 
78  return QStringLiteral("[X-KDE-ParentApp] == '%1'").arg(parentApp);
79 }
80 
81 PackageLoader::PackageLoader()
82  : d(new PackageLoaderPrivate)
83 {
84 }
85 
86 PackageLoader::~PackageLoader()
87 {
88  for (auto wp : qAsConst(d->structures)) {
89  delete wp.data();
90  }
91  delete d;
92 }
93 
95 {
96  if (!s_packageTrader) {
97  s_packageTrader = loader;
98  } else {
99 #ifndef NDEBUG
100  // qCDebug(KPACKAGE_LOG) << "Cannot set packageTrader, already set!" << s_packageTrader;
101 #endif
102  }
103 }
104 
106 {
107  if (!s_packageTrader) {
108  // we have been called before any PackageLoader was set, so just use the default
109  // implementation. this prevents plugins from nefariously injecting their own
110  // plugin loader if the app doesn't
111  s_packageTrader = new PackageLoader;
112  s_packageTrader->d->isDefaultLoader = true;
113  }
114 
115  return s_packageTrader;
116 }
117 
118 Package PackageLoader::loadPackage(const QString &packageFormat, const QString &packagePath)
119 {
120  if (!d->isDefaultLoader) {
121  Package p = internalLoadPackage(packageFormat);
122  if (p.hasValidStructure()) {
123  if (!packagePath.isEmpty()) {
124  p.setPath(packagePath);
125  }
126  return p;
127  }
128  }
129 
130  if (packageFormat.isEmpty()) {
131  return Package();
132  }
133 
134  PackageStructure *structure = loadPackageStructure(packageFormat);
135 
136  if (structure) {
137  Package p(structure);
138  if (!packagePath.isEmpty()) {
139  p.setPath(packagePath);
140  }
141  return p;
142  }
143 
144 #ifndef NDEBUG
145  // qCDebug(KPACKAGE_LOG) << "Couldn't load Package for" << packageFormat << "! reason given: " << error;
146 #endif
147 
148  return Package();
149 }
150 
151 QList<KPluginMetaData> PackageLoader::listPackages(const QString &packageFormat, const QString &packageRoot)
152 {
153  // Note: Use QDateTime::currentSecsSinceEpoch() once we can depend on Qt 5.8
154  const qint64 now = qRound64(QDateTime::currentMSecsSinceEpoch() / 1000.0);
155  bool useRuntimeCache = true;
156  if (now - d->pluginCacheAge > d->maxCacheAge && d->pluginCacheAge != 0) {
157  // cache is old and we're not within a few seconds of startup anymore
158  useRuntimeCache = false;
159  d->pluginCache.clear();
160  }
161 
162  const QString cacheKey = QStringLiteral("%1.%2").arg(packageFormat, packageRoot);
163  if (useRuntimeCache) {
164  auto it = d->pluginCache.constFind(cacheKey);
165  if (it != d->pluginCache.constEnd()) {
166  return *it;
167  }
168  }
169  if (d->pluginCacheAge == 0) {
170  d->pluginCacheAge = now;
171  }
172 
174 
175  //has been a root specified?
176  QString actualRoot = packageRoot;
177 
178  //try to take it from the package structure
179  if (actualRoot.isEmpty()) {
180  PackageStructure *structure = d->structures.value(packageFormat).data();
181  if (!structure) {
182  if (packageFormat == QStringLiteral("KPackage/Generic")) {
183  structure = new GenericPackage();
184  } else if (packageFormat == QStringLiteral("KPackage/GenericQML")) {
185  structure = new GenericQMLPackage();
186  }
187  }
188 
189  if (!structure) {
190  structure = loadPackageStructure(packageFormat);
191  }
192 
193  if (structure) {
194  d->structures.insert(packageFormat, structure);
195  Package p(structure);
196  actualRoot = p.defaultPackageRoot();
197 
198  }
199  }
200 
201  if (actualRoot.isEmpty()) {
202  actualRoot = packageFormat;
203  }
204 
205  QSet<QString> uniqueIds;
206  QStringList paths;
207  if (QDir::isAbsolutePath(actualRoot)) {
208  paths = QStringList(actualRoot);
209  } else {
211  for (const QString &path : listPath) {
212  paths += path + QLatin1Char('/') + actualRoot;
213  }
214  }
215 
216  for (auto const &plugindir : qAsConst(paths)) {
217  const QString &_ixfile = plugindir + s_kpluginindex;
218  if (QFile::exists(_ixfile)) {
219  KCompressionDevice indexFile(_ixfile, KCompressionDevice::BZip2);
220  qCDebug(KPACKAGE_LOG) << "kpluginindex: Using indexfile: " << _ixfile;
221  indexFile.open(QIODevice::ReadOnly);
223  indexFile.close();
224 
225  QJsonArray plugins = jdoc.array();
226  for (QJsonArray::const_iterator iter = plugins.constBegin(); iter != plugins.constEnd(); ++iter) {
227  const QJsonObject &obj = QJsonValue(*iter).toObject();
228  const QString &pluginFileName = obj.value(QStringLiteral("FileName")).toString();
229  const KPluginMetaData m(obj, QString(), pluginFileName);
230  if (m.isValid() && !uniqueIds.contains(m.pluginId())) {
231  uniqueIds << m.pluginId();
232  lst << m;
233  }
234  }
235  } else {
236  qCDebug(KPACKAGE_LOG) << "kpluginindex: Not cached" << plugindir;
237  // If there's no cache file, fall back to listing the directory
239  const QStringList nameFilters = { QStringLiteral("metadata.json"), QStringLiteral("metadata.desktop") };
240 
241  QDirIterator it(plugindir, nameFilters, QDir::Files, flags);
242  QSet<QString> dirs;
243  while (it.hasNext()) {
244  it.next();
245 
246  const QString dir = it.fileInfo().absoluteDir().path();
247 
248  if (dirs.contains(dir)) {
249  continue;
250  }
251  dirs << dir;
252 
253  const QString metadataPath = it.fileInfo().absoluteFilePath();
254  const KPluginMetaData info(metadataPath);
255 
256  if (!info.isValid() || uniqueIds.contains(info.pluginId())) {
257  continue;
258  }
259 
260  if (packageFormat.isEmpty() || info.serviceTypes().isEmpty() || info.serviceTypes().contains(packageFormat)) {
261  uniqueIds << info.pluginId();
262  lst << info;
263  }
264  }
265  }
266  }
267 
268  if (useRuntimeCache) {
269  d->pluginCache.insert(cacheKey, lst);
270  }
271  return lst;
272 }
273 
274 QList<KPluginMetaData> PackageLoader::findPackages(const QString &packageFormat, const QString &packageRoot, std::function<bool(const KPluginMetaData &)> filter)
275 {
277  const auto lstPlugins = listPackages(packageFormat, packageRoot);
278  for (auto const &plugin : lstPlugins) {
279  if (!filter || filter(plugin)) {
280  lst << plugin;
281  }
282  }
283  return lst;
284 }
285 
287 {
288  PackageStructure *structure = d->structures.value(packageFormat).data();
289  if (!structure) {
290  if (packageFormat == QStringLiteral("KPackage/Generic")) {
291  structure = new GenericPackage();
292  d->structures.insert(packageFormat, structure);
293  } else if (packageFormat == QStringLiteral("KPackage/GenericQML")) {
294  structure = new GenericQMLPackage();
295  d->structures.insert(packageFormat, structure);
296  }
297  }
298 
299  if (structure) {
300  return structure;
301  }
302 
303  QStringList libraryPaths;
304 
305  const QString subDirectory = QStringLiteral("kpackage/packagestructure");
306  const auto lstPaths = QCoreApplication::libraryPaths();
307 
308  for (const QString &dir : lstPaths) {
309  QString d = dir + QDir::separator() + subDirectory;
310  if (!d.endsWith(QDir::separator())) {
311  d += QDir::separator();
312  }
313  libraryPaths << d;
314  }
315 
316  QString pluginFileName;
317 
318  for (const QString &plugindir : qAsConst(libraryPaths)) {
319  const QString &_ixfile = plugindir + s_kpluginindex;
320  KCompressionDevice indexFile(_ixfile, KCompressionDevice::BZip2);
321  if (QFile::exists(_ixfile)) {
322  indexFile.open(QIODevice::ReadOnly);
324  indexFile.close();
325  QJsonArray plugins = jdoc.array();
326  for (QJsonArray::const_iterator iter = plugins.constBegin(); iter != plugins.constEnd(); ++iter) {
327  const QJsonObject &obj = QJsonValue(*iter).toObject();
328  const QString &candidate = obj.value(QStringLiteral("FileName")).toString();
329  const KPluginMetaData m(obj, candidate);
330  if (m.isValid() && m.pluginId() == packageFormat) {
331  pluginFileName = candidate;
332  break;
333  }
334  }
335  } else {
337  QVectorIterator<KPluginMetaData> iter(plugins);
338  while (iter.hasNext()) {
339  auto md = iter.next();
340  if (md.isValid() && md.pluginId() == packageFormat) {
341  pluginFileName = md.fileName();
342  break;
343  }
344  }
345  }
346  }
347 
348  QString error;
349  if (!pluginFileName.isEmpty()) {
350  KPluginLoader loader(pluginFileName);
351  const QVariantList argsWithMetaData = QVariantList() << loader.metaData().toVariantMap();
352  KPluginFactory *factory = loader.factory();
353  if (factory) {
354  structure = factory->create<PackageStructure>(nullptr, argsWithMetaData);
355  if (!structure) {
356  error = QCoreApplication::translate("", "No service matching the requirements was found");
357  }
358  }
359  }
360 
361  if (structure && !error.isEmpty()) {
362  qCWarning(KPACKAGE_LOG) << i18n("Could not load installer for package of type %1. Error reported was: %2",
363  packageFormat, error);
364  }
365 
366  if (structure) {
367  d->structures.insert(packageFormat, structure);
368  }
369 
370  return structure;
371 }
372 
374 {
375  d->structures.insert(packageFormat, structure);
376 }
377 
379 {
380  Q_UNUSED(name);
381  return Package();
382 }
383 
384 } // KPackage Namespace
385 
QString next()
QStringList serviceTypes() const
virtual Package internalLoadPackage(const QString &packageFormat)
A re-implementable method that allows subclasses to override the default behaviour of loadPackage...
QJsonArray::const_iterator constEnd() const const
QFileInfo fileInfo() const const
QJsonArray array() const const
QJsonDocument fromBinaryData(const QByteArray &data, QJsonDocument::DataValidation validation)
T * create(QObject *parent=nullptr, const QVariantList &args=QVariantList())
const QString path() const
Definition: package.cpp:587
QVariantMap toVariantMap() const const
QString translate(const char *context, const char *sourceText, const char *disambiguation, int n)
bool contains(const QString &str, Qt::CaseSensitivity cs) const const
QString defaultPackageRoot() const
Definition: package.cpp:156
bool exists() const const
QJsonArray::const_iterator constBegin() const const
QList< KPluginMetaData > findPackages(const QString &packageFormat, const QString &packageRoot=QString(), std::function< bool(const KPluginMetaData &)> filter=std::function< bool(const KPluginMetaData &)>())
List package of a certain type that match a certain filter function.
QChar separator()
bool hasValidStructure() const
Definition: package.cpp:72
QStringList standardLocations(QStandardPaths::StandardLocation type)
static PackageLoader * self()
Return the active plugin loader.
This is an abstract base class which defines an interface to which the package loading logic can comm...
Definition: packageloader.h:31
qint64 currentMSecsSinceEpoch()
QString toString() const const
QString path() const const
This class is used to define the filesystem structure of a package type.
QJsonObject toObject() const const
void setPath(const QString &path)
Sets the path to the root of this package.
Definition: package.cpp:463
bool isEmpty() const const
QString absoluteFilePath() const const
bool isEmpty() const const
QJsonObject metaData() const
static void setPackageLoader(PackageLoader *loader)
Set the plugin loader which will be queried for all loads.
#define I18N_NOOP(text)
QByteArray readAll()
QDir absoluteDir() const const
bool endsWith(const QString &s, Qt::CaseSensitivity cs) const const
QCoreApplication * instance()
bool hasNext() const const
QList< KPluginMetaData > listPackages(const QString &packageFormat, const QString &packageRoot=QString())
List all available packages of a certain type.
KPackage::PackageStructure * loadPackageStructure(const QString &packageFormat)
Loads a PackageStructure for a given format.
Package(PackageStructure *structure=nullptr)
Default constructor.
Definition: package.cpp:37
object representing an installed package
Definition: package.h:67
Namespace for everything in kpackage.
Definition: package.cpp:34
QStringList libraryPaths()
bool contains(const T &value) const const
KPluginFactory * factory()
QString i18n(const char *text, const TYPE &arg...)
void close() override
bool isAbsolutePath(const QString &path)
void addKnownPackageStructure(const QString &packageFormat, KPackage::PackageStructure *structure)
Adds a new known package structure that can be used by the functions to load packages such as loadPac...
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
Package loadPackage(const QString &packageFormat, const QString &packagePath=QString())
Load a Package plugin.
typedef IteratorFlags
QString name(const QByteArray &key) const
Definition: package.cpp:122
bool open(QIODevice::OpenMode mode) override
QJsonValue value(const QString &key) const const
static QVector< KPluginMetaData > findPlugins(const QString &directory, std::function< bool(const KPluginMetaData &)> filter=std::function< bool(const KPluginMetaData &)>())
bool hasNext() const const
const T & next()
bool isValid() const
QString pluginId() const
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Tue Oct 20 2020 22:57:15 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.