1/*
2 SPDX-FileCopyrightText: 2008 Rob Scheepmaker <r.scheepmaker@student.utwente.nl>
3 SPDX-FileCopyrightText: 2010 Shaun Reich <shaun.reich@kdemail.net>
4 SPDX-FileCopyrightText: 2021 Kai Uwe Broulik <kde@broulik.de>
5
6 SPDX-License-Identifier: LGPL-2.1-or-later
7*/
8
9#include "kdynamicjobtracker_p.h"
10#include "kio_widgets_debug.h"
11#include "kuiserver_interface.h"
12
13#include <KJobTrackerInterface>
14#include <KUiServerJobTracker>
15#include <KUiServerV2JobTracker>
16#include <KWidgetJobTracker>
17#include <kio/jobtracker.h>
18
19#include <QApplication>
20#include <QDBusConnection>
21#include <QDBusConnectionInterface>
22#include <QDBusServiceWatcher>
23#include <QMap>
24#include <QXmlStreamReader>
25
26struct AllTrackers {
27 KUiServerJobTracker *kuiserverTracker;
28 KUiServerV2JobTracker *kuiserverV2Tracker;
29 KWidgetJobTracker *widgetTracker;
30};
31
32class KDynamicJobTrackerPrivate
33{
34public:
35 KDynamicJobTrackerPrivate()
36 {
37 }
38
39 ~KDynamicJobTrackerPrivate()
40 {
41 delete kuiserverTracker;
42 delete kuiserverV2Tracker;
43 delete widgetTracker;
44 }
45
46 static bool hasDBusInterface(const QString &introspectionData, const QString &interface)
47 {
48 QXmlStreamReader xml(introspectionData);
49 while (!xml.atEnd() && !xml.hasError()) {
50 xml.readNext();
51
52 if (xml.tokenType() == QXmlStreamReader::StartElement && xml.name() == QLatin1String("interface")) {
53 if (xml.attributes().value(qualifiedName: QLatin1String("name")) == interface) {
54 return true;
55 }
56 }
57 }
58 return false;
59 }
60
61 KUiServerJobTracker *kuiserverTracker = nullptr;
62 KUiServerV2JobTracker *kuiserverV2Tracker = nullptr;
63 KWidgetJobTracker *widgetTracker = nullptr;
64 QMap<KJob *, AllTrackers> trackers;
65
66 enum JobViewServerSupport {
67 NeedsChecking,
68 Error,
69 V2Supported,
70 V2NotSupported,
71 };
72 JobViewServerSupport jobViewServerSupport = NeedsChecking;
73 QDBusServiceWatcher *jobViewServerWatcher = nullptr;
74};
75
76KDynamicJobTracker::KDynamicJobTracker(QObject *parent)
77 : KJobTrackerInterface(parent)
78 , d(new KDynamicJobTrackerPrivate)
79{
80}
81
82KDynamicJobTracker::~KDynamicJobTracker() = default;
83
84void KDynamicJobTracker::registerJob(KJob *job)
85{
86 if (d->trackers.contains(key: job)) {
87 return;
88 }
89
90 // only interested in finished() signal,
91 // so catching ourselves instead of using KJobTrackerInterface::registerJob()
92 connect(sender: job, signal: &KJob::finished, context: this, slot: &KDynamicJobTracker::unregisterJob);
93
94 const bool canHaveWidgets = (qobject_cast<QApplication *>(qApp) != nullptr);
95
96 // always add an entry, even with no trackers used at all,
97 // so unregisterJob() will work as normal
98 AllTrackers &trackers = d->trackers[job];
99 trackers.kuiserverTracker = nullptr;
100 trackers.kuiserverV2Tracker = nullptr;
101 trackers.widgetTracker = nullptr;
102
103 auto useWidgetsFallback = [this, canHaveWidgets, &trackers, job] {
104 if (canHaveWidgets) {
105 // fallback to widget tracker only!
106 if (!d->widgetTracker) {
107 d->widgetTracker = new KWidgetJobTracker();
108 }
109
110 trackers.widgetTracker = d->widgetTracker;
111 trackers.widgetTracker->registerJob(job);
112 }
113 };
114
115 // do not try to use kuiserver on Windows/macOS
116#if defined(Q_OS_WIN) || defined(Q_OS_MAC)
117 useWidgetsFallback();
118 return;
119#endif
120
121 // do not try to query kuiserver if dbus is not available
122 if (!QDBusConnection::sessionBus().interface()) {
123 useWidgetsFallback();
124 return;
125 }
126
127 const QString kuiserverService = QStringLiteral("org.kde.kuiserver");
128
129 if (!d->jobViewServerWatcher) {
130 d->jobViewServerWatcher = new QDBusServiceWatcher(kuiserverService,
131 QDBusConnection::sessionBus(),
132 QDBusServiceWatcher::WatchForOwnerChange | QDBusServiceWatcher::WatchForUnregistration,
133 this);
134 connect(sender: d->jobViewServerWatcher, signal: &QDBusServiceWatcher::serviceOwnerChanged, context: this, slot: [this] {
135 d->jobViewServerSupport = KDynamicJobTrackerPrivate::NeedsChecking;
136 });
137 }
138
139 if (d->jobViewServerSupport == KDynamicJobTrackerPrivate::NeedsChecking) {
140 // Unfortunately no DBus ObjectManager support in Qt DBus.
141 QDBusMessage msg = QDBusMessage::createMethodCall(destination: kuiserverService,
142 QStringLiteral("/JobViewServer"),
143 QStringLiteral("org.freedesktop.DBus.Introspectable"),
144 QStringLiteral("Introspect"));
145 auto reply = QDBusConnection::sessionBus().call(message: msg);
146 if (reply.type() == QDBusMessage::ErrorMessage || reply.arguments().count() != 1) {
147 qCWarning(KIO_WIDGETS) << "Failed to check which JobView API is supported" << reply.errorMessage();
148 d->jobViewServerSupport = KDynamicJobTrackerPrivate::Error;
149 } else {
150 const QString introspectionData = reply.arguments().first().toString();
151
152 if (KDynamicJobTrackerPrivate::hasDBusInterface(introspectionData, QStringLiteral("org.kde.JobViewServerV2"))) {
153 d->jobViewServerSupport = KDynamicJobTrackerPrivate::V2Supported;
154 } else {
155 d->jobViewServerSupport = KDynamicJobTrackerPrivate::V2NotSupported;
156 }
157 }
158 }
159
160 if (d->jobViewServerSupport == KDynamicJobTrackerPrivate::V2Supported) {
161 if (!d->kuiserverV2Tracker) {
162 d->kuiserverV2Tracker = new KUiServerV2JobTracker();
163 }
164
165 trackers.kuiserverV2Tracker = d->kuiserverV2Tracker;
166 trackers.kuiserverV2Tracker->registerJob(job);
167 return;
168 }
169
170 // No point in trying to set up V1 if calling the service above failed.
171 if (d->jobViewServerSupport != KDynamicJobTrackerPrivate::Error) {
172 if (!d->kuiserverTracker) {
173 d->kuiserverTracker = new KUiServerJobTracker();
174 }
175
176 trackers.kuiserverTracker = d->kuiserverTracker;
177 trackers.kuiserverTracker->registerJob(job);
178 }
179
180 if (canHaveWidgets) {
181 bool needsWidgetTracker = d->jobViewServerSupport == KDynamicJobTrackerPrivate::Error;
182
183 if (!needsWidgetTracker) {
184 org::kde::kuiserver interface(kuiserverService, QStringLiteral("/JobViewServer"), QDBusConnection::sessionBus(), this);
185 QDBusReply<bool> reply = interface.requiresJobTracker();
186 needsWidgetTracker = !reply.isValid() || reply.value();
187 }
188
189 // If kuiserver isn't available or it tells us a job tracker is required
190 // create a widget tracker.
191 if (needsWidgetTracker) {
192 if (!d->widgetTracker) {
193 d->widgetTracker = new KWidgetJobTracker();
194 }
195 trackers.widgetTracker = d->widgetTracker;
196 trackers.widgetTracker->registerJob(job);
197 }
198 }
199}
200
201void KDynamicJobTracker::unregisterJob(KJob *job)
202{
203 job->disconnect(receiver: this);
204
205 QMap<KJob *, AllTrackers>::Iterator it = d->trackers.find(key: job);
206
207 if (it == d->trackers.end()) {
208 qCWarning(KIO_WIDGETS) << "Tried to unregister a kio job that hasn't been registered.";
209 return;
210 }
211
212 const AllTrackers &trackers = it.value();
213 KUiServerJobTracker *kuiserverTracker = trackers.kuiserverTracker;
214 KUiServerV2JobTracker *kuiserverV2Tracker = trackers.kuiserverV2Tracker;
215 KWidgetJobTracker *widgetTracker = trackers.widgetTracker;
216
217 if (kuiserverTracker) {
218 kuiserverTracker->unregisterJob(job);
219 }
220
221 if (kuiserverV2Tracker) {
222 kuiserverV2Tracker->unregisterJob(job);
223 }
224
225 if (widgetTracker) {
226 widgetTracker->unregisterJob(job);
227 }
228
229 d->trackers.erase(it);
230}
231
232Q_GLOBAL_STATIC(KDynamicJobTracker, globalJobTracker)
233
234// Simply linking to this library, creates a GUI job tracker for all KIO jobs
235static int registerDynamicJobTracker()
236{
237 KIO::setJobTracker(globalJobTracker());
238
239 return 0; // something
240}
241
242Q_CONSTRUCTOR_FUNCTION(registerDynamicJobTracker)
243
244#include "moc_kdynamicjobtracker_p.cpp"
245

source code of kio/src/widgets/kdynamicjobtracker.cpp