File indexing completed on 2024-10-13 09:35:32

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     // do not try to query kuiserver if dbus is not available
0104     if (!QDBusConnection::sessionBus().interface()) {
0105         if (canHaveWidgets) {
0106             // fallback to widget tracker only!
0107             if (!d->widgetTracker) {
0108                 d->widgetTracker = new KWidgetJobTracker();
0109             }
0110 
0111             trackers.widgetTracker = d->widgetTracker;
0112             trackers.widgetTracker->registerJob(job);
0113         }
0114         return;
0115     }
0116 
0117     const QString kuiserverService = QStringLiteral("org.kde.kuiserver");
0118 
0119     if (!d->jobViewServerWatcher) {
0120         d->jobViewServerWatcher = new QDBusServiceWatcher(kuiserverService,
0121                                                           QDBusConnection::sessionBus(),
0122                                                           QDBusServiceWatcher::WatchForOwnerChange | QDBusServiceWatcher::WatchForUnregistration,
0123                                                           this);
0124         connect(d->jobViewServerWatcher, &QDBusServiceWatcher::serviceOwnerChanged, this, [this] {
0125             d->jobViewServerSupport = KDynamicJobTrackerPrivate::NeedsChecking;
0126         });
0127     }
0128 
0129     if (d->jobViewServerSupport == KDynamicJobTrackerPrivate::NeedsChecking) {
0130         // Unfortunately no DBus ObjectManager support in Qt DBus.
0131         QDBusMessage msg = QDBusMessage::createMethodCall(kuiserverService,
0132                                                           QStringLiteral("/JobViewServer"),
0133                                                           QStringLiteral("org.freedesktop.DBus.Introspectable"),
0134                                                           QStringLiteral("Introspect"));
0135         auto reply = QDBusConnection::sessionBus().call(msg);
0136         if (reply.type() == QDBusMessage::ErrorMessage || reply.arguments().count() != 1) {
0137             qCWarning(KIO_WIDGETS) << "Failed to check which JobView API is supported" << reply.errorMessage();
0138             d->jobViewServerSupport = KDynamicJobTrackerPrivate::Error;
0139         } else {
0140             const QString introspectionData = reply.arguments().first().toString();
0141 
0142             if (KDynamicJobTrackerPrivate::hasDBusInterface(introspectionData, QStringLiteral("org.kde.JobViewServerV2"))) {
0143                 d->jobViewServerSupport = KDynamicJobTrackerPrivate::V2Supported;
0144             } else {
0145                 d->jobViewServerSupport = KDynamicJobTrackerPrivate::V2NotSupported;
0146             }
0147         }
0148     }
0149 
0150     if (d->jobViewServerSupport == KDynamicJobTrackerPrivate::V2Supported) {
0151         if (!d->kuiserverV2Tracker) {
0152             d->kuiserverV2Tracker = new KUiServerV2JobTracker();
0153         }
0154 
0155         trackers.kuiserverV2Tracker = d->kuiserverV2Tracker;
0156         trackers.kuiserverV2Tracker->registerJob(job);
0157         return;
0158     }
0159 
0160     // No point in trying to set up V1 if calling the service above failed.
0161     if (d->jobViewServerSupport != KDynamicJobTrackerPrivate::Error) {
0162         if (!d->kuiserverTracker) {
0163             d->kuiserverTracker = new KUiServerJobTracker();
0164         }
0165 
0166         trackers.kuiserverTracker = d->kuiserverTracker;
0167         trackers.kuiserverTracker->registerJob(job);
0168     }
0169 
0170     if (canHaveWidgets) {
0171         bool needsWidgetTracker = d->jobViewServerSupport == KDynamicJobTrackerPrivate::Error;
0172 
0173         if (!needsWidgetTracker) {
0174             org::kde::kuiserver interface(kuiserverService, QStringLiteral("/JobViewServer"), QDBusConnection::sessionBus(), this);
0175             QDBusReply<bool> reply = interface.requiresJobTracker();
0176             needsWidgetTracker = !reply.isValid() || reply.value();
0177         }
0178 
0179         // If kuiserver isn't available or it tells us a job tracker is required
0180         // create a widget tracker.
0181         if (needsWidgetTracker) {
0182             if (!d->widgetTracker) {
0183                 d->widgetTracker = new KWidgetJobTracker();
0184             }
0185             trackers.widgetTracker = d->widgetTracker;
0186             trackers.widgetTracker->registerJob(job);
0187         }
0188     }
0189 }
0190 
0191 void KDynamicJobTracker::unregisterJob(KJob *job)
0192 {
0193     job->disconnect(this);
0194 
0195     QMap<KJob *, AllTrackers>::Iterator it = d->trackers.find(job);
0196 
0197     if (it == d->trackers.end()) {
0198         qCWarning(KIO_WIDGETS) << "Tried to unregister a kio job that hasn't been registered.";
0199         return;
0200     }
0201 
0202     const AllTrackers &trackers = it.value();
0203     KUiServerJobTracker *kuiserverTracker = trackers.kuiserverTracker;
0204     KUiServerV2JobTracker *kuiserverV2Tracker = trackers.kuiserverV2Tracker;
0205     KWidgetJobTracker *widgetTracker = trackers.widgetTracker;
0206 
0207     if (kuiserverTracker) {
0208         kuiserverTracker->unregisterJob(job);
0209     }
0210 
0211     if (kuiserverV2Tracker) {
0212         kuiserverV2Tracker->unregisterJob(job);
0213     }
0214 
0215     if (widgetTracker) {
0216         widgetTracker->unregisterJob(job);
0217     }
0218 
0219     d->trackers.erase(it);
0220 }
0221 
0222 Q_GLOBAL_STATIC(KDynamicJobTracker, globalJobTracker)
0223 
0224 // Simply linking to this library, creates a GUI job tracker for all KIO jobs
0225 static int registerDynamicJobTracker()
0226 {
0227     KIO::setJobTracker(globalJobTracker());
0228 
0229     return 0; // something
0230 }
0231 
0232 Q_CONSTRUCTOR_FUNCTION(registerDynamicJobTracker)
0233 
0234 #include "moc_kdynamicjobtracker_p.cpp"