File indexing completed on 2024-05-12 03:54:56

0001 /*
0002     This file is part of the KDE project
0003 
0004     SPDX-FileCopyrightText: 2000 Stephan Kulow <coolo@kde.org>
0005     SPDX-FileCopyrightText: 2000 David Faure <faure@kde.org>
0006     SPDX-FileCopyrightText: 2006 Kevin Ottens <ervin@kde.org>
0007 
0008     SPDX-License-Identifier: LGPL-2.0-or-later
0009 */
0010 
0011 #include "kjob.h"
0012 #include "kjob_p.h"
0013 
0014 #include "kcoreaddons_debug.h"
0015 #include "kjobuidelegate.h"
0016 
0017 #include <QEventLoop>
0018 #include <QTimer>
0019 
0020 KJobPrivate::KJobPrivate()
0021 {
0022 }
0023 
0024 KJobPrivate::~KJobPrivate()
0025 {
0026 }
0027 
0028 KJob::KJob(QObject *parent)
0029     : QObject(parent)
0030     , d_ptr(new KJobPrivate)
0031 {
0032     d_ptr->q_ptr = this;
0033 }
0034 
0035 KJob::KJob(KJobPrivate &dd, QObject *parent)
0036     : QObject(parent)
0037     , d_ptr(&dd)
0038 {
0039     d_ptr->q_ptr = this;
0040 }
0041 
0042 KJob::~KJob()
0043 {
0044     if (!d_ptr->isFinished) {
0045         d_ptr->isFinished = true;
0046         Q_EMIT finished(this, QPrivateSignal());
0047     }
0048 
0049     delete d_ptr->speedTimer;
0050     delete d_ptr->uiDelegate;
0051 }
0052 
0053 void KJob::setUiDelegate(KJobUiDelegate *delegate)
0054 {
0055     Q_D(KJob);
0056     if (!delegate) {
0057         delete d->uiDelegate;
0058         d->uiDelegate = nullptr;
0059         return;
0060     }
0061 
0062     if (delegate->setJob(this)) {
0063         delete d->uiDelegate;
0064         d->uiDelegate = delegate;
0065         d->uiDelegate->connectJob(this);
0066     }
0067 }
0068 
0069 KJobUiDelegate *KJob::uiDelegate() const
0070 {
0071     return d_func()->uiDelegate;
0072 }
0073 
0074 KJob::Capabilities KJob::capabilities() const
0075 {
0076     return d_func()->capabilities;
0077 }
0078 
0079 bool KJob::isSuspended() const
0080 {
0081     return d_func()->suspended;
0082 }
0083 
0084 void KJob::finishJob(bool emitResult)
0085 {
0086     Q_D(KJob);
0087     Q_ASSERT(!d->isFinished);
0088     d->isFinished = true;
0089 
0090     if (d->eventLoop) {
0091         d->eventLoop->quit();
0092     }
0093 
0094     // If we are displaying a progress dialog, remove it first.
0095     Q_EMIT finished(this, QPrivateSignal());
0096 
0097     if (emitResult) {
0098         Q_EMIT result(this, QPrivateSignal());
0099     }
0100 
0101     if (isAutoDelete()) {
0102         deleteLater();
0103     }
0104 }
0105 
0106 bool KJob::kill(KillVerbosity verbosity)
0107 {
0108     Q_D(KJob);
0109     if (d->isFinished) {
0110         return true;
0111     }
0112 
0113     if (doKill()) {
0114         // A subclass can (but should not) call emitResult() or kill()
0115         // from doKill() and thus set isFinished to true.
0116         if (!d->isFinished) {
0117             setError(KilledJobError);
0118             finishJob(verbosity != Quietly);
0119         }
0120         return true;
0121     } else {
0122         return false;
0123     }
0124 }
0125 
0126 bool KJob::suspend()
0127 {
0128     Q_D(KJob);
0129     if (!d->suspended) {
0130         if (doSuspend()) {
0131             d->suspended = true;
0132             Q_EMIT suspended(this, QPrivateSignal());
0133 
0134             return true;
0135         }
0136     }
0137 
0138     return false;
0139 }
0140 
0141 bool KJob::resume()
0142 {
0143     Q_D(KJob);
0144     if (d->suspended) {
0145         if (doResume()) {
0146             d->suspended = false;
0147             Q_EMIT resumed(this, QPrivateSignal());
0148 
0149             return true;
0150         }
0151     }
0152 
0153     return false;
0154 }
0155 
0156 bool KJob::doKill()
0157 {
0158     return false;
0159 }
0160 
0161 bool KJob::doSuspend()
0162 {
0163     return false;
0164 }
0165 
0166 bool KJob::doResume()
0167 {
0168     return false;
0169 }
0170 
0171 void KJob::setCapabilities(KJob::Capabilities capabilities)
0172 {
0173     Q_D(KJob);
0174     d->capabilities = capabilities;
0175 }
0176 
0177 bool KJob::exec()
0178 {
0179     Q_D(KJob);
0180     // Usually this job would delete itself, via deleteLater() just after
0181     // emitting result() (unless configured otherwise). Since we use an event
0182     // loop below, that event loop will process the deletion event and we'll
0183     // have been deleted when exec() returns. This crashes, so temporarily
0184     // suspend autodeletion and manually do it afterwards.
0185     const bool wasAutoDelete = isAutoDelete();
0186     setAutoDelete(false);
0187 
0188     Q_ASSERT(!d->eventLoop);
0189 
0190     QEventLoop loop(this);
0191     d->eventLoop = &loop;
0192 
0193     start();
0194     if (!d->isFinished) {
0195         d->m_startedWithExec = true;
0196         d->eventLoop->exec(QEventLoop::ExcludeUserInputEvents);
0197     }
0198     d->eventLoop = nullptr;
0199 
0200     if (wasAutoDelete) {
0201         deleteLater();
0202     }
0203     return (d->error == NoError);
0204 }
0205 
0206 int KJob::error() const
0207 {
0208     return d_func()->error;
0209 }
0210 
0211 QString KJob::errorText() const
0212 {
0213     return d_func()->errorText;
0214 }
0215 
0216 QString KJob::errorString() const
0217 {
0218     return d_func()->errorText;
0219 }
0220 
0221 qulonglong KJob::processedAmount(Unit unit) const
0222 {
0223     if (unit >= UnitsCount) {
0224         qCWarning(KCOREADDONS_DEBUG) << "KJob::processedAmount() was called on an invalid Unit" << unit;
0225         return 0;
0226     }
0227 
0228     return d_func()->m_jobAmounts[unit].processedAmount;
0229 }
0230 
0231 qulonglong KJob::totalAmount(Unit unit) const
0232 {
0233     if (unit >= UnitsCount) {
0234         qCWarning(KCOREADDONS_DEBUG) << "KJob::totalAmount() was called on an invalid Unit" << unit;
0235         return 0;
0236     }
0237 
0238     return d_func()->m_jobAmounts[unit].totalAmount;
0239 }
0240 
0241 unsigned long KJob::percent() const
0242 {
0243     return d_func()->percentage;
0244 }
0245 
0246 bool KJob::isFinished() const
0247 {
0248     return d_func()->isFinished;
0249 }
0250 
0251 void KJob::setError(int errorCode)
0252 {
0253     Q_D(KJob);
0254     d->error = errorCode;
0255 }
0256 
0257 void KJob::setErrorText(const QString &errorText)
0258 {
0259     Q_D(KJob);
0260     d->errorText = errorText;
0261 }
0262 
0263 void KJob::setProcessedAmount(Unit unit, qulonglong amount)
0264 {
0265     if (unit >= UnitsCount) {
0266         qCWarning(KCOREADDONS_DEBUG) << "KJob::setProcessedAmount() was called on an invalid Unit" << unit;
0267         return;
0268     }
0269 
0270     Q_D(KJob);
0271 
0272     auto &[processed, total] = d->m_jobAmounts[unit];
0273 
0274     const bool should_emit = (processed != amount);
0275 
0276     processed = amount;
0277 
0278     if (should_emit) {
0279         Q_EMIT processedAmountChanged(this, unit, amount, QPrivateSignal{});
0280         if (unit == d->progressUnit) {
0281             Q_EMIT processedSize(this, amount);
0282             emitPercent(processed, total);
0283         }
0284     }
0285 }
0286 
0287 void KJob::setTotalAmount(Unit unit, qulonglong amount)
0288 {
0289     if (unit >= UnitsCount) {
0290         qCWarning(KCOREADDONS_DEBUG) << "KJob::setTotalAmount() was called on an invalid Unit" << unit;
0291         return;
0292     }
0293 
0294     Q_D(KJob);
0295 
0296     auto &[processed, total] = d->m_jobAmounts[unit];
0297 
0298     const bool should_emit = (total != amount);
0299 
0300     total = amount;
0301 
0302     if (should_emit) {
0303         Q_EMIT totalAmountChanged(this, unit, amount, QPrivateSignal{});
0304         if (unit == d->progressUnit) {
0305             Q_EMIT totalSize(this, amount);
0306             emitPercent(processed, total);
0307         }
0308     }
0309 }
0310 
0311 void KJob::setProgressUnit(Unit unit)
0312 {
0313     Q_D(KJob);
0314     d->progressUnit = unit;
0315 }
0316 
0317 void KJob::setPercent(unsigned long percentage)
0318 {
0319     Q_D(KJob);
0320     if (d->percentage != percentage) {
0321         d->percentage = percentage;
0322         Q_EMIT percentChanged(this, percentage, QPrivateSignal{});
0323     }
0324 }
0325 
0326 void KJob::emitResult()
0327 {
0328     if (!d_func()->isFinished) {
0329         finishJob(true);
0330     }
0331 }
0332 
0333 void KJob::emitPercent(qulonglong processedAmount, qulonglong totalAmount)
0334 {
0335     if (totalAmount != 0) {
0336         setPercent(100.0 * processedAmount / totalAmount);
0337     }
0338 }
0339 
0340 void KJob::emitSpeed(unsigned long value)
0341 {
0342     Q_D(KJob);
0343     if (!d->speedTimer) {
0344         d->speedTimer = new QTimer(this);
0345         connect(d->speedTimer, &QTimer::timeout, this, [d]() {
0346             d->speedTimeout();
0347         });
0348     }
0349 
0350     Q_EMIT speed(this, value);
0351     d->speedTimer->start(5000); // 5 seconds interval should be enough
0352 }
0353 
0354 void KJobPrivate::speedTimeout()
0355 {
0356     Q_Q(KJob);
0357     // send 0 and stop the timer
0358     // timer will be restarted only when we receive another speed event
0359     Q_EMIT q->speed(q, 0);
0360     speedTimer->stop();
0361 }
0362 
0363 bool KJob::isAutoDelete() const
0364 {
0365     Q_D(const KJob);
0366     return d->isAutoDelete;
0367 }
0368 
0369 void KJob::setAutoDelete(bool autodelete)
0370 {
0371     Q_D(KJob);
0372     d->isAutoDelete = autodelete;
0373 }
0374 
0375 void KJob::setFinishedNotificationHidden(bool hide)
0376 {
0377     Q_D(KJob);
0378     d->m_hideFinishedNotification = hide;
0379 }
0380 
0381 bool KJob::isFinishedNotificationHidden() const
0382 {
0383     Q_D(const KJob);
0384     return d->m_hideFinishedNotification;
0385 }
0386 
0387 bool KJob::isStartedWithExec() const
0388 {
0389     Q_D(const KJob);
0390     return d->m_startedWithExec;
0391 }
0392 
0393 #include "moc_kjob.cpp"