File indexing completed on 2024-04-21 03:56:32

0001 /*
0002     SPDX-FileCopyrightText: 2012 Sebastian Kügler <sebas@kde.org>
0003     SPDX-FileCopyrightText: 2023 Alexander Lohnau <alexander.lohnau@gmx.de>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "packagejob.h"
0009 
0010 #include "config-package.h"
0011 #include "packageloader.h"
0012 #include "packagestructure.h"
0013 #include "private/package_p.h"
0014 #include "private/packagejobthread_p.h"
0015 #include "private/utils.h"
0016 
0017 #include "kpackage_debug.h"
0018 
0019 #include <QDBusConnection>
0020 #include <QDBusMessage>
0021 #include <QDebug>
0022 #include <QStandardPaths>
0023 #include <QThreadPool>
0024 #include <QTimer>
0025 
0026 namespace KPackage
0027 {
0028 struct StructureOrErrorJob {
0029     PackageStructure *structure = nullptr;
0030     PackageJob *errorJob = nullptr;
0031 };
0032 class PackageJobPrivate
0033 {
0034 public:
0035     static StructureOrErrorJob loadStructure(const QString &packageFormat)
0036     {
0037         if (auto structure = PackageLoader::self()->loadPackageStructure(packageFormat)) {
0038             return StructureOrErrorJob{structure, nullptr};
0039         } else {
0040             auto job = new PackageJob(PackageJob::Install, Package(), QString(), QString());
0041             job->setErrorText(QStringLiteral("Could not load package structure ") + packageFormat);
0042             job->setError(PackageJob::JobError::InvalidPackageStructure);
0043             QTimer::singleShot(0, job, [job]() {
0044                 job->emitResult();
0045             });
0046             return StructureOrErrorJob{nullptr, job};
0047         }
0048     }
0049     PackageJobThread *thread = nullptr;
0050     Package package;
0051     QString installPath;
0052 };
0053 
0054 PackageJob::PackageJob(OperationType type, const Package &package, const QString &src, const QString &dest)
0055     : KJob()
0056     , d(new PackageJobPrivate)
0057 {
0058     d->thread = new PackageJobThread(type, src, dest, package);
0059     d->package = package;
0060 
0061     if (type == Install) {
0062         setupNotificationsOnJobFinished(QStringLiteral("packageInstalled"));
0063     } else if (type == Update) {
0064         setupNotificationsOnJobFinished(QStringLiteral("packageUpdated"));
0065         d->thread->update(src, dest, package);
0066     } else if (type == Uninstall) {
0067         setupNotificationsOnJobFinished(QStringLiteral("packageUninstalled"));
0068     } else {
0069         Q_UNREACHABLE();
0070     }
0071     connect(d->thread, &PackageJobThread::installPathChanged, this, [this](const QString &installPath) {
0072         d->package.setPath(installPath);
0073     });
0074     connect(d->thread, &PackageJobThread::jobThreadFinished, this, [this]() {
0075         emitResult();
0076     });
0077 }
0078 
0079 PackageJob::~PackageJob() = default;
0080 
0081 void PackageJob::start()
0082 {
0083     if (d->thread) {
0084         QThreadPool::globalInstance()->start(d->thread);
0085         d->thread = nullptr;
0086     } else {
0087         qCWarning(KPACKAGE_LOG) << "The KPackage::PackageJob was already started";
0088     }
0089 }
0090 
0091 PackageJob *PackageJob::install(const QString &packageFormat, const QString &sourcePackage, const QString &packageRoot)
0092 {
0093     auto structOrErr = PackageJobPrivate::loadStructure(packageFormat);
0094     if (auto structure = structOrErr.structure) {
0095         Package package(structure);
0096         package.setPath(sourcePackage);
0097         QString dest = packageRoot.isEmpty() ? package.defaultPackageRoot() : packageRoot;
0098         PackageLoader::invalidateCache();
0099 
0100         // use absolute paths if passed, otherwise go under share
0101         if (!QDir::isAbsolutePath(dest)) {
0102             dest = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + dest;
0103         }
0104         auto job = new PackageJob(Install, package, sourcePackage, dest);
0105         job->start();
0106         return job;
0107     } else {
0108         return structOrErr.errorJob;
0109     }
0110 }
0111 
0112 PackageJob *PackageJob::update(const QString &packageFormat, const QString &sourcePackage, const QString &packageRoot)
0113 {
0114     auto structOrErr = PackageJobPrivate::loadStructure(packageFormat);
0115     if (auto structure = structOrErr.structure) {
0116         Package package(structure);
0117         package.setPath(sourcePackage);
0118         QString dest = packageRoot.isEmpty() ? package.defaultPackageRoot() : packageRoot;
0119         PackageLoader::invalidateCache();
0120 
0121         // use absolute paths if passed, otherwise go under share
0122         if (!QDir::isAbsolutePath(dest)) {
0123             dest = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + dest;
0124         }
0125         auto job = new PackageJob(Update, package, sourcePackage, dest);
0126         job->start();
0127         return job;
0128     } else {
0129         return structOrErr.errorJob;
0130     }
0131 }
0132 
0133 PackageJob *PackageJob::uninstall(const QString &packageFormat, const QString &pluginId, const QString &packageRoot)
0134 {
0135     auto structOrErr = PackageJobPrivate::loadStructure(packageFormat);
0136     if (auto structure = structOrErr.structure) {
0137         Package package(structure);
0138         QString uninstallPath;
0139         // We handle the empty path when uninstalling the package
0140         // If the dir already got deleted the pluginId is an empty string, without this
0141         // check we would delete the package root, BUG: 410682
0142         if (!pluginId.isEmpty()) {
0143             uninstallPath = packageRoot + QLatin1Char('/') + pluginId;
0144         }
0145         package.setPath(uninstallPath);
0146 
0147         PackageLoader::invalidateCache();
0148         auto job = new PackageJob(Uninstall, package, QString(), QString());
0149         job->start();
0150         return job;
0151     } else {
0152         return structOrErr.errorJob;
0153     }
0154 }
0155 
0156 KPackage::Package PackageJob::package() const
0157 {
0158     return d->package;
0159 }
0160 void PackageJob::setupNotificationsOnJobFinished(const QString &messageName)
0161 {
0162     // capture first as uninstalling wipes d->package
0163     // or d-package can become dangling during the job if deleted externally
0164     const QString pluginId = d->package.metadata().pluginId();
0165     const QString kpackageType = readKPackageType(d->package.metadata());
0166 
0167     auto onJobFinished = [=](bool ok, JobError errorCode, const QString &error) {
0168         if (ok) {
0169             auto msg = QDBusMessage::createSignal(QStringLiteral("/KPackage/") + kpackageType, QStringLiteral("org.kde.plasma.kpackage"), messageName);
0170             msg.setArguments({pluginId});
0171             QDBusConnection::sessionBus().send(msg);
0172         }
0173 
0174         if (ok) {
0175             setError(NoError);
0176         } else {
0177             setError(errorCode);
0178             setErrorText(error);
0179         }
0180         emitResult();
0181     };
0182     connect(d->thread, &PackageJobThread::jobThreadFinished, this, onJobFinished, Qt::QueuedConnection);
0183 }
0184 
0185 } // namespace KPackage
0186 
0187 #include "moc_packagejob.cpp"