| 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 <KInhibitionJobTracker> |
| 14 | #include <KJobTrackerInterface> |
| 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 | |
| 26 | struct AllTrackers { |
| 27 | KUiServerV2JobTracker *kuiserverV2Tracker; |
| 28 | KWidgetJobTracker *widgetTracker; |
| 29 | KInhibitionJobTracker *inhibitionTracker; |
| 30 | }; |
| 31 | |
| 32 | class KDynamicJobTrackerPrivate |
| 33 | { |
| 34 | public: |
| 35 | KDynamicJobTrackerPrivate() |
| 36 | { |
| 37 | } |
| 38 | |
| 39 | ~KDynamicJobTrackerPrivate() |
| 40 | { |
| 41 | delete kuiserverV2Tracker; |
| 42 | delete widgetTracker; |
| 43 | delete inhibitionTracker; |
| 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 | KUiServerV2JobTracker *kuiserverV2Tracker = nullptr; |
| 62 | KWidgetJobTracker *widgetTracker = nullptr; |
| 63 | KInhibitionJobTracker *inhibitionTracker = 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 | |
| 76 | KDynamicJobTracker::KDynamicJobTracker(QObject *parent) |
| 77 | : KJobTrackerInterface(parent) |
| 78 | , d(new KDynamicJobTrackerPrivate) |
| 79 | { |
| 80 | } |
| 81 | |
| 82 | KDynamicJobTracker::~KDynamicJobTracker() = default; |
| 83 | |
| 84 | void 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.kuiserverV2Tracker = nullptr; |
| 100 | trackers.widgetTracker = nullptr; |
| 101 | trackers.inhibitionTracker = 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 | if (!job->property(name: "kio_no_inhibit_suspend" ).toBool()) { |
| 128 | if (!d->inhibitionTracker) { |
| 129 | d->inhibitionTracker = new KInhibitionJobTracker(); |
| 130 | } |
| 131 | |
| 132 | trackers.inhibitionTracker = d->inhibitionTracker; |
| 133 | trackers.inhibitionTracker->registerJob(job); |
| 134 | } |
| 135 | |
| 136 | const QString kuiserverService = QStringLiteral("org.kde.kuiserver" ); |
| 137 | |
| 138 | if (!d->jobViewServerWatcher) { |
| 139 | d->jobViewServerWatcher = new QDBusServiceWatcher(kuiserverService, |
| 140 | QDBusConnection::sessionBus(), |
| 141 | QDBusServiceWatcher::WatchForOwnerChange | QDBusServiceWatcher::WatchForUnregistration, |
| 142 | this); |
| 143 | connect(sender: d->jobViewServerWatcher, signal: &QDBusServiceWatcher::serviceOwnerChanged, context: this, slot: [this] { |
| 144 | d->jobViewServerSupport = KDynamicJobTrackerPrivate::NeedsChecking; |
| 145 | }); |
| 146 | } |
| 147 | |
| 148 | if (d->jobViewServerSupport == KDynamicJobTrackerPrivate::NeedsChecking) { |
| 149 | // Unfortunately no DBus ObjectManager support in Qt DBus. |
| 150 | QDBusMessage msg = QDBusMessage::createMethodCall(destination: kuiserverService, |
| 151 | QStringLiteral("/JobViewServer" ), |
| 152 | QStringLiteral("org.freedesktop.DBus.Introspectable" ), |
| 153 | QStringLiteral("Introspect" )); |
| 154 | auto reply = QDBusConnection::sessionBus().call(message: msg); |
| 155 | if (reply.type() == QDBusMessage::ErrorMessage || reply.arguments().count() != 1) { |
| 156 | qCWarning(KIO_WIDGETS) << "Failed to check which JobView API is supported" << reply.errorMessage(); |
| 157 | d->jobViewServerSupport = KDynamicJobTrackerPrivate::Error; |
| 158 | } else { |
| 159 | const QString introspectionData = reply.arguments().first().toString(); |
| 160 | |
| 161 | if (KDynamicJobTrackerPrivate::hasDBusInterface(introspectionData, QStringLiteral("org.kde.JobViewServerV2" ))) { |
| 162 | d->jobViewServerSupport = KDynamicJobTrackerPrivate::V2Supported; |
| 163 | } else { |
| 164 | d->jobViewServerSupport = KDynamicJobTrackerPrivate::V2NotSupported; |
| 165 | } |
| 166 | } |
| 167 | |
| 168 | org::kde::kuiserver interface(kuiserverService, QStringLiteral("/JobViewServer" ), QDBusConnection::sessionBus(), this); |
| 169 | |
| 170 | QDBusReply<bool> requiresTrackerReply = interface.requiresJobTracker(); |
| 171 | if (!requiresTrackerReply.isValid() || requiresTrackerReply.value()) { |
| 172 | d->jobViewServerSupport = KDynamicJobTrackerPrivate::Error; |
| 173 | } |
| 174 | |
| 175 | QDBusConnection::sessionBus().connect(service: kuiserverService, |
| 176 | QStringLiteral("/JobViewServer" ), |
| 177 | QStringLiteral("org.kde.kuiserver" ), |
| 178 | QStringLiteral("requiresJobTrackerChanged" ), |
| 179 | receiver: this, |
| 180 | SLOT(handleRequiresJobTrackerChanged(bool))); |
| 181 | } |
| 182 | |
| 183 | if (d->jobViewServerSupport == KDynamicJobTrackerPrivate::V2Supported) { |
| 184 | if (!d->kuiserverV2Tracker) { |
| 185 | d->kuiserverV2Tracker = new KUiServerV2JobTracker(); |
| 186 | } |
| 187 | |
| 188 | trackers.kuiserverV2Tracker = d->kuiserverV2Tracker; |
| 189 | trackers.kuiserverV2Tracker->registerJob(job); |
| 190 | return; |
| 191 | } |
| 192 | |
| 193 | // If kuiserver isn't available or it tells us a job tracker is required |
| 194 | // create a widget tracker. |
| 195 | if (d->jobViewServerSupport == KDynamicJobTrackerPrivate::Error || d->jobViewServerSupport == KDynamicJobTrackerPrivate::V2NotSupported) { |
| 196 | useWidgetsFallback(); |
| 197 | } |
| 198 | } |
| 199 | |
| 200 | void KDynamicJobTracker::unregisterJob(KJob *job) |
| 201 | { |
| 202 | job->disconnect(receiver: this); |
| 203 | |
| 204 | QMap<KJob *, AllTrackers>::Iterator it = d->trackers.find(key: job); |
| 205 | |
| 206 | if (it == d->trackers.end()) { |
| 207 | qCWarning(KIO_WIDGETS) << "Tried to unregister a kio job that hasn't been registered." ; |
| 208 | return; |
| 209 | } |
| 210 | |
| 211 | const AllTrackers &trackers = it.value(); |
| 212 | KUiServerV2JobTracker *kuiserverV2Tracker = trackers.kuiserverV2Tracker; |
| 213 | KWidgetJobTracker *widgetTracker = trackers.widgetTracker; |
| 214 | KInhibitionJobTracker *inhibitionTracker = trackers.inhibitionTracker; |
| 215 | |
| 216 | if (kuiserverV2Tracker) { |
| 217 | kuiserverV2Tracker->unregisterJob(job); |
| 218 | } |
| 219 | |
| 220 | if (widgetTracker) { |
| 221 | widgetTracker->unregisterJob(job); |
| 222 | } |
| 223 | |
| 224 | if (inhibitionTracker) { |
| 225 | inhibitionTracker->unregisterJob(job); |
| 226 | } |
| 227 | |
| 228 | d->trackers.erase(it); |
| 229 | } |
| 230 | |
| 231 | void KDynamicJobTracker::handleRequiresJobTrackerChanged(bool req) |
| 232 | { |
| 233 | if (req) { |
| 234 | d->jobViewServerSupport = KDynamicJobTrackerPrivate::Error; |
| 235 | } else { |
| 236 | d->jobViewServerSupport = KDynamicJobTrackerPrivate::V2Supported; |
| 237 | } |
| 238 | } |
| 239 | |
| 240 | Q_GLOBAL_STATIC(KDynamicJobTracker, globalJobTracker) |
| 241 | |
| 242 | // Simply linking to this library, creates a GUI job tracker for all KIO jobs |
| 243 | static int registerDynamicJobTracker() |
| 244 | { |
| 245 | KIO::setJobTracker(globalJobTracker()); |
| 246 | |
| 247 | return 0; // something |
| 248 | } |
| 249 | |
| 250 | Q_CONSTRUCTOR_FUNCTION(registerDynamicJobTracker) |
| 251 | |
| 252 | #include "moc_kdynamicjobtracker_p.cpp" |
| 253 | |