File indexing completed on 2024-04-14 14:25:51

0001 /*
0002     This file is part of the KDE project
0003     SPDX-FileCopyrightText: 2008 Rafael Fernández López <ereslibre@kde.org>
0004     SPDX-FileCopyrightText: 2007 Kevin Ottens <ervin@kde.org>
0005 
0006     SPDX-License-Identifier: LGPL-2.0-only
0007 */
0008 
0009 #include "kuiserverjobtracker.h"
0010 #include "kuiserverjobtracker_p.h"
0011 
0012 #include "debug.h"
0013 #include "jobviewiface.h"
0014 
0015 #include <KJob>
0016 
0017 #include <QtGlobal>
0018 #include <QApplication>
0019 #include <QDBusConnection>
0020 #include <QIcon>
0021 
0022 Q_GLOBAL_STATIC(KSharedUiServerProxy, serverProxy)
0023 
0024 class Q_DECL_HIDDEN KUiServerJobTracker::Private
0025 {
0026 public:
0027     Private(KUiServerJobTracker *parent)
0028         : q(parent)
0029     {
0030     }
0031 
0032     KUiServerJobTracker *const q;
0033 
0034     void _k_killJob();
0035 
0036     static void updateDestUrl(KJob *job, org::kde::JobViewV2 *jobView);
0037 
0038     QHash<KJob *, org::kde::JobViewV2 *> progressJobView;
0039 
0040     QMetaObject::Connection serverRegisteredConnection;
0041 };
0042 
0043 void KUiServerJobTracker::Private::_k_killJob()
0044 {
0045     org::kde::JobViewV2 *jobView = qobject_cast<org::kde::JobViewV2 *>(q->sender());
0046 
0047     if (jobView) {
0048         KJob *job = progressJobView.key(jobView);
0049 
0050         if (job) {
0051             job->kill(KJob::EmitResult);
0052         }
0053     }
0054 }
0055 
0056 void KUiServerJobTracker::Private::updateDestUrl(KJob *job, org::kde::JobViewV2 *jobView)
0057 {
0058     const QVariant destUrl = job->property("destUrl");
0059     if (destUrl.isValid()) {
0060         jobView->setDestUrl(QDBusVariant(destUrl));
0061     }
0062 }
0063 
0064 KUiServerJobTracker::KUiServerJobTracker(QObject *parent)
0065     : KJobTrackerInterface(parent)
0066     , d(new Private(this))
0067 {
0068 }
0069 
0070 KUiServerJobTracker::~KUiServerJobTracker()
0071 {
0072     if (!d->progressJobView.isEmpty()) {
0073         qWarning() << "A KUiServerJobTracker instance contains" << d->progressJobView.size() << "stalled jobs";
0074     }
0075 
0076     qDeleteAll(d->progressJobView);
0077 
0078     delete d;
0079 }
0080 
0081 void KUiServerJobTracker::registerJob(KJob *job)
0082 {
0083     // Already registered job?
0084     if (d->progressJobView.contains(job)) {
0085         return;
0086     }
0087 
0088     // Watch the server registering/unregistering and re-register the jobs as needed
0089     if (!d->serverRegisteredConnection) {
0090         d->serverRegisteredConnection = connect(serverProxy(), &KSharedUiServerProxy::serverRegistered, this, [this]() {
0091             // Remember the list of jobs to re-register and then delete the old ones
0092             const QList<KJob *> staleJobs = d->progressJobView.keys();
0093 
0094             qDeleteAll(d->progressJobView);
0095             d->progressJobView.clear();
0096 
0097             for (KJob *job : staleJobs) {
0098                 registerJob(job);
0099             }
0100         });
0101     }
0102 
0103     const QString appName = QCoreApplication::applicationName();
0104     // This will only work if main() used QIcon::fromTheme.
0105     QString programIconName = QApplication::windowIcon().name();
0106 
0107     if (programIconName.isEmpty()) {
0108         programIconName = appName;
0109     }
0110 
0111     QPointer<KJob> jobWatch = job;
0112     QDBusReply<QDBusObjectPath> reply = serverProxy()->uiserver()->requestView(appName, programIconName, job->capabilities());
0113 
0114     // If we got a valid reply, register the interface for later usage.
0115     if (reply.isValid()) {
0116         org::kde::JobViewV2 *jobView = new org::kde::JobViewV2(QStringLiteral("org.kde.JobViewServer"), reply.value().path(), QDBusConnection::sessionBus());
0117         if (!jobWatch) {
0118             // qCDebug(KJOBWIDGETS) << "deleted out from under us when asking the server proxy for the view";
0119             jobView->terminate(QString());
0120             delete jobView;
0121             return;
0122         }
0123 
0124         QObject::connect(jobView, SIGNAL(cancelRequested()), this, SLOT(_k_killJob()));
0125         QObject::connect(jobView, &org::kde::JobViewV2::suspendRequested, job, &KJob::suspend);
0126         QObject::connect(jobView, &org::kde::JobViewV2::resumeRequested, job, &KJob::resume);
0127 
0128         d->updateDestUrl(job, jobView);
0129 
0130         if (!jobWatch) {
0131             // qCDebug(KJOBWIDGETS) << "deleted out from under us when creating the dbus interface";
0132             jobView->terminate(QString());
0133             delete jobView;
0134             return;
0135         }
0136 
0137         d->progressJobView.insert(job, jobView);
0138     } else if (!jobWatch) {
0139         qWarning() << "Uh-oh...KUiServerJobTracker was trying to forward a job, but it was deleted from under us."
0140                    << "kuiserver *may* have a stranded job. we can't do anything about it because the returned objectPath is invalid.";
0141         return;
0142     }
0143 
0144     KJobTrackerInterface::registerJob(job);
0145 }
0146 
0147 void KUiServerJobTracker::unregisterJob(KJob *job)
0148 {
0149     KJobTrackerInterface::unregisterJob(job);
0150 
0151     if (!d->progressJobView.contains(job)) {
0152         return;
0153     }
0154 
0155     org::kde::JobViewV2 *jobView = d->progressJobView.take(job);
0156 
0157     d->updateDestUrl(job, jobView);
0158 
0159     jobView->setError(job->error());
0160 
0161     if (job->error()) {
0162         jobView->terminate(job->errorText());
0163     } else {
0164         jobView->terminate(QString());
0165     }
0166 
0167     delete jobView;
0168 }
0169 
0170 void KUiServerJobTracker::finished(KJob *job)
0171 {
0172     if (!d->progressJobView.contains(job)) {
0173         return;
0174     }
0175 
0176     org::kde::JobViewV2 *jobView = d->progressJobView.take(job);
0177 
0178     d->updateDestUrl(job, jobView);
0179 
0180     jobView->setError(job->error());
0181 
0182     if (job->error()) {
0183         jobView->terminate(job->errorText());
0184     } else {
0185         jobView->terminate(QString());
0186     }
0187 }
0188 
0189 void KUiServerJobTracker::suspended(KJob *job)
0190 {
0191     if (!d->progressJobView.contains(job)) {
0192         return;
0193     }
0194 
0195     org::kde::JobViewV2 *jobView = d->progressJobView[job];
0196 
0197     jobView->setSuspended(true);
0198 }
0199 
0200 void KUiServerJobTracker::resumed(KJob *job)
0201 {
0202     if (!d->progressJobView.contains(job)) {
0203         return;
0204     }
0205 
0206     org::kde::JobViewV2 *jobView = d->progressJobView[job];
0207 
0208     jobView->setSuspended(false);
0209 }
0210 
0211 void KUiServerJobTracker::description(KJob *job, const QString &title, const QPair<QString, QString> &field1, const QPair<QString, QString> &field2)
0212 {
0213     if (!d->progressJobView.contains(job)) {
0214         return;
0215     }
0216 
0217     org::kde::JobViewV2 *jobView = d->progressJobView[job];
0218 
0219     jobView->setInfoMessage(title);
0220 
0221     if (field1.first.isNull() || field1.second.isNull()) {
0222         jobView->clearDescriptionField(0);
0223     } else {
0224         jobView->setDescriptionField(0, field1.first, field1.second);
0225     }
0226 
0227     if (field2.first.isNull() || field2.second.isNull()) {
0228         jobView->clearDescriptionField(1);
0229     } else {
0230         jobView->setDescriptionField(1, field2.first, field2.second);
0231     }
0232 }
0233 
0234 void KUiServerJobTracker::infoMessage(KJob *job, const QString &plain, const QString &rich)
0235 {
0236     Q_UNUSED(rich)
0237 
0238     if (!d->progressJobView.contains(job)) {
0239         return;
0240     }
0241 
0242     org::kde::JobViewV2 *jobView = d->progressJobView[job];
0243 
0244     jobView->setInfoMessage(plain);
0245 }
0246 
0247 void KUiServerJobTracker::totalAmount(KJob *job, KJob::Unit unit, qulonglong amount)
0248 {
0249     if (!d->progressJobView.contains(job)) {
0250         return;
0251     }
0252 
0253     org::kde::JobViewV2 *jobView = d->progressJobView[job];
0254 
0255     switch (unit) {
0256     case KJob::Bytes:
0257         jobView->setTotalAmount(amount, QStringLiteral("bytes"));
0258         break;
0259     case KJob::Files:
0260         jobView->setTotalAmount(amount, QStringLiteral("files"));
0261         break;
0262     case KJob::Directories:
0263         jobView->setTotalAmount(amount, QStringLiteral("dirs"));
0264         break;
0265     case KJob::Items:
0266         jobView->setTotalAmount(amount, QStringLiteral("items"));
0267         break;
0268     case KJob::UnitsCount:
0269         Q_UNREACHABLE();
0270         break;
0271     }
0272 }
0273 
0274 void KUiServerJobTracker::processedAmount(KJob *job, KJob::Unit unit, qulonglong amount)
0275 {
0276     if (!d->progressJobView.contains(job)) {
0277         return;
0278     }
0279 
0280     org::kde::JobViewV2 *jobView = d->progressJobView[job];
0281 
0282     switch (unit) {
0283     case KJob::Bytes:
0284         jobView->setProcessedAmount(amount, QStringLiteral("bytes"));
0285         break;
0286     case KJob::Files:
0287         jobView->setProcessedAmount(amount, QStringLiteral("files"));
0288         break;
0289     case KJob::Directories:
0290         jobView->setProcessedAmount(amount, QStringLiteral("dirs"));
0291         break;
0292     case KJob::Items:
0293         jobView->setProcessedAmount(amount, QStringLiteral("items"));
0294         break;
0295     case KJob::UnitsCount:
0296         Q_UNREACHABLE();
0297         break;
0298     }
0299 }
0300 
0301 void KUiServerJobTracker::percent(KJob *job, unsigned long percent)
0302 {
0303     if (!d->progressJobView.contains(job)) {
0304         return;
0305     }
0306 
0307     org::kde::JobViewV2 *jobView = d->progressJobView[job];
0308 
0309     jobView->setPercent(percent);
0310 }
0311 
0312 void KUiServerJobTracker::speed(KJob *job, unsigned long value)
0313 {
0314     if (!d->progressJobView.contains(job)) {
0315         return;
0316     }
0317 
0318     org::kde::JobViewV2 *jobView = d->progressJobView[job];
0319 
0320     jobView->setSpeed(value);
0321 }
0322 
0323 KSharedUiServerProxy::KSharedUiServerProxy()
0324     : m_uiserver(new org::kde::JobViewServer(QStringLiteral("org.kde.JobViewServer"), QStringLiteral("/JobViewServer"), QDBusConnection::sessionBus()))
0325     , m_watcher(new QDBusServiceWatcher(QStringLiteral("org.kde.JobViewServer"), QDBusConnection::sessionBus(), QDBusServiceWatcher::WatchForOwnerChange))
0326 {
0327     QDBusConnectionInterface *bus = QDBusConnection::sessionBus().interface();
0328     if (!bus->isServiceRegistered(QStringLiteral("org.kde.JobViewServer"))) {
0329         QDBusReply<void> reply = bus->startService(QStringLiteral("org.kde.kuiserver"));
0330         if (!reply.isValid()) {
0331             qCCritical(KJOBWIDGETS) << "Couldn't start kuiserver from org.kde.kuiserver.service:" << reply.error();
0332             return;
0333         }
0334 
0335         if (!bus->isServiceRegistered(QStringLiteral("org.kde.JobViewServer"))) {
0336             qCDebug(KJOBWIDGETS) << "The dbus name org.kde.JobViewServer is STILL NOT REGISTERED, even after starting kuiserver. Should not happen.";
0337             return;
0338         }
0339 
0340         qCDebug(KJOBWIDGETS) << "kuiserver registered";
0341     } else {
0342         qCDebug(KJOBWIDGETS) << "kuiserver found";
0343     }
0344 
0345     connect(m_watcher.get(), &QDBusServiceWatcher::serviceOwnerChanged, this, &KSharedUiServerProxy::uiserverOwnerChanged);
0346 
0347     // cleanup early enough to avoid issues with dbus at application exit
0348     // see e.g. https://phabricator.kde.org/D2545
0349     qAddPostRoutine([]() {
0350         serverProxy->m_uiserver.reset();
0351         serverProxy->m_watcher.reset();
0352     });
0353 }
0354 
0355 KSharedUiServerProxy::~KSharedUiServerProxy()
0356 {
0357 }
0358 
0359 org::kde::JobViewServer *KSharedUiServerProxy::uiserver()
0360 {
0361     return m_uiserver.get();
0362 }
0363 
0364 void KSharedUiServerProxy::uiserverOwnerChanged(const QString &serviceName, const QString &oldOwner, const QString &newOwner)
0365 {
0366     Q_UNUSED(serviceName);
0367     Q_UNUSED(oldOwner);
0368 
0369     if (!newOwner.isEmpty()) { // registered
0370         Q_EMIT serverRegistered();
0371     } else if (newOwner.isEmpty()) { // unregistered
0372         Q_EMIT serverUnregistered();
0373     }
0374 }
0375 
0376 #include "moc_kuiserverjobtracker.cpp"
0377 #include "moc_kuiserverjobtracker_p.cpp"