1/*
2 SPDX-FileCopyrightText: 2012 Sebastian Kügler <sebas@kde.org>
3 SPDX-FileCopyrightText: 2023 Alexander Lohnau <alexander.lohnau@gmx.de>
4
5 SPDX-License-Identifier: LGPL-2.0-or-later
6*/
7
8#include "packagejob.h"
9
10#include "config-package.h"
11#include "packageloader.h"
12#include "packagestructure.h"
13#include "private/package_p.h"
14#include "private/packagejobthread_p.h"
15#include "private/utils.h"
16
17#include "kpackage_debug.h"
18
19#include <QDBusConnection>
20#include <QDBusMessage>
21#include <QDebug>
22#include <QStandardPaths>
23#include <QThreadPool>
24#include <QTimer>
25
26namespace KPackage
27{
28struct StructureOrErrorJob {
29 PackageStructure *structure = nullptr;
30 PackageJob *errorJob = nullptr;
31};
32class PackageJobPrivate
33{
34public:
35 static StructureOrErrorJob loadStructure(const QString &packageFormat)
36 {
37 if (auto structure = PackageLoader::self()->loadPackageStructure(packageFormat)) {
38 return StructureOrErrorJob{.structure: structure, .errorJob: nullptr};
39 } else {
40 auto job = new PackageJob(PackageJob::Install, Package(), QString(), QString());
41 job->setErrorText(QStringLiteral("Could not load package structure ") + packageFormat);
42 job->setError(PackageJob::JobError::InvalidPackageStructure);
43 QTimer::singleShot(interval: 0, receiver: job, slot: [job]() {
44 job->emitResult();
45 });
46 return StructureOrErrorJob{.structure: nullptr, .errorJob: job};
47 }
48 }
49 PackageJobThread *thread = nullptr;
50 Package package;
51 QString installPath;
52};
53
54PackageJob::PackageJob(OperationType type, const Package &package, const QString &src, const QString &dest)
55 : KJob()
56 , d(new PackageJobPrivate)
57{
58 d->thread = new PackageJobThread(type, src, dest, package);
59 d->package = package;
60
61 if (type == Install) {
62 setupNotificationsOnJobFinished(QStringLiteral("packageInstalled"));
63 } else if (type == Update) {
64 setupNotificationsOnJobFinished(QStringLiteral("packageUpdated"));
65 d->thread->update(src, dest, package);
66 } else if (type == Uninstall) {
67 setupNotificationsOnJobFinished(QStringLiteral("packageUninstalled"));
68 } else {
69 Q_UNREACHABLE();
70 }
71 connect(sender: d->thread, signal: &PackageJobThread::installPathChanged, context: this, slot: [this](const QString &installPath) {
72 d->package.setPath(installPath);
73 });
74 connect(sender: d->thread, signal: &PackageJobThread::jobThreadFinished, context: this, slot: [this]() {
75 emitResult();
76 });
77}
78
79PackageJob::~PackageJob() = default;
80
81void PackageJob::start()
82{
83 if (d->thread) {
84 QThreadPool::globalInstance()->start(runnable: d->thread);
85 d->thread = nullptr;
86 } else {
87 qCWarning(KPACKAGE_LOG) << "The KPackage::PackageJob was already started";
88 }
89}
90
91PackageJob *PackageJob::install(const QString &packageFormat, const QString &sourcePackage, const QString &packageRoot)
92{
93 auto structOrErr = PackageJobPrivate::loadStructure(packageFormat);
94 if (auto structure = structOrErr.structure) {
95 Package package(structure);
96 package.setPath(sourcePackage);
97 QString dest = packageRoot.isEmpty() ? package.defaultPackageRoot() : packageRoot;
98 PackageLoader::invalidateCache();
99
100 // use absolute paths if passed, otherwise go under share
101 if (!QDir::isAbsolutePath(path: dest)) {
102 dest = QStandardPaths::writableLocation(type: QStandardPaths::GenericDataLocation) + QLatin1Char('/') + dest;
103 }
104 auto job = new PackageJob(Install, package, sourcePackage, dest);
105 job->start();
106 return job;
107 } else {
108 return structOrErr.errorJob;
109 }
110}
111
112PackageJob *PackageJob::update(const QString &packageFormat, const QString &sourcePackage, const QString &packageRoot)
113{
114 auto structOrErr = PackageJobPrivate::loadStructure(packageFormat);
115 if (auto structure = structOrErr.structure) {
116 Package package(structure);
117 package.setPath(sourcePackage);
118 QString dest = packageRoot.isEmpty() ? package.defaultPackageRoot() : packageRoot;
119 PackageLoader::invalidateCache();
120
121 // use absolute paths if passed, otherwise go under share
122 if (!QDir::isAbsolutePath(path: dest)) {
123 dest = QStandardPaths::writableLocation(type: QStandardPaths::GenericDataLocation) + QLatin1Char('/') + dest;
124 }
125 auto job = new PackageJob(Update, package, sourcePackage, dest);
126 job->start();
127 return job;
128 } else {
129 return structOrErr.errorJob;
130 }
131}
132
133PackageJob *PackageJob::uninstall(const QString &packageFormat, const QString &pluginId, const QString &packageRoot)
134{
135 auto structOrErr = PackageJobPrivate::loadStructure(packageFormat);
136 if (auto structure = structOrErr.structure) {
137 Package package(structure);
138 QString uninstallPath;
139 // We handle the empty path when uninstalling the package
140 // If the dir already got deleted the pluginId is an empty string, without this
141 // check we would delete the package root, BUG: 410682
142 if (!pluginId.isEmpty()) {
143 uninstallPath = packageRoot + QLatin1Char('/') + pluginId;
144 }
145 package.setPath(uninstallPath);
146
147 PackageLoader::invalidateCache();
148 auto job = new PackageJob(Uninstall, package, QString(), QString());
149 job->start();
150 return job;
151 } else {
152 return structOrErr.errorJob;
153 }
154}
155
156KPackage::Package PackageJob::package() const
157{
158 return d->package;
159}
160void PackageJob::setupNotificationsOnJobFinished(const QString &messageName)
161{
162 // capture first as uninstalling wipes d->package
163 // or d-package can become dangling during the job if deleted externally
164 const QString pluginId = d->package.metadata().pluginId();
165 const QString kpackageType = readKPackageType(metaData: d->package.metadata());
166
167 auto onJobFinished = [=, this](bool ok, JobError errorCode, const QString &error) {
168 if (ok) {
169 auto msg = QDBusMessage::createSignal(QStringLiteral("/KPackage/") + kpackageType, QStringLiteral("org.kde.plasma.kpackage"), name: messageName);
170 msg.setArguments({pluginId});
171 QDBusConnection::sessionBus().send(message: msg);
172 }
173
174 if (ok) {
175 setError(NoError);
176 } else {
177 setError(errorCode);
178 setErrorText(error);
179 }
180 emitResult();
181 };
182 connect(sender: d->thread, signal: &PackageJobThread::jobThreadFinished, context: this, slot&: onJobFinished, type: Qt::QueuedConnection);
183}
184
185} // namespace KPackage
186
187#include "moc_packagejob.cpp"
188

source code of kpackage/src/kpackage/packagejob.cpp