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

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