diff --git a/src/kpackage/packageloader.cpp b/src/kpackage/packageloader.cpp index 89b0bfe..abcfea9 100644 --- a/src/kpackage/packageloader.cpp +++ b/src/kpackage/packageloader.cpp @@ -1,433 +1,435 @@ /* * Copyright 2010 Ryan Rix * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "packageloader.h" #include #include #include #include #include #include #include #include #include +#include #include #include #include #include "config-package.h" #include "private/packages_p.h" #include "package.h" #include "packagestructure.h" namespace KPackage { static PackageLoader *s_packageTrader = nullptr; static const int s_maxCacheAge = 60; static QHash s_pluginCacheAge; static QHash> s_pluginCache; class PackageLoaderPrivate { public: PackageLoaderPrivate() : isDefaultLoader(false), packageStructurePluginDir(QStringLiteral("kpackage/packagestructure")) { } static QSet knownCategories(); static QString parentAppConstraint(const QString &parentApp = QString()); static QSet s_customCategories; QHash > structures; bool isDefaultLoader; QString packageStructurePluginDir; }; QSet PackageLoaderPrivate::s_customCategories; QSet PackageLoaderPrivate::knownCategories() { // this is to trick the tranlsation tools into making the correct // strings for translation QSet categories = s_customCategories; categories << QStringLiteral(I18N_NOOP("Accessibility")).toLower() << QStringLiteral(I18N_NOOP("Application Launchers")).toLower() << QStringLiteral(I18N_NOOP("Astronomy")).toLower() << QStringLiteral(I18N_NOOP("Date and Time")).toLower() << QStringLiteral(I18N_NOOP("Development Tools")).toLower() << QStringLiteral(I18N_NOOP("Education")).toLower() << QStringLiteral(I18N_NOOP("Environment and Weather")).toLower() << QStringLiteral(I18N_NOOP("Examples")).toLower() << QStringLiteral(I18N_NOOP("File System")).toLower() << QStringLiteral(I18N_NOOP("Fun and Games")).toLower() << QStringLiteral(I18N_NOOP("Graphics")).toLower() << QStringLiteral(I18N_NOOP("Language")).toLower() << QStringLiteral(I18N_NOOP("Mapping")).toLower() << QStringLiteral(I18N_NOOP("Miscellaneous")).toLower() << QStringLiteral(I18N_NOOP("Multimedia")).toLower() << QStringLiteral(I18N_NOOP("Online Services")).toLower() << QStringLiteral(I18N_NOOP("Productivity")).toLower() << QStringLiteral(I18N_NOOP("System Information")).toLower() << QStringLiteral(I18N_NOOP("Utilities")).toLower() << QStringLiteral(I18N_NOOP("Windows and Tasks")).toLower(); return categories; } QString PackageLoaderPrivate::parentAppConstraint(const QString &parentApp) { if (parentApp.isEmpty()) { const QCoreApplication *app = QCoreApplication::instance(); if (!app) { return QString(); } return QStringLiteral("((not exist [X-KDE-ParentApp] or [X-KDE-ParentApp] == '') or [X-KDE-ParentApp] == '%1')") .arg(app->applicationName()); } return QStringLiteral("[X-KDE-ParentApp] == '%1'").arg(parentApp); } PackageLoader::PackageLoader() : d(new PackageLoaderPrivate) { } PackageLoader::~PackageLoader() { foreach (auto wp, d->structures) { delete wp.data(); } delete d; } void PackageLoader::setPackageLoader(PackageLoader *loader) { if (!s_packageTrader) { s_packageTrader = loader; } else { #ifndef NDEBUG // qDebug() << "Cannot set packageTrader, already set!" << s_packageTrader; #endif } } PackageLoader *PackageLoader::self() { if (!s_packageTrader) { // we have been called before any PackageLoader was set, so just use the default // implementation. this prevents plugins from nefariously injecting their own // plugin loader if the app doesn't s_packageTrader = new PackageLoader; s_packageTrader->d->isDefaultLoader = true; } return s_packageTrader; } Package PackageLoader::loadPackage(const QString &packageFormat, const QString &packagePath) { if (!d->isDefaultLoader) { Package p = internalLoadPackage(packageFormat); if (p.hasValidStructure()) { if (!packagePath.isEmpty()) { p.setPath(packagePath); } return p; } } if (packageFormat.isEmpty()) { return Package(); } PackageStructure *structure = loadPackageStructure(packageFormat); if (structure) { Package p(structure); if (!packagePath.isEmpty()) { p.setPath(packagePath); } return p; } #ifndef NDEBUG // qDebug() << "Couldn't load Package for" << packageFormat << "! reason given: " << error; #endif return Package(); } QList PackageLoader::listPackages(const QString &packageFormat, const QString &packageRoot) { QElapsedTimer tmr; tmr.restart(); const bool useRuntimeCache = QFile::exists(QStringLiteral("/home/pine/USECACHE")); QString cacheKey = QString(QStringLiteral("%1.%2")).arg(packageFormat, packageRoot); qint64 now = QDateTime::currentSecsSinceEpoch(); if (useRuntimeCache && s_pluginCache.contains(cacheKey)) { if (now - s_pluginCacheAge.value(cacheKey) > s_maxCacheAge) { // cache is too old, ditch and compute s_pluginCache.remove(cacheKey); s_pluginCacheAge.remove(cacheKey); } else { qWarning() << "TMR CC Returning list from cache" << cacheKey << tmr.elapsed(); return s_pluginCache.value(cacheKey); } } QList lst; //has been a root specified? QString actualRoot = packageRoot; //try to take it from the package structure if (actualRoot.isEmpty()) { PackageStructure *structure = d->structures.value(packageFormat).data(); if (!structure) { if (packageFormat == QStringLiteral("KPackage/Generic")) { structure = new GenericPackage(); } else if (packageFormat == QStringLiteral("KPackage/GenericQML")) { structure = new GenericQMLPackage(); } } if (!structure) { structure = loadPackageStructure(packageFormat); } if (structure) { d->structures.insert(packageFormat, structure); Package p(structure); actualRoot = p.defaultPackageRoot(); } } if (actualRoot.isEmpty()) { actualRoot = packageFormat; } QSet uniqueIds; QStringList paths; if (QDir::isAbsolutePath(actualRoot)) { paths = QStringList(actualRoot); } else { foreach(const QString &path, QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation)) { paths += path + QLatin1Char('/') + actualRoot; } } Q_FOREACH(auto const &plugindir, paths) { const QString &_ixfile = plugindir + QStringLiteral("kpluginindex.json"); - QFile indexFile(_ixfile); - //qDebug() << "indexfile: " << _ixfile << indexFile.exists(); - if (indexFile.exists()) { - qDebug() << "kpluginindex: Using indexfile: " << _ixfile << indexFile.exists(); +// QFile indexFile(_ixfile); + KCompressionDevice indexFile(_ixfile, KCompressionDevice::None); + if (QFile::exists(_ixfile)) { + qDebug() << "kpluginindex: Using indexfile: " << _ixfile; indexFile.open(QIODevice::ReadOnly); QJsonDocument jdoc = QJsonDocument::fromBinaryData(indexFile.readAll()); indexFile.close(); QJsonArray plugins = jdoc.array(); for (QJsonArray::const_iterator iter = plugins.constBegin(); iter != plugins.constEnd(); ++iter) { const QJsonObject &obj = QJsonValue(*iter).toObject(); const QString &pluginFileName = obj.value(QStringLiteral("FileName")).toString(); const KPluginMetaData m(obj, QString(), pluginFileName); if (m.isValid() && !uniqueIds.contains(m.pluginId())) { uniqueIds << m.pluginId(); lst << m; } } } else { qDebug() << "kpluginindex: Not cached" << plugindir; // If there's no cache file, fall back to listing the directory const QDirIterator::IteratorFlags flags = QDirIterator::Subdirectories; const QStringList nameFilters = { QStringLiteral("metadata.json"), QStringLiteral("metadata.desktop") }; QDirIterator it(plugindir, nameFilters, QDir::Files, flags); QSet dirs; while (it.hasNext()) { it.next(); const QString dir = it.fileInfo().absoluteDir().path(); if (dirs.contains(dir)) { continue; } dirs << dir; const QString metadataPath = it.fileInfo().absoluteFilePath(); const KPluginMetaData info(metadataPath); if (!info.isValid() || uniqueIds.contains(info.pluginId())) { continue; } if (packageFormat.isEmpty() || info.serviceTypes().isEmpty() || info.serviceTypes().contains(packageFormat)) { uniqueIds << info.pluginId(); lst << info; } } } } qWarning() << "TMR spent" << tmr.elapsed() << "in" << packageFormat << packageRoot; if (useRuntimeCache) { s_pluginCache.insert(cacheKey, lst); s_pluginCacheAge.insert(cacheKey, now); qWarning() << "TMR CC Cache populated for " << cacheKey; } else { qWarning() << "TMR CC Not using cache" << cacheKey; } return lst; } QList PackageLoader::findPackages(const QString &packageFormat, const QString &packageRoot, std::function filter) { QList lst; Q_FOREACH(auto const &plugin, listPackages(packageFormat, packageRoot)) { if (!filter || filter(plugin)) { lst << plugin; } } return lst; } KPackage::PackageStructure *PackageLoader::loadPackageStructure(const QString &packageFormat) { QElapsedTimer tmr; tmr.restart(); PackageStructure *structure = d->structures.value(packageFormat).data(); if (!structure) { if (packageFormat == QStringLiteral("KPackage/Generic")) { structure = new GenericPackage(); d->structures.insert(packageFormat, structure); } else if (packageFormat == QStringLiteral("KPackage/GenericQML")) { structure = new GenericQMLPackage(); d->structures.insert(packageFormat, structure); } } if (structure) { return structure; } QStringList libraryPaths; const QString subDirectory = QStringLiteral("kpackage/packagestructure"); Q_FOREACH (const QString &dir, QCoreApplication::libraryPaths()) { QString d = dir + QDir::separator() + subDirectory; if (!d.endsWith(QDir::separator())) { d += QDir::separator(); } libraryPaths << d; } QString pluginFileName; Q_FOREACH (const QString &plugindir, libraryPaths) { const QString &_ixfile = plugindir + QStringLiteral("kpluginindex.json"); - QFile indexFile(_ixfile); - if (indexFile.exists()) { +// QFile indexFile(_ixfile); + KCompressionDevice indexFile(_ixfile, KCompressionDevice::BZip2); + if (QFile::exists(_ixfile)) { indexFile.open(QIODevice::ReadOnly); QJsonDocument jdoc = QJsonDocument::fromBinaryData(indexFile.readAll()); indexFile.close(); QJsonArray plugins = jdoc.array(); for (QJsonArray::const_iterator iter = plugins.constBegin(); iter != plugins.constEnd(); ++iter) { const QJsonObject &obj = QJsonValue(*iter).toObject(); const QString &candidate = obj.value(QStringLiteral("FileName")).toString(); const KPluginMetaData m(obj, candidate); if (m.isValid() && m.pluginId() == packageFormat) { pluginFileName = candidate; break; } } } else { QVector plugins = KPluginLoader::findPlugins(plugindir); QVectorIterator iter(plugins); while (iter.hasNext()) { auto md = iter.next(); if (md.isValid() && md.pluginId() == packageFormat) { pluginFileName = md.fileName(); break; } } } } QString error; if (!pluginFileName.isEmpty()) { KPluginLoader loader(pluginFileName); const QVariantList argsWithMetaData = QVariantList() << loader.metaData().toVariantMap(); KPluginFactory *factory = loader.factory(); if (factory) { structure = factory->create(nullptr, argsWithMetaData); if (!structure) { error = QCoreApplication::translate("", "No service matching the requirements was found"); } } } if (structure && !error.isEmpty()) { qWarning() << i18n("Could not load installer for package of type %1. Error reported was: %2", packageFormat, error); } if (structure) d->structures.insert(packageFormat, structure); //qDebug() << "TMR structure spent" << tmr.elapsed() << "in" << packageFormat; return structure; } void PackageLoader::addKnownPackageStructure(const QString &packageFormat, KPackage::PackageStructure *structure) { d->structures.insert(packageFormat, structure); } Package PackageLoader::internalLoadPackage(const QString &name) { Q_UNUSED(name); return Package(); } } // KPackage Namespace diff --git a/src/kpackage/private/packagejobthread.cpp b/src/kpackage/private/packagejobthread.cpp index 0cc39d7..f801f18 100644 --- a/src/kpackage/private/packagejobthread.cpp +++ b/src/kpackage/private/packagejobthread.cpp @@ -1,473 +1,477 @@ /****************************************************************************** * Copyright 2007-2009 by Aaron Seigo * * Copyright 2012 Sebastian Kügler * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Library General Public * * License as published by the Free Software Foundation; either * * version 2 of the License, or (at your option) any later version. * * * * This library is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * Library General Public License for more details. * * * * You should have received a copy of the GNU Library General Public License * * along with this library; see the file COPYING.LIB. If not, write to * * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301, USA. * *******************************************************************************/ #include "private/packagejobthread_p.h" #include "package.h" #include "config-package.h" #include #include #include #include + +#include + #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace KPackage { bool copyFolder(QString sourcePath, QString targetPath) { QDir source(sourcePath); if (!source.exists()) { return false; } QDir target(targetPath); if (!target.exists()) { QString targetName = target.dirName(); target.cdUp(); target.mkdir(targetName); target = QDir(targetPath); } foreach (const QString &fileName, source.entryList(QDir::Files)) { QString sourceFilePath = sourcePath + QDir::separator() + fileName; QString targetFilePath = targetPath + QDir::separator() + fileName; if (!QFile::copy(sourceFilePath, targetFilePath)) { return false; } } foreach (const QString &subFolderName, source.entryList(QDir::AllDirs | QDir::NoDotAndDotDot)) { QString sourceSubFolderPath = sourcePath + QDir::separator() + subFolderName; QString targetSubFolderPath = targetPath + QDir::separator() + subFolderName; if (!copyFolder(sourceSubFolderPath, targetSubFolderPath)) { return false; } } return true; } bool removeFolder(QString folderPath) { QDir folder(folderPath); return folder.removeRecursively(); } bool removeIndex(const QString& dir) { bool ok = true; QFileInfo fileInfo(dir, QStringLiteral("kpluginindex.json")); if (fileInfo.exists()) { if (fileInfo.isWritable()) { QFile f(fileInfo.absoluteFilePath()); if (!f.remove()) { ok = false; qWarning() << "Cannot remove kplugin index file: " << fileInfo.absoluteFilePath(); } else { qDebug() << "Deleted index: " << fileInfo.absoluteFilePath(); } } else { qWarning() << "Cannot remove kplugin index file (not writable): " << fileInfo.absoluteFilePath(); ok = false; } } return ok; } Q_GLOBAL_STATIC_WITH_ARGS(QStringList, metaDataFiles, (QStringList(QLatin1String("metadata.desktop")) << QLatin1String("metadata.json"))) bool indexDirectory(const QString& dir, const QString& dest) { QVariantMap vm; QVariantMap pluginsVm; vm[QStringLiteral("Version")] = QStringLiteral("1.0"); vm[QStringLiteral("Timestamp")] = QDateTime::currentMSecsSinceEpoch(); QJsonArray plugins; QDirIterator it(dir, *metaDataFiles, QDir::Files, QDirIterator::Subdirectories); while (it.hasNext()) { it.next(); const QString path = it.fileInfo().absoluteFilePath(); QJsonObject obj = KPluginMetaData(path).rawData(); obj.insert(QStringLiteral("FileName"), path); plugins.append(obj); } // Less than two plugin means it's not worth indexing if (plugins.count() < 2) { removeIndex(dir); return true; } QString destfile = dest; if (!QDir::isAbsolutePath(dest)) { destfile = dir + QLatin1Char('/') + dest; } QDir().mkpath(QFileInfo(destfile).dir().absolutePath()); - QFile file(destfile); + //QFile file(destfile); + KCompressionDevice file(destfile, KCompressionDevice::None); if (!file.open(QIODevice::WriteOnly)) { qWarning() << "Failed to open " << destfile; return false; } QJsonDocument jdoc; jdoc.setArray(plugins); // file.write(jdoc.toJson()); file.write(jdoc.toBinaryData()); qWarning() << "Generated " << destfile << " (" << plugins.count() << " plugins)"; return true; } class PackageJobThreadPrivate { public: QString installPath; QString errorMessage; int errorCode; }; PackageJobThread::PackageJobThread(QObject *parent) : QThread(parent) { d = new PackageJobThreadPrivate; d->errorCode = KJob::NoError; } PackageJobThread::~PackageJobThread() { delete d; } bool PackageJobThread::install(const QString &src, const QString &dest) { bool ok = installPackage(src, dest, Install); emit installPathChanged(d->installPath); emit finished(ok, d->errorMessage); return ok; } static QString resolveHandler(const QString &scheme) { QString candidatePath = QStringLiteral(CMAKE_INSTALL_FULL_LIBEXECDIR_KF5 "/kpackagehandlers/%1handler").arg(scheme); if (qEnvironmentVariableIsSet("KPACKAGE_DEP_RESOLVERS_PATH")) { candidatePath = QStringLiteral("%1/%2handler").arg(QString::fromUtf8(qgetenv("KPACKAGE_DEP_RESOLVERS_PATH")), scheme); } return QFile::exists(candidatePath) ? candidatePath : QString(); } bool PackageJobThread::installDependency(const QUrl &destUrl) { auto handler = resolveHandler(destUrl.scheme()); if (handler.isEmpty()) return false; QProcess process; process.setProgram(handler); process.setArguments({ destUrl.toString() }); process.setProcessChannelMode(QProcess::ForwardedChannels); process.start(); process.waitForFinished(); return process.exitCode() == 0; } bool PackageJobThread::installPackage(const QString &src, const QString &dest, OperationType operation) { QDir root(dest); if (!root.exists()) { QDir().mkpath(dest); if (!root.exists()) { d->errorMessage = i18n("Could not create package root directory: %1", dest); d->errorCode = Package::JobError::RootCreationError; //qWarning() << "Could not create package root directory: " << dest; return false; } } QFileInfo fileInfo(src); if (!fileInfo.exists()) { d->errorMessage = i18n("No such file: %1", src); d->errorCode = Package::JobError::PackageFileNotFoundError; return false; } QString path; QTemporaryDir tempdir; bool archivedPackage = false; if (fileInfo.isDir()) { // we have a directory, so let's just install what is in there path = src; // make sure we end in a slash! if (!path.endsWith(QLatin1Char('/'))) { path.append(QLatin1Char('/')); } } else { KArchive *archive = nullptr; QMimeDatabase db; QMimeType mimetype = db.mimeTypeForFile(src); if (mimetype.inherits(QStringLiteral("application/zip"))) { archive = new KZip(src); } else if (mimetype.inherits(QStringLiteral("application/x-compressed-tar")) || mimetype.inherits(QStringLiteral("application/x-tar")) || mimetype.inherits(QStringLiteral("application/x-bzip-compressed-tar")) || mimetype.inherits(QStringLiteral("application/x-xz")) || mimetype.inherits(QStringLiteral("application/x-lzma"))) { archive = new KTar(src); } else { //qWarning() << "Could not open package file, unsupported archive format:" << src << mimetype.name(); d->errorMessage = i18n("Could not open package file, unsupported archive format: %1 %2", src, mimetype.name()); d->errorCode = Package::JobError::UnsupportedArchiveFormatError; return false; } if (!archive->open(QIODevice::ReadOnly)) { //qWarning() << "Could not open package file:" << src; delete archive; d->errorMessage = i18n("Could not open package file: %1", src); d->errorCode = Package::JobError::PackageOpenError; return false; } archivedPackage = true; path = tempdir.path() + QLatin1Char('/'); d->installPath = path; const KArchiveDirectory *source = archive->directory(); source->copyTo(path); QStringList entries = source->entries(); if (entries.count() == 1) { const KArchiveEntry *entry = source->entry(entries[0]); if (entry->isDirectory()) { path = path + entry->name() + QLatin1Char('/'); } } delete archive; } QDir packageDir(path); QFileInfoList entries = packageDir.entryInfoList(*metaDataFiles); KPluginMetaData meta; if (!entries.isEmpty()) { const QString metadataFilePath = entries.first().filePath(); if (metadataFilePath.endsWith(QLatin1String(".desktop"))) { meta = KPluginMetaData::fromDesktopFile(metadataFilePath, {QStringLiteral(":/kservicetypes5/kpackage-generic.desktop")}); } else { QFile f(metadataFilePath); if(!f.open(QIODevice::ReadOnly)){ qWarning() << "Couldn't open metadata file" << src << path; d->errorMessage = i18n("Could not open metadata file: %1", src); d->errorCode = Package::JobError::MetadataFileMissingError; return false; } QJsonObject metadataObject = QJsonDocument::fromJson(f.readAll()).object(); meta = KPluginMetaData(metadataObject, QString(), metadataFilePath); } } if (!meta.isValid()) { qDebug() << "No metadata file in package" << src << path; d->errorMessage = i18n("No metadata file in package: %1", src); d->errorCode = Package::JobError::MetadataFileMissingError; return false; } QString pluginName = meta.pluginId(); qDebug() << "pluginname: " << meta.pluginId(); if (pluginName.isEmpty()) { //qWarning() << "Package plugin name not specified"; d->errorMessage = i18n("Package plugin name not specified: %1", src); d->errorCode = Package::JobError::PluginNameMissingError; return false; } // Ensure that package names are safe so package uninstall can't inject // bad characters into the paths used for removal. QRegExp validatePluginName(QStringLiteral("^[\\w-\\.]+$")); // Only allow letters, numbers, underscore and period. if (!validatePluginName.exactMatch(pluginName)) { //qDebug() << "Package plugin name " << pluginName << "contains invalid characters"; d->errorMessage = i18n("Package plugin name %1 contains invalid characters", pluginName); d->errorCode = Package::JobError::PluginNameInvalidError; return false; } QString targetName = dest; if (targetName[targetName.size() - 1] != QLatin1Char('/')) { targetName.append(QLatin1Char('/')); } targetName.append(pluginName); if (QFile::exists(targetName)) { if (operation == Update) { KPluginMetaData oldMeta(targetName + QLatin1String("/metadata.desktop")); if (oldMeta.serviceTypes() != meta.serviceTypes()) { d->errorMessage = i18n("The new package has a different type from the old version already installed.", meta.version(), meta.pluginId(), oldMeta.version()); d->errorCode = Package::JobError::UpdatePackageTypeMismatchError; } else if (isVersionNewer(oldMeta.version(), meta.version())) { const bool ok = uninstallPackage(targetName); if (!ok) { d->errorMessage = i18n("Impossible to remove the old installation of %1 located at %2. error: %3", pluginName, targetName, d->errorMessage); d->errorCode = Package::JobError::OldVersionRemovalError; } } else { d->errorMessage = i18n("Not installing version %1 of %2. Version %3 already installed.", meta.version(), meta.pluginId(), oldMeta.version()); d->errorCode = Package::JobError::NewerVersionAlreadyInstalledError; } } else { d->errorMessage = i18n("%1 already exists", targetName); d->errorCode = Package::JobError::PackageAlreadyInstalledError; } if (d->errorCode != KJob::NoError) { d->installPath = targetName; return false; } } //install dependencies const QStringList dependencies = KPluginMetaData::readStringList(meta.rawData(), QStringLiteral("X-KPackage-Dependencies")); for(const QString &dep : dependencies) { QUrl depUrl(dep); if (!installDependency(depUrl)) { d->errorMessage = i18n("Could not install dependency: '%1'", dep); d->errorCode = Package::JobError::PackageCopyError; return false; } } if (archivedPackage) { // it's in a temp dir, so just move it over. const bool ok = copyFolder(path, targetName); removeFolder(path); if (!ok) { //qWarning() << "Could not move package to destination:" << targetName; d->errorMessage = i18n("Could not move package to destination: %1", targetName); d->errorCode = Package::JobError::PackageMoveError; return false; } } else { // it's a directory containing the stuff, so copy the contents rather // than move them const bool ok = copyFolder(path, targetName); if (!ok) { //qWarning() << "Could not copy package to destination:" << targetName; d->errorMessage = i18n("Could not copy package to destination: %1", targetName); d->errorCode = Package::JobError::PackageCopyError; return false; } } if (archivedPackage) { // no need to remove the temp dir (which has been successfully moved if it's an archive) tempdir.setAutoRemove(false); } indexDirectory(dest, QStringLiteral("kpluginindex.json")); d->installPath = targetName; return true; } bool PackageJobThread::update(const QString &src, const QString &dest) { bool ok = installPackage(src, dest, Update); emit installPathChanged(d->installPath); emit finished(ok, d->errorMessage); return ok; } bool PackageJobThread::uninstall(const QString &packagePath) { bool ok = uninstallPackage(packagePath); //qDebug() << "emit installPathChanged " << d->installPath; emit installPathChanged(QString()); //qDebug() << "Thread: installFinished" << ok; emit finished(ok, d->errorMessage); return ok; } bool PackageJobThread::uninstallPackage(const QString &packagePath) { if (!QFile::exists(packagePath)) { d->errorMessage = i18n("%1 does not exist", packagePath); d->errorCode = Package::JobError::PackageFileNotFoundError; return false; } QString pkg; QString root; { // FIXME: remove, pass in packageroot, type and pluginName separately? QStringList ps = packagePath.split(QLatin1Char('/')); int ix = ps.count() - 1; if (packagePath.endsWith(QLatin1Char('/'))) { ix = ps.count() - 2; } pkg = ps[ix]; ps.pop_back(); root = ps.join(QLatin1Char('/')); } bool ok = removeFolder(packagePath); if (!ok) { d->errorMessage = i18n("Could not delete package from: %1", packagePath); d->errorCode = Package::JobError::PackageUninstallError; return false; } indexDirectory(root, QStringLiteral("kpluginindex.json")); return true; } Package::JobError PackageJobThread::errorCode() const { return (Package::JobError)d->errorCode; } } // namespace KPackage #include "moc_packagejobthread_p.cpp" diff --git a/src/kpackagetool/kpackagetool.cpp b/src/kpackagetool/kpackagetool.cpp index a452b5d..110c708 100644 --- a/src/kpackagetool/kpackagetool.cpp +++ b/src/kpackagetool/kpackagetool.cpp @@ -1,770 +1,773 @@ /****************************************************************************** * Copyright 2008 Aaron Seigo * * Copyright 2012-2017 Sebastian Kügler * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Library General Public * * License as published by the Free Software Foundation; either * * version 2 of the License, or (at your option) any later version. * * * * This library is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * Library General Public License for more details. * * * * You should have received a copy of the GNU Library General Public License * * along with this library; see the file COPYING.LIB. If not, write to * * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301, USA. * *******************************************************************************/ #include "kpackagetool.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "options.h" #include "../kpackage/config-package.h" //for the index creation function #include "../kpackage/private/packagejobthread_p.h" Q_GLOBAL_STATIC_WITH_ARGS(QTextStream, cout, (stdout)) Q_GLOBAL_STATIC_WITH_ARGS(QTextStream, cerr, (stderr)) static QVector listPackageTypes() { QStringList libraryPaths; const QString subDirectory = QStringLiteral("kpackage/packagestructure"); Q_FOREACH (const QString &dir, QCoreApplication::libraryPaths()) { QString d = dir + QDir::separator() + subDirectory; if (!d.endsWith(QDir::separator())) { d += QDir::separator(); } libraryPaths << d; } QVector offers; Q_FOREACH (const QString &plugindir, libraryPaths) { const QString &_ixfile = plugindir + QStringLiteral("kpluginindex.json"); QFile indexFile(_ixfile); if (indexFile.exists()) { indexFile.open(QIODevice::ReadOnly); QJsonDocument jdoc = QJsonDocument::fromBinaryData(indexFile.readAll()); indexFile.close(); QJsonArray plugins = jdoc.array(); for (QJsonArray::const_iterator iter = plugins.constBegin(); iter != plugins.constEnd(); ++iter) { const QJsonObject obj = iter->toObject(); const QString candidate = obj.value(QStringLiteral("FileName")).toString(); offers << KPluginMetaData(obj, candidate); } } else { QVector plugins = KPluginLoader::findPlugins(plugindir); QVectorIterator iter(plugins); while (iter.hasNext()) { auto md = iter.next(); offers << md; } } } return offers; } namespace KPackage { class PackageToolPrivate { public: QString packageRoot; QString packageFile; QString package; QStringList pluginTypes; KPackage::Package installer; KPluginMetaData metadata; QString installPath; void output(const QString &msg); QStringList packages(const QStringList &types, const QString &path = QString()); void renderTypeTable(const QMap &plugins); void listTypes(); void coutput(const QString &msg); void cerror(const QString &msg); QCommandLineParser *parser; }; PackageTool::PackageTool(int &argc, char **argv, QCommandLineParser *parser) : QCoreApplication(argc, argv) { d = new PackageToolPrivate; d->parser = parser; QTimer::singleShot(0, this, SLOT(runMain())); } PackageTool::~PackageTool() { delete d; } void PackageTool::runMain() { KPackage::PackageStructure structure; if (d->parser->isSet(Options::hash)) { const QString path = d->parser->value(Options::hash); KPackage::Package package(&structure); package.setPath(path); const QString hash = QString::fromLocal8Bit(package.cryptographicHash(QCryptographicHash::Sha1)); if (hash.isEmpty()) { d->coutput(i18n("Failed to generate a Package hash for %1", path)); exit(9); } else { d->coutput(i18n("SHA1 hash for Package at %1: '%2'", package.path(), hash)); exit(0); } return; } if (d->parser->isSet(Options::listTypes)) { d->listTypes(); exit(0); return; } QString type = d->parser->value(Options::type); d->pluginTypes.clear(); d->installer = Package(); if (d->parser->isSet(Options::remove)) { d->package = d->parser->value(Options::remove); } else if (d->parser->isSet(Options::upgrade)) { d->package = d->parser->value(Options::upgrade); } else if (d->parser->isSet(Options::install)) { d->package = d->parser->value(Options::install); } else if (d->parser->isSet(Options::show)) { d->package = d->parser->value(Options::show); } else if (d->parser->isSet(Options::appstream)) { d->package = d->parser->value(Options::appstream); } if (!QDir::isAbsolutePath(d->package)) { d->packageFile = QDir(QDir::currentPath() + QLatin1Char('/') + d->package).absolutePath(); d->packageFile = QFileInfo(d->packageFile).canonicalFilePath(); if (d->parser->isSet(Options::upgrade)) { d->package = d->packageFile; } } else { d->packageFile = d->package; } if (!d->packageFile.isEmpty() && (!d->parser->isSet(Options::type) || type.compare(i18nc("package type", "wallpaper"), Qt::CaseInsensitive) == 0 || type.compare(QLatin1String("wallpaper"), Qt::CaseInsensitive) == 0)) { // Check type for common plasma packages KPackage::Package package(&structure); QString serviceType; package.setPath(d->packageFile); if (package.isValid() && package.metadata().isValid()) { serviceType = package.metadata().value(QStringLiteral("X-Plasma-ServiceType")); if (serviceType.isEmpty() && !package.metadata().serviceTypes().isEmpty()) { serviceType = package.metadata().serviceTypes().first(); } } if (!serviceType.isEmpty()) { if (serviceType == QLatin1String("KPackage/Generic")) { type = QStringLiteral("KPackage/Generic"); } else { type = serviceType; //qDebug() << "fallthrough type is" << serviceType; } } } { PackageStructure *structure = PackageLoader::self()->loadPackageStructure(type); if (structure) { d->installer = Package(structure); } if (!d->installer.hasValidStructure()) { qWarning() << "Package type" << type << "not found"; } d->packageRoot = d->installer.defaultPackageRoot(); d->pluginTypes << type; } if (d->parser->isSet(Options::show)) { const QString pluginName = d->package; showPackageInfo(pluginName); return; } else if (d->parser->isSet(Options::appstream)) { const QString pluginName = d->package; showAppstreamInfo(pluginName); return; } if (d->parser->isSet(Options::list)) { d->packageRoot = findPackageRoot(d->package, d->packageRoot); d->coutput(i18n("Listing service types: %1 in %2", d->pluginTypes.join(QStringLiteral(", ")), d->packageRoot)); listPackages(d->pluginTypes, d->packageRoot); exit(0); } else if (d->parser->isSet(Options::generateIndex)) { recreateIndex(); exit(0); } else if (d->parser->isSet(Options::removeIndex)) { removeIndex(); exit(0); } else { // install, remove or upgrade if (!d->installer.isValid()) { d->installer = KPackage::Package(new KPackage::PackageStructure()); } d->packageRoot = findPackageRoot(d->package, d->packageRoot); if (d->parser->isSet(Options::remove) || d->parser->isSet(Options::upgrade)) { QString pkgPath; foreach (const QString &t, d->pluginTypes) { KPackage::Package pkg = KPackage::PackageLoader::self()->loadPackage(t); pkg.setPath(d->package); if (pkg.isValid()) { pkgPath = pkg.path(); if (pkgPath.isEmpty() && !d->packageFile.isEmpty()) { pkgPath = d->packageFile; } continue; } } if (pkgPath.isEmpty()) { pkgPath = d->package; } if (d->parser->isSet(Options::upgrade)) { d->installer.setPath(d->package); } QString _p = d->packageRoot; if (!_p.endsWith(QLatin1Char('/'))) { _p.append(QLatin1Char('/')); } _p.append(d->package); d->installer.setDefaultPackageRoot(d->packageRoot); d->installer.setPath(pkgPath); if (!d->parser->isSet(Options::type)) { foreach (const QString &st, d->installer.metadata().serviceTypes()) { if (!d->pluginTypes.contains(st)) { d->pluginTypes << st; } } } QString pluginName; if (d->installer.isValid()) { d->metadata = d->installer.metadata(); if (!d->metadata.isValid()) { pluginName = d->package; } else if (!d->metadata.isValid() && d->metadata.pluginId().isEmpty()) { // plugin name given in command line pluginName = d->package; } else { // Parameter was a plasma package, get plugin name from the package pluginName = d->metadata.pluginId(); } } QStringList installed = d->packages(d->pluginTypes); if (QFile::exists(d->packageFile)) { d->installer.setPath(d->packageFile); if (d->installer.isValid()) { if (d->installer.metadata().isValid()) { pluginName = d->installer.metadata().pluginId(); } } } // Uninstalling ... if (installed.contains(pluginName)) { // Assume it's a plugin name d->installer.setPath(pluginName); KJob *uninstallJob = d->installer.uninstall(pluginName, d->packageRoot); connect(uninstallJob, SIGNAL(result(KJob*)), SLOT(packageUninstalled(KJob*))); return; } else { d->coutput(i18n("Error: Plugin %1 is not installed.", pluginName)); exit(2); } } if (d->parser->isSet(Options::install)) { KJob *installJob = d->installer.install(d->packageFile, d->packageRoot); connect(installJob, SIGNAL(result(KJob*)), SLOT(packageInstalled(KJob*))); return; } if (d->package.isEmpty()) { qWarning() << i18nc("No option was given, this is the error message telling the user he needs at least one, do not translate install, remove, upgrade nor list", "One of install, remove, upgrade or list is required."); exit(6); } } } void PackageToolPrivate::coutput(const QString &msg) { *cout << msg << endl; } void PackageToolPrivate::cerror(const QString &msg) { *cerr << msg << endl; } QStringList PackageToolPrivate::packages(const QStringList &types, const QString &path) { QStringList result; foreach (const QString &type, types) { const QList services = KPackage::PackageLoader::self()->listPackages(type, path); foreach (const KPluginMetaData &service, services) { const QString _plugin = service.pluginId(); if (!result.contains(_plugin)) { result << _plugin; } } } return result; } void PackageTool::showPackageInfo(const QString &pluginName) { QString type = QStringLiteral("KPackage/Generic"); if (!d->pluginTypes.contains(type) && d->pluginTypes.count() > 0) { type = d->pluginTypes.at(0); } KPackage::Package pkg = KPackage::PackageLoader::self()->loadPackage(type); pkg.setDefaultPackageRoot(d->packageRoot); if (QFile::exists(d->packageFile)) { pkg.setPath(d->packageFile); } else { pkg.setPath(pluginName); } KPluginMetaData i = pkg.metadata(); if (!i.isValid()) { *cerr << i18n("Error: Can't find plugin metadata: %1\n", pluginName); exit(3); return; } d->coutput(i18n("Showing info for package: %1", pluginName)); d->coutput(i18n(" Name : %1", i.name())); d->coutput(i18n(" Comment : %1", i.value(QStringLiteral("Comment")))); d->coutput(i18n(" Plugin : %1", i.pluginId())); auto const authors = i.authors(); d->coutput(i18n(" Author : %1", authors.first().name())); d->coutput(i18n(" Path : %1", pkg.path())); exit(0); } bool translateKPluginToAppstream(const QString &tagName, const QString &configField, const QJsonObject &configObject, QXmlStreamWriter& writer, bool canEndWithDot) { const QRegularExpression rx(QStringLiteral("%1\\[(.*)\\]").arg(configField)); const QJsonValue native = configObject.value(configField); if (native.isUndefined()) { return false; } QString content = native.toString(); if (!canEndWithDot && content.endsWith(QLatin1Char('.'))) { content.chop(1); } writer.writeTextElement(tagName, content); for (auto it = configObject.begin(), itEnd = configObject.end(); it != itEnd; ++it) { const auto match = rx.match(it.key()); if (match.hasMatch()) { QString content = it->toString(); if (!canEndWithDot && content.endsWith(QLatin1Char('.'))) { content.chop(1); } writer.writeStartElement(tagName); writer.writeAttribute(QStringLiteral("xml:lang"), match.captured(1)); writer.writeCharacters(content); writer.writeEndElement(); } } return true; } void PackageTool::showAppstreamInfo(const QString &pluginName) { KPluginMetaData i; KPluginMetaData packageStructureMetaData; //if the path passed is an absolute path, and a metadata file is found under it, use that metadata file to generate the appstream info. // This can happen in the case an application wanting to support kpackage based extensions includes in the same project both the packagestructure plugin and the packages themselves. In that case at build time the packagestructure plugin wouldn't be installed yet if (QFile::exists(pluginName + QStringLiteral("/metadata.desktop"))) { i = KPluginMetaData(pluginName + QStringLiteral("/metadata.desktop")); } else if (QFile::exists(pluginName + QStringLiteral("/metadata.json"))) { i = KPluginMetaData(pluginName + QStringLiteral("/metadata.json")); } else { QString type = QStringLiteral("KPackage/Generic"); if (!d->pluginTypes.contains(type) && d->pluginTypes.count() > 0) { type = d->pluginTypes.at(0); } KPackage::Package pkg = KPackage::PackageLoader::self()->loadPackage(type); pkg.setDefaultPackageRoot(d->packageRoot); if (QFile::exists(d->packageFile)) { pkg.setPath(d->packageFile); } else { pkg.setPath(pluginName); } { foreach(const KPluginMetaData &md, listPackageTypes()) { if (md.pluginId() == type) { packageStructureMetaData = md; break; } } } i = pkg.metadata(); } if (!i.isValid()) { *cerr << i18n("Error: Can't find plugin metadata: %1\n", pluginName); std::exit(3); return; } QString parentApp = i.rawData().value(QLatin1String("X-KDE-ParentApp")).toString(); if (parentApp.isEmpty()) { parentApp = packageStructureMetaData.rawData().value(QLatin1String("X-KDE-ParentApp")).toString(); } const QJsonObject rootObject = i.rawData()[QStringLiteral("KPlugin")].toObject(); // Be super aggressive in finding a NoDisplay property. It can be a top-level property or // inside the KPlugin object, it also can be either a stringy type or a bool type. Try all // possible scopes and type conversions to find NoDisplay for (const auto object : { i.rawData(), rootObject }) { if (object.value(QLatin1String("NoDisplay")).toBool(false) || // Standard value is unprocessed string we'll need to deal with. object.value(QLatin1String("NoDisplay")).toString() == QStringLiteral("true")) { std::exit(0); } } QXmlStreamAttributes componentAttributes; if (!parentApp.isEmpty()) { componentAttributes << QXmlStreamAttribute(QLatin1String("type"), QLatin1String("addon")); } // Compatibility: without appstream-metainfo-output argument we print the XML output to STDOUT // with the argument we'll print to the defined path. // TODO: in KF6 we should switch to argument-only. QIODevice *outputDevice = cout->device(); QScopedPointer outputFile; const auto outputPath = d->parser->value(Options::appstreamOutput); if (!outputPath.isEmpty()) { auto outputUrl = QUrl::fromUserInput(outputPath); outputFile.reset(new QFile(outputUrl.toLocalFile())); if (!outputFile->open(QFile::WriteOnly | QFile::Text)) { *cerr << "Failed to open output file for writing."; exit(1); } outputDevice = outputFile.data(); } QXmlStreamWriter writer(outputDevice); writer.setAutoFormatting(true); writer.writeStartDocument(); writer.writeStartElement(QStringLiteral("component")); writer.writeAttributes(componentAttributes); writer.writeTextElement(QStringLiteral("id"), i.pluginId()); if (!parentApp.isEmpty()) { writer.writeTextElement(QStringLiteral("extends"), parentApp); } translateKPluginToAppstream(QStringLiteral("name"), QStringLiteral("Name"), rootObject, writer, false); translateKPluginToAppstream(QStringLiteral("summary"), QStringLiteral("Description"), rootObject, writer, false); if (!i.website().isEmpty()) { writer.writeStartElement(QStringLiteral("url")); writer.writeAttribute(QStringLiteral("type"), QStringLiteral("homepage")); writer.writeCharacters(i.website()); writer.writeEndElement(); } const auto authors = i.authors(); if (!authors.isEmpty()) { QStringList authorsText; for (const auto &author: authors) { authorsText += QStringLiteral("%1 <%2>").arg(author.name(), author.emailAddress()); } writer.writeTextElement(QStringLiteral("developer_name"), authorsText.join(QStringLiteral(", "))); } if (!i.iconName().isEmpty()) { writer.writeStartElement(QStringLiteral("icon")); writer.writeAttribute(QStringLiteral("type"), QStringLiteral("stock")); writer.writeCharacters(i.iconName()); writer.writeEndElement(); } writer.writeTextElement(QStringLiteral("project_license"), KAboutLicense::byKeyword(i.license()).spdx()); writer.writeTextElement(QStringLiteral("metadata_license"), QStringLiteral("CC0-1.0")); writer.writeEndElement(); writer.writeEndDocument(); exit(0); } QString PackageTool::findPackageRoot(const QString &pluginName, const QString &prefix) { Q_UNUSED(pluginName); Q_UNUSED(prefix); QString packageRoot; if (d->parser->isSet(Options::packageRoot) && d->parser->isSet(Options::global) && !d->parser->isSet(Options::generateIndex)) { qWarning() << i18nc("The user entered conflicting options packageroot and global, this is the error message telling the user he can use only one", "The packageroot and global options conflict with each other, please select only one."); ::exit(7); } else if (d->parser->isSet(Options::packageRoot)) { packageRoot = d->parser->value(Options::packageRoot); //qDebug() << "(set via arg) d->packageRoot is: " << d->packageRoot; } else if (d->parser->isSet(Options::global)) { auto const paths = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, d->packageRoot, QStandardPaths::LocateDirectory); if (!paths.isEmpty()) { packageRoot = paths.last(); } } else { packageRoot = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + d->packageRoot; } return packageRoot; } void PackageTool::listPackages(const QStringList &types, const QString &path) { QStringList list = d->packages(types, path); list.sort(); foreach (const QString &package, list) { d->coutput(package); } exit(0); } void PackageToolPrivate::renderTypeTable(const QMap &plugins) { const QString nameHeader = i18n("Addon Name"); const QString pluginHeader = i18n("Service Type"); const QString pathHeader = i18n("Path"); int nameWidth = nameHeader.length(); int pluginWidth = pluginHeader.length(); int pathWidth = pathHeader.length(); QMapIterator pluginIt(plugins); while (pluginIt.hasNext()) { pluginIt.next(); if (pluginIt.key().length() > nameWidth) { nameWidth = pluginIt.key().length(); } if (pluginIt.value()[0].length() > pluginWidth) { pluginWidth = pluginIt.value()[0].length(); } if (pluginIt.value()[1].length() > pathWidth) { pathWidth = pluginIt.value()[1].length(); } } std::cout << nameHeader.toLocal8Bit().constData() << std::setw(nameWidth - nameHeader.length() + 2) << ' ' << pluginHeader.toLocal8Bit().constData() << std::setw(pluginWidth - pluginHeader.length() + 2) << ' ' << pathHeader.toLocal8Bit().constData() << std::setw(pathWidth - pathHeader.length() + 2) << ' ' << std::endl; std::cout << std::setfill('-') << std::setw(nameWidth) << '-' << " " << std::setw(pluginWidth) << '-' << " " << std::setw(pathWidth) << '-' << " " << std::endl; std::cout << std::setfill(' '); pluginIt.toFront(); while (pluginIt.hasNext()) { pluginIt.next(); std::cout << pluginIt.key().toLocal8Bit().constData() << std::setw(nameWidth - pluginIt.key().length() + 2) << ' ' << pluginIt.value()[0].toLocal8Bit().constData() << std::setw(pluginWidth - pluginIt.value()[0].length() + 2) << ' ' << pluginIt.value()[1].toLocal8Bit().constData() << std::setw(pathWidth - pluginIt.value()[1].length() + 2) << std::endl; } } void PackageToolPrivate::listTypes() { coutput(i18n("Package types that are installable with this tool:")); coutput(i18n("Built in:")); QMap builtIns; builtIns.insert(i18n("KPackage/Generic"), QStringList() << QStringLiteral("KPackage/Generic") << QStringLiteral(KPACKAGE_RELATIVE_DATA_INSTALL_DIR "/packages/")); builtIns.insert(i18n("KPackage/GenericQML"), QStringList() << QStringLiteral("KPackage/GenericQML") << QStringLiteral(KPACKAGE_RELATIVE_DATA_INSTALL_DIR "/packagesqml/")); renderTypeTable(builtIns); const QVector offers = listPackageTypes(); if (!offers.isEmpty()) { std::cout << std::endl; coutput(i18n("Provided by plugins:")); QMap plugins; foreach (const KPluginMetaData &info, offers) { KPackage::Package pkg = KPackage::PackageLoader::self()->loadPackage(info.pluginId()); QString name = info.name(); QString plugin = info.pluginId(); QString path = pkg.defaultPackageRoot(); plugins.insert(name, QStringList() << plugin << path); } renderTypeTable(plugins); } } void PackageTool::recreateIndex() { d->packageRoot = findPackageRoot(d->package, d->packageRoot); if (!QDir::isAbsolutePath(d->packageRoot)) { if (d->parser->isSet(Options::global)) { Q_FOREACH(auto const &p, QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, d->packageRoot, QStandardPaths::LocateDirectory)) { // Caching is limited to plasma, otherwise all of /usr/share/ may be indexed, taking forever without much gain - QString proot = QStringLiteral("/plasma"); - if (!d->packageRoot.isEmpty()) { - proot = QStringLiteral("/%1").arg(d->packageRoot); - } - QDirIterator it(p + proot, QDir::Dirs | QDir::Writable); - while (it.hasNext()) { - it.next(); - const QString packagedir = it.fileInfo().absoluteFilePath(); - if (KPackage::indexDirectory(packagedir, QStringLiteral("kpluginindex.json"))) { - d->coutput(i18n("Generating %1/kpluginindex.json", packagedir)); - } else { - d->cerror(i18n("Didn't write %1/kpluginindex.json", packagedir)); + QStringList proots = QStringList({QStringLiteral("/plasma"), QStringLiteral("/kwin")}); + for (const auto &_proot: proots) { + QString proot = _proot; + if (!d->packageRoot.isEmpty()) { + proot = QStringLiteral("/%1").arg(d->packageRoot); + } + QDirIterator it(p + proot, QDir::Dirs | QDir::Writable); + while (it.hasNext()) { + it.next(); + const QString packagedir = it.fileInfo().absoluteFilePath(); + if (KPackage::indexDirectory(packagedir, QStringLiteral("kpluginindex.json"))) { + d->coutput(i18n("Generating %1/kpluginindex.json", packagedir)); + } else { + d->cerror(i18n("Didn't write %1/kpluginindex.json", packagedir)); + } } } } return; } else { d->packageRoot = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + d->packageRoot; } } if (KPackage::indexDirectory(d->packageRoot, QStringLiteral("kpluginindex.json"))) { d->coutput(i18n("Generating %1/kpluginindex.json", d->packageRoot)); } else { d->cerror(i18n("Cannot write %1/kpluginindex.json", d->packageRoot)); } } void PackageTool::removeIndex() { d->packageRoot = findPackageRoot(d->package, d->packageRoot); if (!QDir::isAbsolutePath(d->packageRoot)) { if (d->parser->isSet(Options::global)) { Q_FOREACH(auto const &p, QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, d->packageRoot, QStandardPaths::LocateDirectory)) { QDirIterator it(p, QStringList(QLatin1String("kpluginindex.json")), QDir::Files | QDir::Writable, QDirIterator::Subdirectories); qDebug() << "IX index remove" << p; while (it.hasNext()) { it.next(); const QString path = it.fileInfo().absoluteFilePath(); QFile file(path); if (!file.remove()) { d->cerror(i18n("Could not remove index file %1", file.fileName())); } else { d->coutput(i18n("Removed %1", file.fileName())); } } } return; } else { d->packageRoot = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + d->packageRoot; } } QFile file(d->packageRoot + QStringLiteral("kpluginindex.json")); if (file.exists()) { if (!file.remove()) { d->cerror(i18n("Could not remove index file %1", file.fileName())); } else { d->coutput(i18n("Removed %1", file.fileName())); } } } void PackageTool::packageInstalled(KJob *job) { bool success = (job->error() == KJob::NoError); int exitcode = 0; if (success) { if (d->parser->isSet(Options::upgrade)) { d->coutput(i18n("Successfully upgraded %1", d->packageFile)); } else { d->coutput(i18n("Successfully installed %1", d->packageFile)); } } else { d->cerror(i18n("Error: Installation of %1 failed: %2", d->packageFile, job->errorText())); exitcode = 4; } exit(exitcode); } void PackageTool::packageUninstalled(KJob *job) { bool success = (job->error() == KJob::NoError); int exitcode = 0; if (success) { if (d->parser->isSet(Options::upgrade)) { d->coutput(i18n("Upgrading package from file: %1", d->packageFile)); KJob *installJob = d->installer.install(d->packageFile, d->packageRoot); connect(installJob, SIGNAL(result(KJob*)), SLOT(packageInstalled(KJob*))); return; } d->coutput(i18n("Successfully uninstalled %1", d->packageFile)); } else { d->cerror(i18n("Error: Uninstallation of %1 failed: %2", d->packageFile, job->errorText())); exitcode = 7; } exit(exitcode); } } // namespace KPackage #include "moc_kpackagetool.cpp"