File indexing completed on 2024-04-28 15:29:18

0001 /*
0002     SPDX-FileCopyrightText: 2008 Aaron Seigo <aseigo@kde.org>
0003     SPDX-FileCopyrightText: 2012-2017 Sebastian Kügler <sebas@kde.org>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "kpackagetool.h"
0009 
0010 #include <KAboutData>
0011 #include <KLocalizedString>
0012 #include <KShell>
0013 #include <QDebug>
0014 
0015 #include <KJob>
0016 #include <kpackage/package.h>
0017 #include <kpackage/packageloader.h>
0018 #include <kpackage/packagestructure.h>
0019 #include <kpackage/private/utils.h>
0020 
0021 #include <QCommandLineParser>
0022 #include <QDir>
0023 #include <QFileInfo>
0024 #include <QMap>
0025 #include <QRegularExpression>
0026 #include <QStandardPaths>
0027 #include <QStringList>
0028 #include <QTimer>
0029 #include <QUrl>
0030 #include <QVector>
0031 #include <QXmlStreamWriter>
0032 
0033 #include <iomanip>
0034 #include <iostream>
0035 
0036 #include "options.h"
0037 
0038 #include "../kpackage/config-package.h"
0039 // for the index creation function
0040 #include "../kpackage/package_export.h"
0041 #include "../kpackage/private/packagejobthread_p.h"
0042 
0043 #include "kpackage_debug.h"
0044 
0045 Q_GLOBAL_STATIC_WITH_ARGS(QTextStream, cout, (stdout))
0046 Q_GLOBAL_STATIC_WITH_ARGS(QTextStream, cerr, (stderr))
0047 
0048 namespace KPackage
0049 {
0050 class PackageToolPrivate
0051 {
0052 public:
0053     QString packageRoot;
0054     QString packageFile;
0055     QString package;
0056     QStringList pluginTypes;
0057     KPackage::Package installer;
0058     KPluginMetaData metadata;
0059     QString installPath;
0060     void output(const QString &msg);
0061     QStringList packages(const QStringList &types, const QString &path = QString());
0062     void renderTypeTable(const QMap<QString, QString> &plugins);
0063     void listTypes();
0064     void coutput(const QString &msg);
0065     void cerror(const QString &msg);
0066     QCommandLineParser *parser = nullptr;
0067 };
0068 
0069 PackageTool::PackageTool(int &argc, char **argv, QCommandLineParser *parser)
0070     : QCoreApplication(argc, argv)
0071 {
0072     d = new PackageToolPrivate;
0073     d->parser = parser;
0074     QTimer::singleShot(0, this, &PackageTool::runMain);
0075 }
0076 
0077 PackageTool::~PackageTool()
0078 {
0079     delete d;
0080 }
0081 
0082 void PackageTool::runMain()
0083 {
0084     KPackage::PackageStructure structure;
0085     if (d->parser->isSet(Options::hash())) {
0086         const QString path = d->parser->value(Options::hash());
0087         KPackage::Package package(&structure);
0088         package.setPath(path);
0089         const QString hash = QString::fromLocal8Bit(package.cryptographicHash(QCryptographicHash::Sha1));
0090         if (hash.isEmpty()) {
0091             d->coutput(i18n("Failed to generate a Package hash for %1", path));
0092             exit(9);
0093         } else {
0094             d->coutput(i18n("SHA1 hash for Package at %1: '%2'", package.path(), hash));
0095             exit(0);
0096         }
0097         return;
0098     }
0099 
0100     if (d->parser->isSet(Options::listTypes())) {
0101         d->listTypes();
0102         exit(0);
0103         return;
0104     }
0105 
0106     QString type = d->parser->value(Options::type());
0107     d->pluginTypes.clear();
0108     d->installer = Package();
0109 
0110     if (d->parser->isSet(Options::remove())) {
0111         d->package = d->parser->value(Options::remove());
0112     } else if (d->parser->isSet(Options::upgrade())) {
0113         d->package = d->parser->value(Options::upgrade());
0114     } else if (d->parser->isSet(Options::install())) {
0115         d->package = d->parser->value(Options::install());
0116     } else if (d->parser->isSet(Options::show())) {
0117         d->package = d->parser->value(Options::show());
0118     } else if (d->parser->isSet(Options::appstream())) {
0119         d->package = d->parser->value(Options::appstream());
0120     }
0121 
0122     if (!QDir::isAbsolutePath(d->package)) {
0123         d->packageFile = QDir(QDir::currentPath() + QLatin1Char('/') + d->package).absolutePath();
0124         d->packageFile = QFileInfo(d->packageFile).canonicalFilePath();
0125         if (d->parser->isSet(Options::upgrade())) {
0126             d->package = d->packageFile;
0127         }
0128     } else {
0129         d->packageFile = d->package;
0130     }
0131 
0132     if (!d->packageFile.isEmpty()
0133         && (!d->parser->isSet(Options::type()) || type.compare(i18nc("package type", "wallpaper"), Qt::CaseInsensitive) == 0
0134             || type.compare(QLatin1String("wallpaper"), Qt::CaseInsensitive) == 0)) {
0135         // Check type for common plasma packages
0136         KPackage::Package package(&structure);
0137         QString serviceType;
0138         package.setPath(d->packageFile);
0139 
0140         if (package.isValid() && package.metadata().isValid()) {
0141             serviceType = package.metadata().value(QStringLiteral("X-Plasma-ServiceType"));
0142             const auto serviceTypes = readKPackageTypes(package.metadata());
0143             if (serviceType.isEmpty() && !serviceTypes.isEmpty()) {
0144                 serviceType = serviceTypes.first();
0145             }
0146         }
0147 
0148         if (!serviceType.isEmpty()) {
0149             if (serviceType == QLatin1String("KPackage/Generic")) {
0150                 type = QStringLiteral("KPackage/Generic");
0151             } else {
0152                 type = serviceType;
0153                 // qDebug() << "fallthrough type is" << serviceType;
0154             }
0155         }
0156     }
0157 
0158     {
0159         PackageStructure *structure = PackageLoader::self()->loadPackageStructure(type);
0160 
0161         if (structure) {
0162             d->installer = Package(structure);
0163         }
0164 
0165         if (!d->installer.hasValidStructure()) {
0166             qWarning() << "Package type" << type << "not found";
0167         }
0168 
0169         d->packageRoot = d->installer.defaultPackageRoot();
0170         d->pluginTypes << type;
0171     }
0172     if (d->parser->isSet(Options::show())) {
0173         const QString pluginName = d->package;
0174         showPackageInfo(pluginName);
0175         return;
0176     } else if (d->parser->isSet(Options::appstream())) {
0177         const QString pluginName = d->package;
0178         showAppstreamInfo(pluginName);
0179         return;
0180     }
0181 
0182     if (d->parser->isSet(Options::list())) {
0183         d->packageRoot = findPackageRoot(d->package, d->packageRoot);
0184         d->coutput(i18n("Listing service types: %1 in %2", d->pluginTypes.join(QStringLiteral(", ")), d->packageRoot));
0185         listPackages(d->pluginTypes, d->packageRoot);
0186         exit(0);
0187 
0188     } else if (d->parser->isSet(Options::generateIndex())) {
0189         // TODO KF6 Remove
0190         qWarning() << "The indexing feature is removed in KPackage 5.82";
0191         exit(0);
0192 
0193     } else if (d->parser->isSet(Options::removeIndex())) {
0194         // TODO KF6 Remove
0195         qWarning() << "The indexing feature is removed in KPackage 5.82";
0196         exit(0);
0197 
0198     } else {
0199         // install, remove or upgrade
0200         if (!d->installer.isValid()) {
0201             d->installer = KPackage::Package(new KPackage::PackageStructure());
0202         }
0203 
0204         d->packageRoot = findPackageRoot(d->package, d->packageRoot);
0205 
0206         if (d->parser->isSet(Options::remove()) || d->parser->isSet(Options::upgrade())) {
0207             QString pkgPath;
0208             for (const QString &t : std::as_const(d->pluginTypes)) {
0209                 KPackage::Package pkg = KPackage::PackageLoader::self()->loadPackage(t);
0210                 pkg.setPath(d->package);
0211                 if (pkg.isValid()) {
0212                     pkgPath = pkg.path();
0213                     if (pkgPath.isEmpty() && !d->packageFile.isEmpty()) {
0214                         pkgPath = d->packageFile;
0215                     }
0216                     continue;
0217                 }
0218             }
0219             if (pkgPath.isEmpty()) {
0220                 pkgPath = d->package;
0221             }
0222 
0223             if (d->parser->isSet(Options::upgrade())) {
0224                 d->installer.setPath(d->package);
0225             }
0226             QString _p = d->packageRoot;
0227             if (!_p.endsWith(QLatin1Char('/'))) {
0228                 _p.append(QLatin1Char('/'));
0229             }
0230             _p.append(d->package);
0231             d->installer.setDefaultPackageRoot(d->packageRoot);
0232             d->installer.setPath(pkgPath);
0233 
0234             if (!d->parser->isSet(Options::type())) {
0235                 const QStringList lst = readKPackageTypes(d->installer.metadata());
0236                 for (const QString &st : lst) {
0237                     if (!d->pluginTypes.contains(st)) {
0238                         d->pluginTypes << st;
0239                     }
0240                 }
0241             }
0242 
0243             QString pluginName;
0244             if (d->installer.isValid()) {
0245                 d->metadata = d->installer.metadata();
0246                 if (!d->metadata.isValid()) {
0247                     pluginName = d->package;
0248                 } else if (!d->metadata.isValid() && d->metadata.pluginId().isEmpty()) {
0249                     // plugin name given in command line
0250                     pluginName = d->package;
0251                 } else {
0252                     // Parameter was a plasma package, get plugin name from the package
0253                     pluginName = d->metadata.pluginId();
0254                 }
0255             }
0256             QStringList installed = d->packages(d->pluginTypes);
0257 
0258             if (QFile::exists(d->packageFile)) {
0259                 d->installer.setPath(d->packageFile);
0260                 if (d->installer.isValid()) {
0261                     if (d->installer.metadata().isValid()) {
0262                         pluginName = d->installer.metadata().pluginId();
0263                     }
0264                 }
0265             }
0266             // Uninstalling ...
0267             if (installed.contains(pluginName)) { // Assume it's a plugin name
0268                 d->installer.setPath(pluginName);
0269                 KJob *uninstallJob = d->installer.uninstall(pluginName, d->packageRoot);
0270                 // clang-format off
0271                 connect(uninstallJob, SIGNAL(result(KJob*)), SLOT(packageUninstalled(KJob*)));
0272                 // clang-format on
0273                 return;
0274             } else {
0275                 d->coutput(i18n("Error: Plugin %1 is not installed.", pluginName));
0276                 exit(2);
0277             }
0278         }
0279         if (d->parser->isSet(Options::install())) {
0280             if (QFileInfo::exists(d->packageFile + QLatin1String("/metadata.desktop"))) {
0281                 qWarning() << "Providing a metadata.desktop file for KPackage metadata is deprecated."
0282                            << "Please convert this file using the following command or contact the author:";
0283                 qWarning() << QLatin1String("desktoptojson %1; rm %1").arg(d->packageFile + QLatin1String("/metadata.desktop"));
0284             }
0285             KJob *installJob = d->installer.install(d->packageFile, d->packageRoot);
0286             // clang-format off
0287             connect(installJob, SIGNAL(result(KJob*)), SLOT(packageInstalled(KJob*)));
0288             // clang-format on
0289             return;
0290         }
0291         if (d->package.isEmpty()) {
0292             qWarning() << i18nc(
0293                 "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",
0294                 "One of install, remove, upgrade or list is required.");
0295             exit(6);
0296         }
0297     }
0298 }
0299 
0300 void PackageToolPrivate::coutput(const QString &msg)
0301 {
0302     *cout << msg << '\n';
0303     (*cout).flush();
0304 }
0305 
0306 void PackageToolPrivate::cerror(const QString &msg)
0307 {
0308     *cerr << msg << '\n';
0309     (*cerr).flush();
0310 }
0311 
0312 QStringList PackageToolPrivate::packages(const QStringList &types, const QString &path)
0313 {
0314     QStringList result;
0315 
0316     for (const QString &type : types) {
0317         const QList<KPluginMetaData> services = KPackage::PackageLoader::self()->listPackages(type, path);
0318         for (const KPluginMetaData &service : services) {
0319             const QString _plugin = service.pluginId();
0320             if (!result.contains(_plugin)) {
0321                 result << _plugin;
0322             }
0323         }
0324     }
0325 
0326     return result;
0327 }
0328 
0329 void PackageTool::showPackageInfo(const QString &pluginName)
0330 {
0331     QString type = QStringLiteral("KPackage/Generic");
0332     if (!d->pluginTypes.contains(type) && !d->pluginTypes.isEmpty()) {
0333         type = d->pluginTypes.at(0);
0334     }
0335     KPackage::Package pkg = KPackage::PackageLoader::self()->loadPackage(type);
0336 
0337     pkg.setDefaultPackageRoot(d->packageRoot);
0338 
0339     if (QFile::exists(d->packageFile)) {
0340         pkg.setPath(d->packageFile);
0341     } else {
0342         pkg.setPath(pluginName);
0343     }
0344 
0345     KPluginMetaData i = pkg.metadata();
0346     if (!i.isValid()) {
0347         *cerr << i18n("Error: Can't find plugin metadata: %1\n", pluginName);
0348         exit(3);
0349         return;
0350     }
0351     d->coutput(i18n("Showing info for package: %1", pluginName));
0352     d->coutput(i18n("      Name : %1", i.name()));
0353     d->coutput(i18n("   Comment : %1", i.value(QStringLiteral("Comment"))));
0354     d->coutput(i18n("    Plugin : %1", i.pluginId()));
0355     auto const authors = i.authors();
0356     d->coutput(i18n("    Author : %1", authors.first().name()));
0357     d->coutput(i18n("      Path : %1", pkg.path()));
0358 
0359     exit(0);
0360 }
0361 
0362 bool translateKPluginToAppstream(const QString &tagName,
0363                                  const QString &configField,
0364                                  const QJsonObject &configObject,
0365                                  QXmlStreamWriter &writer,
0366                                  bool canEndWithDot)
0367 {
0368     const QRegularExpression rx(QStringLiteral("%1\\[(.*)\\]").arg(configField));
0369     const QJsonValue native = configObject.value(configField);
0370     if (native.isUndefined()) {
0371         return false;
0372     }
0373 
0374     QString content = native.toString();
0375     if (!canEndWithDot && content.endsWith(QLatin1Char('.'))) {
0376         content.chop(1);
0377     }
0378     writer.writeTextElement(tagName, content);
0379     for (auto it = configObject.begin(), itEnd = configObject.end(); it != itEnd; ++it) {
0380         const auto match = rx.match(it.key());
0381 
0382         if (match.hasMatch()) {
0383             QString content = it->toString();
0384             if (!canEndWithDot && content.endsWith(QLatin1Char('.'))) {
0385                 content.chop(1);
0386             }
0387 
0388             writer.writeStartElement(tagName);
0389             writer.writeAttribute(QStringLiteral("xml:lang"), match.captured(1));
0390             writer.writeCharacters(content);
0391             writer.writeEndElement();
0392         }
0393     }
0394     return true;
0395 }
0396 
0397 void PackageTool::showAppstreamInfo(const QString &pluginName)
0398 {
0399     KPluginMetaData i;
0400     // 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.
0401     // This can happen in the case an application wanting to support kpackage based extensions includes in the same project both the packagestructure plugin and
0402     // the packages themselves. In that case at build time the packagestructure plugin wouldn't be installed yet
0403 
0404     if (QFile::exists(pluginName + QStringLiteral("/metadata.json"))) {
0405         i = KPluginMetaData::fromJsonFile(pluginName + QStringLiteral("/metadata.json"));
0406 #if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 92)
0407     } else if (QFile::exists(pluginName + QStringLiteral("/metadata.desktop"))) {
0408         i = KPluginMetaData::fromDesktopFile(pluginName + QStringLiteral("/metadata.desktop"), {QStringLiteral(":/kservicetypes5/kpackage-generic.desktop")});
0409 #endif
0410     } else {
0411         QString type = QStringLiteral("KPackage/Generic");
0412         if (!d->pluginTypes.contains(type) && !d->pluginTypes.isEmpty()) {
0413             type = d->pluginTypes.at(0);
0414         }
0415         KPackage::Package pkg = KPackage::PackageLoader::self()->loadPackage(type);
0416 
0417         pkg.setDefaultPackageRoot(d->packageRoot);
0418 
0419         if (QFile::exists(d->packageFile)) {
0420             pkg.setPath(d->packageFile);
0421         } else {
0422             pkg.setPath(pluginName);
0423         }
0424 
0425         i = pkg.metadata();
0426     }
0427 
0428     if (!i.isValid()) {
0429         *cerr << i18n("Error: Can't find plugin metadata: %1\n", pluginName);
0430         std::exit(3);
0431         return;
0432     }
0433     QString parentApp = i.value(QLatin1String("X-KDE-ParentApp"));
0434 
0435 #if KPACKAGE_BUILD_DEPRECATED_SINCE(5, 85)
0436     KPluginMetaData packageStructureMetaData;
0437     {
0438         const QStringList packageFormats = readKPackageTypes(i);
0439         if (!packageFormats.isEmpty()) {
0440             packageStructureMetaData = structureForKPackageType(packageFormats.first());
0441         }
0442     }
0443     if (parentApp.isEmpty()) {
0444         parentApp = packageStructureMetaData.value(QLatin1String("X-KDE-ParentApp"));
0445         if (!parentApp.isEmpty()) {
0446             qCDebug(KPACKAGE_LOG) << "Implicitly specifying X-KDE-ParentApp by it's parent structure is deprecated and will"
0447                                      "be removed in KF6. Either the value should be explicitly set or the default will be used";
0448         }
0449     }
0450 #endif
0451 
0452     if (i.value(QStringLiteral("NoDisplay"), false)) {
0453         std::exit(0);
0454     }
0455 
0456     QXmlStreamAttributes componentAttributes;
0457     if (!parentApp.isEmpty()) {
0458         componentAttributes << QXmlStreamAttribute(QLatin1String("type"), QLatin1String("addon"));
0459     }
0460 
0461     // Compatibility: without appstream-metainfo-output argument we print the XML output to STDOUT
0462     // with the argument we'll print to the defined path.
0463     // TODO: in KF6 we should switch to argument-only.
0464     QIODevice *outputDevice = cout->device();
0465     std::unique_ptr<QFile> outputFile;
0466     const auto outputPath = d->parser->value(Options::appstreamOutput());
0467     if (!outputPath.isEmpty()) {
0468         auto outputUrl = QUrl::fromUserInput(outputPath);
0469         outputFile.reset(new QFile(outputUrl.toLocalFile()));
0470         if (!outputFile->open(QFile::WriteOnly | QFile::Text)) {
0471             *cerr << "Failed to open output file for writing.";
0472             exit(1);
0473         }
0474         outputDevice = outputFile.get();
0475     }
0476 
0477     if (i.description().isEmpty()) {
0478         *cerr << "Error: description missing, will result in broken appdata field as <summary/> is mandatory at "
0479               << QFileInfo(i.metaDataFileName()).absoluteFilePath();
0480         std::exit(10);
0481     }
0482 
0483     QXmlStreamWriter writer(outputDevice);
0484     writer.setAutoFormatting(true);
0485     writer.writeStartDocument();
0486     writer.writeStartElement(QStringLiteral("component"));
0487     writer.writeAttributes(componentAttributes);
0488 
0489     writer.writeTextElement(QStringLiteral("id"), i.pluginId());
0490     if (!parentApp.isEmpty()) {
0491         writer.writeTextElement(QStringLiteral("extends"), parentApp);
0492     }
0493 
0494     const QJsonObject rootObject = i.rawData()[QStringLiteral("KPlugin")].toObject();
0495     translateKPluginToAppstream(QStringLiteral("name"), QStringLiteral("Name"), rootObject, writer, false);
0496     translateKPluginToAppstream(QStringLiteral("summary"), QStringLiteral("Description"), rootObject, writer, false);
0497     if (!i.website().isEmpty()) {
0498         writer.writeStartElement(QStringLiteral("url"));
0499         writer.writeAttribute(QStringLiteral("type"), QStringLiteral("homepage"));
0500         writer.writeCharacters(i.website());
0501         writer.writeEndElement();
0502     }
0503 
0504     if (i.pluginId().startsWith(QLatin1String("org.kde."))) {
0505         writer.writeStartElement(QStringLiteral("url"));
0506         writer.writeAttribute(QStringLiteral("type"), QStringLiteral("donation"));
0507         writer.writeCharacters(QStringLiteral("https://www.kde.org/donate.php?app=%1").arg(i.pluginId()));
0508         writer.writeEndElement();
0509     }
0510 
0511     const auto authors = i.authors();
0512     if (!authors.isEmpty()) {
0513         QStringList authorsText;
0514         authorsText.reserve(authors.size());
0515         for (const auto &author : authors) {
0516             authorsText += QStringLiteral("%1 <%2>").arg(author.name(), author.emailAddress());
0517         }
0518         writer.writeTextElement(QStringLiteral("developer_name"), authorsText.join(QStringLiteral(", ")));
0519     }
0520 
0521     if (!i.iconName().isEmpty()) {
0522         writer.writeStartElement(QStringLiteral("icon"));
0523         writer.writeAttribute(QStringLiteral("type"), QStringLiteral("stock"));
0524         writer.writeCharacters(i.iconName());
0525         writer.writeEndElement();
0526     }
0527     writer.writeTextElement(QStringLiteral("project_license"), KAboutLicense::byKeyword(i.license()).spdx());
0528     writer.writeTextElement(QStringLiteral("metadata_license"), QStringLiteral("CC0-1.0"));
0529     writer.writeEndElement();
0530     writer.writeEndDocument();
0531 
0532     exit(0);
0533 }
0534 
0535 QString PackageTool::findPackageRoot(const QString &pluginName, const QString &prefix)
0536 {
0537     Q_UNUSED(pluginName);
0538     Q_UNUSED(prefix);
0539     QString packageRoot;
0540     if (d->parser->isSet(Options::packageRoot()) && d->parser->isSet(Options::global()) && !d->parser->isSet(Options::generateIndex())) {
0541         qWarning() << i18nc("The user entered conflicting options packageroot and global, this is the error message telling the user he can use only one",
0542                             "The packageroot and global options conflict with each other, please select only one.");
0543         ::exit(7);
0544     } else if (d->parser->isSet(Options::packageRoot())) {
0545         packageRoot = d->parser->value(Options::packageRoot());
0546         // qDebug() << "(set via arg) d->packageRoot is: " << d->packageRoot;
0547     } else if (d->parser->isSet(Options::global())) {
0548         auto const paths = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, d->packageRoot, QStandardPaths::LocateDirectory);
0549         if (!paths.isEmpty()) {
0550             packageRoot = paths.last();
0551         }
0552     } else {
0553         packageRoot = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + d->packageRoot;
0554     }
0555     return packageRoot;
0556 }
0557 
0558 void PackageTool::listPackages(const QStringList &types, const QString &path)
0559 {
0560     QStringList list = d->packages(types, path);
0561     list.sort();
0562     for (const QString &package : std::as_const(list)) {
0563         d->coutput(package);
0564     }
0565     exit(0);
0566 }
0567 
0568 void PackageToolPrivate::renderTypeTable(const QMap<QString, QString> &plugins)
0569 {
0570     const QString nameHeader = i18n("KPackage Structure Name");
0571     const QString pathHeader = i18n("Path");
0572     int nameWidth = nameHeader.length();
0573     int pathWidth = pathHeader.length();
0574 
0575     QMapIterator<QString, QString> pluginIt(plugins);
0576     while (pluginIt.hasNext()) {
0577         pluginIt.next();
0578         if (pluginIt.key().length() > nameWidth) {
0579             nameWidth = pluginIt.key().length();
0580         }
0581 
0582         if (pluginIt.value().length() > pathWidth) {
0583             pathWidth = pluginIt.value().length();
0584         }
0585     }
0586 
0587     std::cout << nameHeader.toLocal8Bit().constData() << std::setw(nameWidth - nameHeader.length() + 2) << ' ' << pathHeader.toLocal8Bit().constData()
0588               << std::setw(pathWidth - pathHeader.length() + 2) << ' ' << std::endl;
0589     std::cout << std::setfill('-') << std::setw(nameWidth) << '-' << "  " << std::setw(pathWidth) << '-' << "  " << std::endl;
0590     std::cout << std::setfill(' ');
0591 
0592     pluginIt.toFront();
0593     while (pluginIt.hasNext()) {
0594         pluginIt.next();
0595         std::cout << pluginIt.key().toLocal8Bit().constData() << std::setw(nameWidth - pluginIt.key().length() + 2) << ' '
0596                   << pluginIt.value().toLocal8Bit().constData() << std::setw(pathWidth - pluginIt.value().length() + 2) << std::endl;
0597     }
0598 }
0599 
0600 void PackageToolPrivate::listTypes()
0601 {
0602     coutput(i18n("Package types that are installable with this tool:"));
0603     coutput(i18n("Built in:"));
0604 
0605     QMap<QString, QString> builtIns;
0606     builtIns.insert(i18n("KPackage/Generic"), QStringLiteral(KPACKAGE_RELATIVE_DATA_INSTALL_DIR "/packages/"));
0607     builtIns.insert(i18n("KPackage/GenericQML"), QStringLiteral(KPACKAGE_RELATIVE_DATA_INSTALL_DIR "/genericqml/"));
0608 
0609     renderTypeTable(builtIns);
0610 
0611     const QVector<KPluginMetaData> offers = KPluginMetaData::findPlugins(QStringLiteral("kpackage/packagestructure"));
0612 
0613     if (!offers.isEmpty()) {
0614         std::cout << std::endl;
0615         coutput(i18n("Provided by plugins:"));
0616 
0617         QMap<QString, QString> plugins;
0618         for (const KPluginMetaData &info : offers) {
0619             const QStringList types = readKPackageTypes(info);
0620             if (types.isEmpty()) {
0621                 continue;
0622             }
0623             KPackage::Package pkg = KPackage::PackageLoader::self()->loadPackage(types.first());
0624             QString path = pkg.defaultPackageRoot();
0625             plugins.insert(types.first(), path);
0626         }
0627 
0628         renderTypeTable(plugins);
0629     }
0630 }
0631 
0632 void PackageTool::packageInstalled(KJob *job)
0633 {
0634     bool success = (job->error() == KJob::NoError);
0635     int exitcode = 0;
0636     if (success) {
0637         if (d->parser->isSet(Options::upgrade())) {
0638             d->coutput(i18n("Successfully upgraded %1", d->packageFile));
0639         } else {
0640             d->coutput(i18n("Successfully installed %1", d->packageFile));
0641         }
0642     } else {
0643         d->cerror(i18n("Error: Installation of %1 failed: %2", d->packageFile, job->errorText()));
0644         exitcode = 4;
0645     }
0646     exit(exitcode);
0647 }
0648 
0649 void PackageTool::packageUninstalled(KJob *job)
0650 {
0651     bool success = (job->error() == KJob::NoError);
0652     int exitcode = 0;
0653     if (success) {
0654         if (d->parser->isSet(Options::upgrade())) {
0655             d->coutput(i18n("Upgrading package from file: %1", d->packageFile));
0656             KJob *installJob = d->installer.install(d->packageFile, d->packageRoot);
0657             // clang-format off
0658             connect(installJob, SIGNAL(result(KJob*)), SLOT(packageInstalled(KJob*)));
0659             // clang-format on
0660             return;
0661         }
0662         d->coutput(i18n("Successfully uninstalled %1", d->packageFile));
0663     } else {
0664         d->cerror(i18n("Error: Uninstallation of %1 failed: %2", d->packageFile, job->errorText()));
0665         exitcode = 7;
0666     }
0667     exit(exitcode);
0668 }
0669 
0670 } // namespace KPackage
0671 
0672 #include "moc_kpackagetool.cpp"