File indexing completed on 2024-12-01 03:41:14

0001 /*
0002     SPDX-FileCopyrightText: 2008 Rob Scheepmaker <r.scheepmaker@student.utwente.nl>
0003     SPDX-FileCopyrightText: 2010 Shaun Reich <shaun.reich@kdemail.net>
0004     SPDX-FileCopyrightText: 2021 Kai Uwe Broulik <kde@broulik.de>
0005 
0006     SPDX-License-Identifier: LGPL-2.1-or-later
0007 */
0008 
0009 #include "kdynamicjobtracker_p.h"
0010 #include "kio_widgets_debug.h"
0011 #include "kuiserver_interface.h"
0012 
0013 #include <KJobTrackerInterface>
0014 #include <KUiServerJobTracker>
0015 #include <KUiServerV2JobTracker>
0016 #include <KWidgetJobTracker>
0017 #include <kio/jobtracker.h>
0018 
0019 #include <QApplication>
0020 #include <QDBusConnection>
0021 #include <QDBusConnectionInterface>
0022 #include <QDBusServiceWatcher>
0023 #include <QMap>
0024 #include <QXmlStreamReader>
0025 
0026 struct AllTrackers {
0027     KUiServerJobTracker *kuiserverTracker;
0028     KUiServerV2JobTracker *kuiserverV2Tracker;
0029     KWidgetJobTracker *widgetTracker;
0030 };
0031 
0032 class KDynamicJobTrackerPrivate
0033 {
0034 public:
0035     KDynamicJobTrackerPrivate()
0036     {
0037     }
0038 
0039     ~KDynamicJobTrackerPrivate()
0040     {
0041         delete kuiserverTracker;
0042         delete kuiserverV2Tracker;
0043         delete widgetTracker;
0044     }
0045 
0046     static bool hasDBusInterface(const QString &introspectionData, const QString &interface)
0047     {
0048         QXmlStreamReader xml(introspectionData);
0049         while (!xml.atEnd() && !xml.hasError()) {
0050             xml.readNext();
0051 
0052             if (xml.tokenType() == QXmlStreamReader::StartElement && xml.name() == QLatin1String("interface")) {
0053                 if (xml.attributes().value(QLatin1String("name")) == interface) {
0054                     return true;
0055                 }
0056             }
0057         }
0058         return false;
0059     }
0060 
0061     KUiServerJobTracker *kuiserverTracker = nullptr;
0062     KUiServerV2JobTracker *kuiserverV2Tracker = nullptr;
0063     KWidgetJobTracker *widgetTracker = nullptr;
0064     QMap<KJob *, AllTrackers> trackers;
0065 
0066     enum JobViewServerSupport {
0067         NeedsChecking,
0068         Error,
0069         V2Supported,
0070         V2NotSupported,
0071     };
0072     JobViewServerSupport jobViewServerSupport = NeedsChecking;
0073     QDBusServiceWatcher *jobViewServerWatcher = nullptr;
0074 };
0075 
0076 KDynamicJobTracker::KDynamicJobTracker(QObject *parent)
0077     : KJobTrackerInterface(parent)
0078     , d(new KDynamicJobTrackerPrivate)
0079 {
0080 }
0081 
0082 KDynamicJobTracker::~KDynamicJobTracker() = default;
0083 
0084 void KDynamicJobTracker::registerJob(KJob *job)
0085 {
0086     if (d->trackers.contains(job)) {
0087         return;
0088     }
0089 
0090     // only interested in finished() signal,
0091     // so catching ourselves instead of using KJobTrackerInterface::registerJob()
0092     connect(job, &KJob::finished, this, &KDynamicJobTracker::unregisterJob);
0093 
0094     const bool canHaveWidgets = (qobject_cast<QApplication *>(qApp) != nullptr);
0095 
0096     // always add an entry, even with no trackers used at all,
0097     // so unregisterJob() will work as normal
0098     AllTrackers &trackers = d->trackers[job];
0099     trackers.kuiserverTracker = nullptr;
0100     trackers.kuiserverV2Tracker = nullptr;
0101     trackers.widgetTracker = nullptr;
0102 
0103     auto useWidgetsFallback = [this, canHaveWidgets, &trackers, job] {
0104         if (canHaveWidgets) {
0105             // fallback to widget tracker only!
0106             if (!d->widgetTracker) {
0107                 d->widgetTracker = new KWidgetJobTracker();
0108             }
0109 
0110             trackers.widgetTracker = d->widgetTracker;
0111             trackers.widgetTracker->registerJob(job);
0112         }
0113     };
0114 
0115     // do not try to use kuiserver on Windows/macOS
0116 #if defined(Q_OS_WIN) || defined(Q_OS_MAC)
0117     useWidgetsFallback();
0118     return;
0119 #endif
0120 
0121     // do not try to query kuiserver if dbus is not available
0122     if (!QDBusConnection::sessionBus().interface()) {
0123         useWidgetsFallback();
0124         return;
0125     }
0126 
0127     const QString kuiserverService = QStringLiteral("org.kde.kuiserver");
0128 
0129     if (!d->jobViewServerWatcher) {
0130         d->jobViewServerWatcher = new QDBusServiceWatcher(kuiserverService,
0131                                                           QDBusConnection::sessionBus(),
0132                                                           QDBusServiceWatcher::WatchForOwnerChange | QDBusServiceWatcher::WatchForUnregistration,
0133                                                           this);
0134         connect(d->jobViewServerWatcher, &QDBusServiceWatcher::serviceOwnerChanged, this, [this] {
0135             d->jobViewServerSupport = KDynamicJobTrackerPrivate::NeedsChecking;
0136         });
0137     }
0138 
0139     if (d->jobViewServerSupport == KDynamicJobTrackerPrivate::NeedsChecking) {
0140         // Unfortunately no DBus ObjectManager support in Qt DBus.
0141         QDBusMessage msg = QDBusMessage::createMethodCall(kuiserverService,
0142                                                           QStringLiteral("/JobViewServer"),
0143                                                           QStringLiteral("org.freedesktop.DBus.Introspectable"),
0144                                                           QStringLiteral("Introspect"));
0145         auto reply = QDBusConnection::sessionBus().call(msg);
0146         if (reply.type() == QDBusMessage::ErrorMessage || reply.arguments().count() != 1) {
0147             qCWarning(KIO_WIDGETS) << "Failed to check which JobView API is supported" << reply.errorMessage();
0148             d->jobViewServerSupport = KDynamicJobTrackerPrivate::Error;
0149         } else {
0150             const QString introspectionData = reply.arguments().first().toString();
0151 
0152             if (KDynamicJobTrackerPrivate::hasDBusInterface(introspectionData, QStringLiteral("org.kde.JobViewServerV2"))) {
0153                 d->jobViewServerSupport = KDynamicJobTrackerPrivate::V2Supported;
0154             } else {
0155                 d->jobViewServerSupport = KDynamicJobTrackerPrivate::V2NotSupported;
0156             }
0157         }
0158     }
0159 
0160     if (d->jobViewServerSupport == KDynamicJobTrackerPrivate::V2Supported) {
0161         if (!d->kuiserverV2Tracker) {
0162             d->kuiserverV2Tracker = new KUiServerV2JobTracker();
0163         }
0164 
0165         trackers.kuiserverV2Tracker = d->kuiserverV2Tracker;
0166         trackers.kuiserverV2Tracker->registerJob(job);
0167         return;
0168     }
0169 
0170     // No point in trying to set up V1 if calling the service above failed.
0171     if (d->jobViewServerSupport != KDynamicJobTrackerPrivate::Error) {
0172         if (!d->kuiserverTracker) {
0173             d->kuiserverTracker = new KUiServerJobTracker();
0174         }
0175 
0176         trackers.kuiserverTracker = d->kuiserverTracker;
0177         trackers.kuiserverTracker->registerJob(job);
0178     }
0179 
0180     if (canHaveWidgets) {
0181         bool needsWidgetTracker = d->jobViewServerSupport == KDynamicJobTrackerPrivate::Error;
0182 
0183         if (!needsWidgetTracker) {
0184             org::kde::kuiserver interface(kuiserverService, QStringLiteral("/JobViewServer"), QDBusConnection::sessionBus(), this);
0185             QDBusReply<bool> reply = interface.requiresJobTracker();
0186             needsWidgetTracker = !reply.isValid() || reply.value();
0187         }
0188 
0189         // If kuiserver isn't available or it tells us a job tracker is required
0190         // create a widget tracker.
0191         if (needsWidgetTracker) {
0192             if (!d->widgetTracker) {
0193                 d->widgetTracker = new KWidgetJobTracker();
0194             }
0195             trackers.widgetTracker = d->widgetTracker;
0196             trackers.widgetTracker->registerJob(job);
0197         }
0198     }
0199 }
0200 
0201 void KDynamicJobTracker::unregisterJob(KJob *job)
0202 {
0203     job->disconnect(this);
0204 
0205     QMap<KJob *, AllTrackers>::Iterator it = d->trackers.find(job);
0206 
0207     if (it == d->trackers.end()) {
0208         qCWarning(KIO_WIDGETS) << "Tried to unregister a kio job that hasn't been registered.";
0209         return;
0210     }
0211 
0212     const AllTrackers &trackers = it.value();
0213     KUiServerJobTracker *kuiserverTracker = trackers.kuiserverTracker;
0214     KUiServerV2JobTracker *kuiserverV2Tracker = trackers.kuiserverV2Tracker;
0215     KWidgetJobTracker *widgetTracker = trackers.widgetTracker;
0216 
0217     if (kuiserverTracker) {
0218         kuiserverTracker->unregisterJob(job);
0219     }
0220 
0221     if (kuiserverV2Tracker) {
0222         kuiserverV2Tracker->unregisterJob(job);
0223     }
0224 
0225     if (widgetTracker) {
0226         widgetTracker->unregisterJob(job);
0227     }
0228 
0229     d->trackers.erase(it);
0230 }
0231 
0232 Q_GLOBAL_STATIC(KDynamicJobTracker, globalJobTracker)
0233 
0234 // Simply linking to this library, creates a GUI job tracker for all KIO jobs
0235 static int registerDynamicJobTracker()
0236 {
0237     KIO::setJobTracker(globalJobTracker());
0238 
0239     return 0; // something
0240 }
0241 
0242 Q_CONSTRUCTOR_FUNCTION(registerDynamicJobTracker)
0243 
0244 #include "moc_kdynamicjobtracker_p.cpp"