File indexing completed on 2024-12-08 03:41:13

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 
0079 void KUiServerJobTracker::registerJob(KJob *job)
0080 {
0081     // Already registered job?
0082     if (d->progressJobView.contains(job)) {
0083         return;
0084     }
0085 
0086     // Watch the server registering/unregistering and re-register the jobs as needed
0087     if (!d->serverRegisteredConnection) {
0088         d->serverRegisteredConnection = connect(serverProxy(), &KSharedUiServerProxy::serverRegistered, this, [this]() {
0089             // Remember the list of jobs to re-register and then delete the old ones
0090             const QList<KJob *> staleJobs = d->progressJobView.keys();
0091 
0092             qDeleteAll(d->progressJobView);
0093             d->progressJobView.clear();
0094 
0095             for (KJob *job : staleJobs) {
0096                 registerJob(job);
0097             }
0098         });
0099     }
0100 
0101     const QString appName = QCoreApplication::applicationName();
0102     // This will only work if main() used QIcon::fromTheme.
0103     QString programIconName = QApplication::windowIcon().name();
0104 
0105     if (programIconName.isEmpty()) {
0106         programIconName = appName;
0107     }
0108 
0109     QPointer<KJob> jobWatch = job;
0110     QDBusReply<QDBusObjectPath> reply = serverProxy()->uiserver()->requestView(appName, programIconName, job->capabilities());
0111 
0112     // If we got a valid reply, register the interface for later usage.
0113     if (reply.isValid()) {
0114         org::kde::JobViewV2 *jobView = new org::kde::JobViewV2(QStringLiteral("org.kde.JobViewServer"), reply.value().path(), QDBusConnection::sessionBus());
0115         if (!jobWatch) {
0116             // qCDebug(KJOBWIDGETS) << "deleted out from under us when asking the server proxy for the view";
0117             jobView->terminate(QString());
0118             delete jobView;
0119             return;
0120         }
0121 
0122         QObject::connect(jobView, SIGNAL(cancelRequested()), this, SLOT(_k_killJob()));
0123         QObject::connect(jobView, &org::kde::JobViewV2::suspendRequested, job, &KJob::suspend);
0124         QObject::connect(jobView, &org::kde::JobViewV2::resumeRequested, job, &KJob::resume);
0125 
0126         d->updateDestUrl(job, jobView);
0127 
0128         if (!jobWatch) {
0129             // qCDebug(KJOBWIDGETS) << "deleted out from under us when creating the dbus interface";
0130             jobView->terminate(QString());
0131             delete jobView;
0132             return;
0133         }
0134 
0135         d->progressJobView.insert(job, jobView);
0136     } else if (!jobWatch) {
0137         qWarning() << "Uh-oh...KUiServerJobTracker was trying to forward a job, but it was deleted from under us."
0138                    << "kuiserver *may* have a stranded job. we can't do anything about it because the returned objectPath is invalid.";
0139         return;
0140     }
0141 
0142     KJobTrackerInterface::registerJob(job);
0143 }
0144 
0145 void KUiServerJobTracker::unregisterJob(KJob *job)
0146 {
0147     KJobTrackerInterface::unregisterJob(job);
0148 
0149     if (!d->progressJobView.contains(job)) {
0150         return;
0151     }
0152 
0153     org::kde::JobViewV2 *jobView = d->progressJobView.take(job);
0154 
0155     d->updateDestUrl(job, jobView);
0156 
0157     jobView->setError(job->error());
0158 
0159     if (job->error()) {
0160         jobView->terminate(job->errorText());
0161     } else {
0162         jobView->terminate(QString());
0163     }
0164 
0165     delete jobView;
0166 }
0167 
0168 void KUiServerJobTracker::finished(KJob *job)
0169 {
0170     if (!d->progressJobView.contains(job)) {
0171         return;
0172     }
0173 
0174     org::kde::JobViewV2 *jobView = d->progressJobView.take(job);
0175 
0176     d->updateDestUrl(job, jobView);
0177 
0178     jobView->setError(job->error());
0179 
0180     if (job->error()) {
0181         jobView->terminate(job->errorText());
0182     } else {
0183         jobView->terminate(QString());
0184     }
0185 }
0186 
0187 void KUiServerJobTracker::suspended(KJob *job)
0188 {
0189     if (!d->progressJobView.contains(job)) {
0190         return;
0191     }
0192 
0193     org::kde::JobViewV2 *jobView = d->progressJobView[job];
0194 
0195     jobView->setSuspended(true);
0196 }
0197 
0198 void KUiServerJobTracker::resumed(KJob *job)
0199 {
0200     if (!d->progressJobView.contains(job)) {
0201         return;
0202     }
0203 
0204     org::kde::JobViewV2 *jobView = d->progressJobView[job];
0205 
0206     jobView->setSuspended(false);
0207 }
0208 
0209 void KUiServerJobTracker::description(KJob *job, const QString &title, const QPair<QString, QString> &field1, const QPair<QString, QString> &field2)
0210 {
0211     if (!d->progressJobView.contains(job)) {
0212         return;
0213     }
0214 
0215     org::kde::JobViewV2 *jobView = d->progressJobView[job];
0216 
0217     jobView->setInfoMessage(title);
0218 
0219     if (field1.first.isNull() || field1.second.isNull()) {
0220         jobView->clearDescriptionField(0);
0221     } else {
0222         jobView->setDescriptionField(0, field1.first, field1.second);
0223     }
0224 
0225     if (field2.first.isNull() || field2.second.isNull()) {
0226         jobView->clearDescriptionField(1);
0227     } else {
0228         jobView->setDescriptionField(1, field2.first, field2.second);
0229     }
0230 }
0231 
0232 void KUiServerJobTracker::infoMessage(KJob *job, const QString &message)
0233 {
0234     if (!d->progressJobView.contains(job)) {
0235         return;
0236     }
0237 
0238     org::kde::JobViewV2 *jobView = d->progressJobView[job];
0239 
0240     jobView->setInfoMessage(message);
0241 }
0242 
0243 void KUiServerJobTracker::totalAmount(KJob *job, KJob::Unit unit, qulonglong amount)
0244 {
0245     if (!d->progressJobView.contains(job)) {
0246         return;
0247     }
0248 
0249     org::kde::JobViewV2 *jobView = d->progressJobView[job];
0250 
0251     switch (unit) {
0252     case KJob::Bytes:
0253         jobView->setTotalAmount(amount, QStringLiteral("bytes"));
0254         break;
0255     case KJob::Files:
0256         jobView->setTotalAmount(amount, QStringLiteral("files"));
0257         break;
0258     case KJob::Directories:
0259         jobView->setTotalAmount(amount, QStringLiteral("dirs"));
0260         break;
0261     case KJob::Items:
0262         jobView->setTotalAmount(amount, QStringLiteral("items"));
0263         break;
0264     case KJob::UnitsCount:
0265         Q_UNREACHABLE();
0266         break;
0267     }
0268 }
0269 
0270 void KUiServerJobTracker::processedAmount(KJob *job, KJob::Unit unit, qulonglong amount)
0271 {
0272     if (!d->progressJobView.contains(job)) {
0273         return;
0274     }
0275 
0276     org::kde::JobViewV2 *jobView = d->progressJobView[job];
0277 
0278     switch (unit) {
0279     case KJob::Bytes:
0280         jobView->setProcessedAmount(amount, QStringLiteral("bytes"));
0281         break;
0282     case KJob::Files:
0283         jobView->setProcessedAmount(amount, QStringLiteral("files"));
0284         break;
0285     case KJob::Directories:
0286         jobView->setProcessedAmount(amount, QStringLiteral("dirs"));
0287         break;
0288     case KJob::Items:
0289         jobView->setProcessedAmount(amount, QStringLiteral("items"));
0290         break;
0291     case KJob::UnitsCount:
0292         Q_UNREACHABLE();
0293         break;
0294     }
0295 }
0296 
0297 void KUiServerJobTracker::percent(KJob *job, unsigned long percent)
0298 {
0299     if (!d->progressJobView.contains(job)) {
0300         return;
0301     }
0302 
0303     org::kde::JobViewV2 *jobView = d->progressJobView[job];
0304 
0305     jobView->setPercent(percent);
0306 }
0307 
0308 void KUiServerJobTracker::speed(KJob *job, unsigned long value)
0309 {
0310     if (!d->progressJobView.contains(job)) {
0311         return;
0312     }
0313 
0314     org::kde::JobViewV2 *jobView = d->progressJobView[job];
0315 
0316     jobView->setSpeed(value);
0317 }
0318 
0319 KSharedUiServerProxy::KSharedUiServerProxy()
0320     : m_uiserver(new org::kde::JobViewServer(QStringLiteral("org.kde.JobViewServer"), QStringLiteral("/JobViewServer"), QDBusConnection::sessionBus()))
0321     , m_watcher(new QDBusServiceWatcher(QStringLiteral("org.kde.JobViewServer"), QDBusConnection::sessionBus(), QDBusServiceWatcher::WatchForOwnerChange))
0322 {
0323     QDBusConnectionInterface *bus = QDBusConnection::sessionBus().interface();
0324     if (!bus->isServiceRegistered(QStringLiteral("org.kde.JobViewServer"))) {
0325         QDBusReply<void> reply = bus->startService(QStringLiteral("org.kde.kuiserver"));
0326         if (!reply.isValid()) {
0327             qCCritical(KJOBWIDGETS) << "Couldn't start kuiserver from org.kde.kuiserver.service:" << reply.error();
0328             return;
0329         }
0330 
0331         if (!bus->isServiceRegistered(QStringLiteral("org.kde.JobViewServer"))) {
0332             qCDebug(KJOBWIDGETS) << "The dbus name org.kde.JobViewServer is STILL NOT REGISTERED, even after starting kuiserver. Should not happen.";
0333             return;
0334         }
0335 
0336         qCDebug(KJOBWIDGETS) << "kuiserver registered";
0337     } else {
0338         qCDebug(KJOBWIDGETS) << "kuiserver found";
0339     }
0340 
0341     connect(m_watcher.get(), &QDBusServiceWatcher::serviceOwnerChanged, this, &KSharedUiServerProxy::uiserverOwnerChanged);
0342 
0343     // cleanup early enough to avoid issues with dbus at application exit
0344     // see e.g. https://phabricator.kde.org/D2545
0345     qAddPostRoutine([]() {
0346         serverProxy->m_uiserver.reset();
0347         serverProxy->m_watcher.reset();
0348     });
0349 }
0350 
0351 KSharedUiServerProxy::~KSharedUiServerProxy()
0352 {
0353 }
0354 
0355 org::kde::JobViewServer *KSharedUiServerProxy::uiserver()
0356 {
0357     return m_uiserver.get();
0358 }
0359 
0360 void KSharedUiServerProxy::uiserverOwnerChanged(const QString &serviceName, const QString &oldOwner, const QString &newOwner)
0361 {
0362     Q_UNUSED(serviceName);
0363     Q_UNUSED(oldOwner);
0364 
0365     if (!newOwner.isEmpty()) { // registered
0366         Q_EMIT serverRegistered();
0367     } else if (newOwner.isEmpty()) { // unregistered
0368         Q_EMIT serverUnregistered();
0369     }
0370 }
0371 
0372 #include "moc_kuiserverjobtracker.cpp"
0373 #include "moc_kuiserverjobtracker_p.cpp"