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 | |
26 | namespace KPackage |
27 | { |
28 | struct StructureOrErrorJob { |
29 | PackageStructure *structure = nullptr; |
30 | PackageJob *errorJob = nullptr; |
31 | }; |
32 | class PackageJobPrivate |
33 | { |
34 | public: |
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 | |
54 | PackageJob::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 | |
79 | PackageJob::~PackageJob() = default; |
80 | |
81 | void 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 | |
91 | PackageJob *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 | |
112 | PackageJob *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 | |
133 | PackageJob *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 | |
156 | KPackage::Package PackageJob::package() const |
157 | { |
158 | return d->package; |
159 | } |
160 | void 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 | |