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

source code of kjobwidgets/src/kuiserverjobtracker.cpp