File indexing completed on 2024-05-12 15:43:56

0001 /*
0002     SPDX-FileCopyrightText: 2020 Dan Leinir Turthra Jensen <admin@leinir.dk>
0003 
0004     SPDX-License-Identifier: LGPL-2.1-or-later
0005 */
0006 
0007 #include "kpackagejob.h"
0008 
0009 #include <knewstuffcore_debug.h>
0010 
0011 #include <KLocalizedString>
0012 
0013 #include <KPackage/Package>
0014 #include <KPackage/PackageLoader>
0015 #include <KPackage/PackageStructure>
0016 
0017 #include <QCoreApplication>
0018 #include <QRunnable>
0019 #include <QStandardPaths>
0020 #include <QThreadPool>
0021 #include <QTimer>
0022 
0023 using namespace KNSCore;
0024 
0025 enum Operation {
0026     UnknownOperation,
0027     InstallOperation,
0028     UpdateOperation,
0029     UninstallOperation,
0030 };
0031 class KPackageTask;
0032 class KNSCore::KPackageJobPrivate
0033 {
0034 public:
0035     KPackageJobPrivate()
0036     {
0037     }
0038 
0039     QString package;
0040     QString packageRoot;
0041     QString serviceType;
0042     Operation operation{UnknownOperation};
0043 
0044     KPackageTask *runnable{nullptr};
0045 };
0046 
0047 class KPackageTask : public QObject, public QRunnable
0048 {
0049     Q_OBJECT
0050 public:
0051     QString package;
0052     QString packageRoot;
0053     QString serviceType;
0054     Operation operation{UnknownOperation};
0055 
0056     explicit KPackageTask(QObject *parent = nullptr)
0057         : QObject(parent)
0058         , QRunnable()
0059     {
0060         // We'll handle our own deletion - otherwise we may end up deleted
0061         // before things have been read out that we need to have read
0062         // As this has to be set before QThreadPool runs things, we need to do so here
0063         setAutoDelete(false);
0064     };
0065     ~KPackageTask() override
0066     {
0067     }
0068     void run() override
0069     {
0070         qCDebug(KNEWSTUFFCORE) << "Attempting to perform an installation operation of type" << operation << "on the package" << package << "of type"
0071                                << serviceType << "in the package root" << packageRoot;
0072         int errorlevel{0};
0073         QString errordescription;
0074         // PackageStructure instances are managed internally by KPackage, never delete them
0075         KPackage::PackageStructure *structure = KPackage::PackageLoader::self()->loadPackageStructure(serviceType);
0076         if (structure) {
0077             qCDebug(KNEWSTUFFCORE) << "Service type understood";
0078             installer.reset(new KPackage::Package(structure));
0079             if (installer->hasValidStructure()) {
0080                 qCDebug(KNEWSTUFFCORE) << "Installer successfully created and has a valid structure";
0081                 switch (operation) {
0082                 case InstallOperation:
0083                     job.reset(installer->install(package, packageRoot));
0084                     break;
0085                 case UpdateOperation:
0086                     job.reset(installer->update(package, packageRoot));
0087                     break;
0088                 case UninstallOperation:
0089                     job.reset(installer->uninstall(package, packageRoot));
0090                     break;
0091                 case UnknownOperation:
0092                 default:
0093                     // This should really not be happening, can't create one of these without going through one
0094                     // of the functions below, so how'd you get it in this state?
0095                     break;
0096                 };
0097                 if (job) {
0098                     qCDebug(KNEWSTUFFCORE) << "Created job, now let's wait for it to do its thing...";
0099                     job->setAutoDelete(false);
0100                     QEventLoop loop;
0101                     connect(
0102                         job.get(),
0103                         &KJob::result,
0104                         this,
0105                         [&loop, &errordescription](KJob *job) { // clazy:exclude=lambda-in-connect
0106                             errordescription = job->errorText();
0107                             loop.exit(job->error());
0108                         },
0109                         Qt::BlockingQueuedConnection);
0110                     errorlevel = loop.exec();
0111                 } else {
0112                     errorlevel = 3;
0113                     errordescription = i18n(
0114                         "Failed to create a job for the package management task. This is usually because the package is invalid. We attempted to operate on "
0115                         "the package %1",
0116                         package);
0117                 }
0118             } else {
0119                 errorlevel = 2;
0120                 errordescription =
0121                     i18n("Could not create a package installer for the service type %1: The installer does not have a valid structure", serviceType);
0122             }
0123         } else {
0124             errorlevel = 1;
0125             errordescription = i18n("The service type %1 was not understood by the KPackage installer", serviceType);
0126         }
0127         if (errorlevel > 0) {
0128             Q_EMIT error(errorlevel, errordescription);
0129         }
0130         Q_EMIT result();
0131     }
0132     Q_SIGNAL void result();
0133     Q_SIGNAL void error(int errorCode, const QString &errorText);
0134 
0135 private:
0136     QScopedPointer<KPackage::Package> installer;
0137     QScopedPointer<KJob> job;
0138 };
0139 
0140 KPackageJob::KPackageJob(QObject *parent)
0141     : KJob(parent)
0142     , d(new KPackageJobPrivate)
0143 {
0144 }
0145 
0146 KPackageJob::~KPackageJob() = default;
0147 
0148 void KPackageJob::start()
0149 {
0150     if (d->runnable) {
0151         // refuse to start the task more than once
0152         return;
0153     }
0154     d->runnable = new KPackageTask(this);
0155     d->runnable->package = d->package;
0156     d->runnable->packageRoot = d->packageRoot;
0157     d->runnable->serviceType = d->serviceType;
0158     d->runnable->operation = d->operation;
0159     connect(
0160         d->runnable,
0161         &KPackageTask::error,
0162         this,
0163         [this](int errorCode, const QString &errorText) {
0164             setError(errorCode);
0165             setErrorText(errorText);
0166         },
0167         Qt::QueuedConnection);
0168     connect(
0169         d->runnable,
0170         &KPackageTask::result,
0171         this,
0172         [this]() {
0173             emitResult();
0174         },
0175         Qt::QueuedConnection);
0176     QThreadPool::globalInstance()->start(d->runnable);
0177 }
0178 
0179 KNSCore::KPackageJob *KNSCore::KPackageJob::install(const QString &sourcePackage, const QString &packageRoot, const QString &serviceType)
0180 {
0181     KPackageJob *job = new KPackageJob();
0182     job->d->package = sourcePackage;
0183     job->d->packageRoot = packageRoot;
0184     job->d->serviceType = serviceType;
0185     job->d->operation = InstallOperation;
0186     QTimer::singleShot(0, job, &KPackageJob::start);
0187     return job;
0188 }
0189 
0190 KPackageJob *KPackageJob::update(const QString &sourcePackage, const QString &packageRoot, const QString &serviceType)
0191 {
0192     KPackageJob *job = new KPackageJob();
0193     job->d->package = sourcePackage;
0194     job->d->packageRoot = packageRoot;
0195     job->d->serviceType = serviceType;
0196     job->d->operation = UpdateOperation;
0197     QTimer::singleShot(0, job, &KPackageJob::start);
0198     return job;
0199 }
0200 
0201 KPackageJob *KPackageJob::uninstall(const QString &packageName, const QString &packageRoot, const QString &serviceType)
0202 {
0203     KPackageJob *job = new KPackageJob();
0204     job->d->package = packageName;
0205     job->d->packageRoot = packageRoot;
0206     job->d->serviceType = serviceType;
0207     job->d->operation = UninstallOperation;
0208     QTimer::singleShot(0, job, &KPackageJob::start);
0209     return job;
0210 }
0211 
0212 #include "kpackagejob.moc"
0213 #include "moc_kpackagejob.cpp"