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"