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
22Q_GLOBAL_STATIC(KSharedUiServerProxy, serverProxy)
23
24class Q_DECL_HIDDEN KUiServerJobTracker::Private
25{
26public:
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
43void 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
56void 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
64KUiServerJobTracker::KUiServerJobTracker(QObject *parent)
65 : KJobTrackerInterface(parent)
66 , d(new Private(this))
67{
68}
69
70KUiServerJobTracker::~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
79void 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
145void 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
168void 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
187void 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
198void 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
209void 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
232void 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
243void 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
270void 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
297void 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
308void 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
319KSharedUiServerProxy::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
351KSharedUiServerProxy::~KSharedUiServerProxy()
352{
353}
354
355org::kde::JobViewServer *KSharedUiServerProxy::uiserver()
356{
357 return m_uiserver.get();
358}
359
360void 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

source code of kjobwidgets/src/kuiserverjobtracker.cpp