1 | /* |
2 | This file is part of the KDE project |
3 | SPDX-FileCopyrightText: 2008 Rafael Fernández López <ereslibre@kde.org> |
4 | SPDX-FileCopyrightText: 2007 Kevin Ottens <ervin@kde.org> |
5 | |
6 | SPDX-License-Identifier: LGPL-2.0-only |
7 | */ |
8 | |
9 | #include "kuiserverjobtracker.h" |
10 | #include "kuiserverjobtracker_p.h" |
11 | |
12 | #include "debug.h" |
13 | #include "jobviewiface.h" |
14 | |
15 | #include <KJob> |
16 | |
17 | #include <QtGlobal> |
18 | #include <QApplication> |
19 | #include <QDBusConnection> |
20 | #include <QIcon> |
21 | |
22 | Q_GLOBAL_STATIC(KSharedUiServerProxy, serverProxy) |
23 | |
24 | class Q_DECL_HIDDEN KUiServerJobTracker::Private |
25 | { |
26 | public: |
27 | Private(KUiServerJobTracker *parent) |
28 | : q(parent) |
29 | { |
30 | } |
31 | |
32 | KUiServerJobTracker *const q; |
33 | |
34 | void _k_killJob(); |
35 | |
36 | static void updateDestUrl(KJob *job, org::kde::JobViewV2 *jobView); |
37 | |
38 | QHash<KJob *, org::kde::JobViewV2 *> progressJobView; |
39 | |
40 | QMetaObject::Connection serverRegisteredConnection; |
41 | }; |
42 | |
43 | void KUiServerJobTracker::Private::_k_killJob() |
44 | { |
45 | org::kde::JobViewV2 *jobView = qobject_cast<org::kde::JobViewV2 *>(object: q->sender()); |
46 | |
47 | if (jobView) { |
48 | KJob *job = progressJobView.key(value: jobView); |
49 | |
50 | if (job) { |
51 | job->kill(verbosity: KJob::EmitResult); |
52 | } |
53 | } |
54 | } |
55 | |
56 | void KUiServerJobTracker::Private::updateDestUrl(KJob *job, org::kde::JobViewV2 *jobView) |
57 | { |
58 | const QVariant destUrl = job->property(name: "destUrl" ); |
59 | if (destUrl.isValid()) { |
60 | jobView->setDestUrl(QDBusVariant(destUrl)); |
61 | } |
62 | } |
63 | |
64 | KUiServerJobTracker::KUiServerJobTracker(QObject *parent) |
65 | : KJobTrackerInterface(parent) |
66 | , d(new Private(this)) |
67 | { |
68 | } |
69 | |
70 | KUiServerJobTracker::~KUiServerJobTracker() |
71 | { |
72 | if (!d->progressJobView.isEmpty()) { |
73 | qWarning() << "A KUiServerJobTracker instance contains" << d->progressJobView.size() << "stalled jobs" ; |
74 | } |
75 | |
76 | qDeleteAll(c: d->progressJobView); |
77 | } |
78 | |
79 | void KUiServerJobTracker::registerJob(KJob *job) |
80 | { |
81 | // Already registered job? |
82 | if (d->progressJobView.contains(key: job)) { |
83 | return; |
84 | } |
85 | |
86 | // Watch the server registering/unregistering and re-register the jobs as needed |
87 | if (!d->serverRegisteredConnection) { |
88 | d->serverRegisteredConnection = connect(sender: serverProxy(), signal: &KSharedUiServerProxy::serverRegistered, context: this, slot: [this]() { |
89 | // Remember the list of jobs to re-register and then delete the old ones |
90 | const QList<KJob *> staleJobs = d->progressJobView.keys(); |
91 | |
92 | qDeleteAll(c: d->progressJobView); |
93 | d->progressJobView.clear(); |
94 | |
95 | for (KJob *job : staleJobs) { |
96 | registerJob(job); |
97 | } |
98 | }); |
99 | } |
100 | |
101 | const QString appName = QCoreApplication::applicationName(); |
102 | // This will only work if main() used QIcon::fromTheme. |
103 | QString programIconName = QApplication::windowIcon().name(); |
104 | |
105 | if (programIconName.isEmpty()) { |
106 | programIconName = appName; |
107 | } |
108 | |
109 | QPointer<KJob> jobWatch = job; |
110 | QDBusReply<QDBusObjectPath> reply = serverProxy()->uiserver()->requestView(appName, appIconName: programIconName, capabilities: job->capabilities()); |
111 | |
112 | // If we got a valid reply, register the interface for later usage. |
113 | if (reply.isValid()) { |
114 | org::kde::JobViewV2 *jobView = new org::kde::JobViewV2(QStringLiteral("org.kde.JobViewServer" ), reply.value().path(), QDBusConnection::sessionBus()); |
115 | if (!jobWatch) { |
116 | // qCDebug(KJOBWIDGETS) << "deleted out from under us when asking the server proxy for the view"; |
117 | jobView->terminate(errorMessage: QString()); |
118 | delete jobView; |
119 | return; |
120 | } |
121 | |
122 | QObject::connect(sender: jobView, SIGNAL(cancelRequested()), receiver: this, SLOT(_k_killJob())); |
123 | QObject::connect(sender: jobView, signal: &org::kde::JobViewV2::suspendRequested, context: job, slot: &KJob::suspend); |
124 | QObject::connect(sender: jobView, signal: &org::kde::JobViewV2::resumeRequested, context: job, slot: &KJob::resume); |
125 | |
126 | d->updateDestUrl(job, jobView); |
127 | |
128 | if (!jobWatch) { |
129 | // qCDebug(KJOBWIDGETS) << "deleted out from under us when creating the dbus interface"; |
130 | jobView->terminate(errorMessage: QString()); |
131 | delete jobView; |
132 | return; |
133 | } |
134 | |
135 | d->progressJobView.insert(key: job, value: jobView); |
136 | } else if (!jobWatch) { |
137 | qWarning() << "Uh-oh...KUiServerJobTracker was trying to forward a job, but it was deleted from under us." |
138 | << "kuiserver *may* have a stranded job. we can't do anything about it because the returned objectPath is invalid." ; |
139 | return; |
140 | } |
141 | |
142 | KJobTrackerInterface::registerJob(job); |
143 | } |
144 | |
145 | void KUiServerJobTracker::unregisterJob(KJob *job) |
146 | { |
147 | KJobTrackerInterface::unregisterJob(job); |
148 | |
149 | if (!d->progressJobView.contains(key: job)) { |
150 | return; |
151 | } |
152 | |
153 | org::kde::JobViewV2 *jobView = d->progressJobView.take(key: job); |
154 | |
155 | d->updateDestUrl(job, jobView); |
156 | |
157 | jobView->setError(job->error()); |
158 | |
159 | if (job->error()) { |
160 | jobView->terminate(errorMessage: job->errorText()); |
161 | } else { |
162 | jobView->terminate(errorMessage: QString()); |
163 | } |
164 | |
165 | delete jobView; |
166 | } |
167 | |
168 | void KUiServerJobTracker::finished(KJob *job) |
169 | { |
170 | if (!d->progressJobView.contains(key: job)) { |
171 | return; |
172 | } |
173 | |
174 | org::kde::JobViewV2 *jobView = d->progressJobView.take(key: job); |
175 | |
176 | d->updateDestUrl(job, jobView); |
177 | |
178 | jobView->setError(job->error()); |
179 | |
180 | if (job->error()) { |
181 | jobView->terminate(errorMessage: job->errorText()); |
182 | } else { |
183 | jobView->terminate(errorMessage: QString()); |
184 | } |
185 | } |
186 | |
187 | void KUiServerJobTracker::suspended(KJob *job) |
188 | { |
189 | if (!d->progressJobView.contains(key: job)) { |
190 | return; |
191 | } |
192 | |
193 | org::kde::JobViewV2 *jobView = d->progressJobView[job]; |
194 | |
195 | jobView->setSuspended(true); |
196 | } |
197 | |
198 | void KUiServerJobTracker::resumed(KJob *job) |
199 | { |
200 | if (!d->progressJobView.contains(key: job)) { |
201 | return; |
202 | } |
203 | |
204 | org::kde::JobViewV2 *jobView = d->progressJobView[job]; |
205 | |
206 | jobView->setSuspended(false); |
207 | } |
208 | |
209 | void KUiServerJobTracker::description(KJob *job, const QString &title, const QPair<QString, QString> &field1, const QPair<QString, QString> &field2) |
210 | { |
211 | if (!d->progressJobView.contains(key: job)) { |
212 | return; |
213 | } |
214 | |
215 | org::kde::JobViewV2 *jobView = d->progressJobView[job]; |
216 | |
217 | jobView->setInfoMessage(title); |
218 | |
219 | if (field1.first.isNull() || field1.second.isNull()) { |
220 | jobView->clearDescriptionField(number: 0); |
221 | } else { |
222 | jobView->setDescriptionField(number: 0, name: field1.first, value: field1.second); |
223 | } |
224 | |
225 | if (field2.first.isNull() || field2.second.isNull()) { |
226 | jobView->clearDescriptionField(number: 1); |
227 | } else { |
228 | jobView->setDescriptionField(number: 1, name: field2.first, value: field2.second); |
229 | } |
230 | } |
231 | |
232 | void KUiServerJobTracker::infoMessage(KJob *job, const QString &message) |
233 | { |
234 | if (!d->progressJobView.contains(key: job)) { |
235 | return; |
236 | } |
237 | |
238 | org::kde::JobViewV2 *jobView = d->progressJobView[job]; |
239 | |
240 | jobView->setInfoMessage(message); |
241 | } |
242 | |
243 | void KUiServerJobTracker::totalAmount(KJob *job, KJob::Unit unit, qulonglong amount) |
244 | { |
245 | if (!d->progressJobView.contains(key: job)) { |
246 | return; |
247 | } |
248 | |
249 | org::kde::JobViewV2 *jobView = d->progressJobView[job]; |
250 | |
251 | switch (unit) { |
252 | case KJob::Bytes: |
253 | jobView->setTotalAmount(amount, QStringLiteral("bytes" )); |
254 | break; |
255 | case KJob::Files: |
256 | jobView->setTotalAmount(amount, QStringLiteral("files" )); |
257 | break; |
258 | case KJob::Directories: |
259 | jobView->setTotalAmount(amount, QStringLiteral("dirs" )); |
260 | break; |
261 | case KJob::Items: |
262 | jobView->setTotalAmount(amount, QStringLiteral("items" )); |
263 | break; |
264 | case KJob::UnitsCount: |
265 | Q_UNREACHABLE(); |
266 | break; |
267 | } |
268 | } |
269 | |
270 | void KUiServerJobTracker::processedAmount(KJob *job, KJob::Unit unit, qulonglong amount) |
271 | { |
272 | if (!d->progressJobView.contains(key: job)) { |
273 | return; |
274 | } |
275 | |
276 | org::kde::JobViewV2 *jobView = d->progressJobView[job]; |
277 | |
278 | switch (unit) { |
279 | case KJob::Bytes: |
280 | jobView->setProcessedAmount(amount, QStringLiteral("bytes" )); |
281 | break; |
282 | case KJob::Files: |
283 | jobView->setProcessedAmount(amount, QStringLiteral("files" )); |
284 | break; |
285 | case KJob::Directories: |
286 | jobView->setProcessedAmount(amount, QStringLiteral("dirs" )); |
287 | break; |
288 | case KJob::Items: |
289 | jobView->setProcessedAmount(amount, QStringLiteral("items" )); |
290 | break; |
291 | case KJob::UnitsCount: |
292 | Q_UNREACHABLE(); |
293 | break; |
294 | } |
295 | } |
296 | |
297 | void KUiServerJobTracker::percent(KJob *job, unsigned long percent) |
298 | { |
299 | if (!d->progressJobView.contains(key: job)) { |
300 | return; |
301 | } |
302 | |
303 | org::kde::JobViewV2 *jobView = d->progressJobView[job]; |
304 | |
305 | jobView->setPercent(percent); |
306 | } |
307 | |
308 | void KUiServerJobTracker::speed(KJob *job, unsigned long value) |
309 | { |
310 | if (!d->progressJobView.contains(key: job)) { |
311 | return; |
312 | } |
313 | |
314 | org::kde::JobViewV2 *jobView = d->progressJobView[job]; |
315 | |
316 | jobView->setSpeed(value); |
317 | } |
318 | |
319 | KSharedUiServerProxy::KSharedUiServerProxy() |
320 | : m_uiserver(new org::kde::JobViewServer(QStringLiteral("org.kde.JobViewServer" ), QStringLiteral("/JobViewServer" ), QDBusConnection::sessionBus())) |
321 | , m_watcher(new QDBusServiceWatcher(QStringLiteral("org.kde.JobViewServer" ), QDBusConnection::sessionBus(), QDBusServiceWatcher::WatchForOwnerChange)) |
322 | { |
323 | QDBusConnectionInterface *bus = QDBusConnection::sessionBus().interface(); |
324 | if (!bus->isServiceRegistered(QStringLiteral("org.kde.JobViewServer" ))) { |
325 | QDBusReply<void> reply = bus->startService(QStringLiteral("org.kde.kuiserver" )); |
326 | if (!reply.isValid()) { |
327 | qCCritical(KJOBWIDGETS) << "Couldn't start kuiserver from org.kde.kuiserver.service:" << reply.error(); |
328 | return; |
329 | } |
330 | |
331 | if (!bus->isServiceRegistered(QStringLiteral("org.kde.JobViewServer" ))) { |
332 | qCDebug(KJOBWIDGETS) << "The dbus name org.kde.JobViewServer is STILL NOT REGISTERED, even after starting kuiserver. Should not happen." ; |
333 | return; |
334 | } |
335 | |
336 | qCDebug(KJOBWIDGETS) << "kuiserver registered" ; |
337 | } else { |
338 | qCDebug(KJOBWIDGETS) << "kuiserver found" ; |
339 | } |
340 | |
341 | connect(sender: m_watcher.get(), signal: &QDBusServiceWatcher::serviceOwnerChanged, context: this, slot: &KSharedUiServerProxy::uiserverOwnerChanged); |
342 | |
343 | // cleanup early enough to avoid issues with dbus at application exit |
344 | // see e.g. https://phabricator.kde.org/D2545 |
345 | qAddPostRoutine([]() { |
346 | serverProxy->m_uiserver.reset(); |
347 | serverProxy->m_watcher.reset(); |
348 | }); |
349 | } |
350 | |
351 | KSharedUiServerProxy::~KSharedUiServerProxy() |
352 | { |
353 | } |
354 | |
355 | org::kde::JobViewServer *KSharedUiServerProxy::uiserver() |
356 | { |
357 | return m_uiserver.get(); |
358 | } |
359 | |
360 | void KSharedUiServerProxy::uiserverOwnerChanged(const QString &serviceName, const QString &oldOwner, const QString &newOwner) |
361 | { |
362 | Q_UNUSED(serviceName); |
363 | Q_UNUSED(oldOwner); |
364 | |
365 | if (!newOwner.isEmpty()) { // registered |
366 | Q_EMIT serverRegistered(); |
367 | } else if (newOwner.isEmpty()) { // unregistered |
368 | Q_EMIT serverUnregistered(); |
369 | } |
370 | } |
371 | |
372 | #include "moc_kuiserverjobtracker.cpp" |
373 | #include "moc_kuiserverjobtracker_p.cpp" |
374 | |