File indexing completed on 2024-05-19 04:39:57

0001 /*
0002     This file is part of the KDE project
0003 
0004     SPDX-FileCopyrightText: 2007-2008 Hamish Rodda <rodda@kde.org>
0005     SPDX-FileCopyrightText: 2023 Igor Kushnir <igorkuo@gmail.com>
0006 
0007     SPDX-License-Identifier: LGPL-2.0-or-later
0008 */
0009 
0010 #include "ksequentialcompoundjob.h"
0011 #include "ksequentialcompoundjob_p.h"
0012 
0013 #include "debug.h"
0014 
0015 using namespace KDevCoreAddons;
0016 
0017 KSequentialCompoundJobPrivate::KSequentialCompoundJobPrivate() = default;
0018 KSequentialCompoundJobPrivate::~KSequentialCompoundJobPrivate() = default;
0019 
0020 bool KSequentialCompoundJobPrivate::isCurrentlyRunningSubjob(KJob *job) const
0021 {
0022     return m_jobIndex >= 0 && !m_subjobs.empty() && job == m_subjobs.constFirst();
0023 }
0024 
0025 void KSequentialCompoundJobPrivate::startNextSubjob()
0026 {
0027     ++m_jobIndex;
0028     Q_ASSERT(!m_subjobs.empty());
0029     auto *const job = m_subjobs.front();
0030 
0031     qCDebug(UTIL) << "starting subjob" << m_jobIndex + 1 << "of" << m_jobCount << ':' << job;
0032     job->start();
0033 }
0034 
0035 void KSequentialCompoundJobPrivate::disconnectSubjob(KJob *job)
0036 {
0037     Q_Q(KSequentialCompoundJob);
0038     QObject::disconnect(job, &KJob::percentChanged, q, &KSequentialCompoundJob::subjobPercentChanged);
0039     KCompoundJobPrivate::disconnectSubjob(job);
0040 }
0041 
0042 KSequentialCompoundJob::KSequentialCompoundJob(QObject *parent)
0043     : KSequentialCompoundJob(*new KSequentialCompoundJobPrivate, parent)
0044 {
0045 }
0046 
0047 KSequentialCompoundJob::KSequentialCompoundJob(KSequentialCompoundJobPrivate &dd, QObject *parent)
0048     : KCompoundJob(dd, parent)
0049 {
0050     setCapabilities(Killable);
0051 }
0052 
0053 KSequentialCompoundJob::~KSequentialCompoundJob() = default;
0054 
0055 void KSequentialCompoundJob::setAbortOnSubjobError(bool abort)
0056 {
0057     Q_D(KSequentialCompoundJob);
0058     d->m_abortOnSubjobError = abort;
0059 }
0060 
0061 void KSequentialCompoundJob::start()
0062 {
0063     Q_D(KSequentialCompoundJob);
0064     if (d->m_subjobs.empty()) {
0065         qCDebug(UTIL) << "no subjobs, finishing in start()";
0066         emitResult();
0067         return;
0068     }
0069 
0070     d->startNextSubjob();
0071 }
0072 
0073 void KSequentialCompoundJob::subjobPercentChanged(KJob *job, unsigned long percent)
0074 {
0075     Q_D(KSequentialCompoundJob);
0076     Q_ASSERT(d->m_jobIndex < d->m_jobCount); // invariant
0077     if (!d->isCurrentlyRunningSubjob(job)) {
0078         qCDebug(UTIL) << "ignoring percentChanged() signal emitted by an unstarted or finished subjob" << job;
0079         return;
0080     }
0081     Q_ASSERT(d->m_jobIndex >= 0);
0082 
0083     const unsigned long totalPercent = (100.0 * d->m_jobIndex + percent) / d->m_jobCount;
0084     qCDebug(UTIL) << "subjob percent:" << percent << "; total percent:" << totalPercent;
0085     setPercent(totalPercent);
0086 }
0087 
0088 void KSequentialCompoundJob::subjobFinished(KJob *job)
0089 {
0090     Q_D(KSequentialCompoundJob);
0091     if (d->m_killingSubjob || isFinished()) {
0092         // doKill() will return true and this compound job will finish, or already finished
0093         removeSubjob(job);
0094         return;
0095     }
0096 
0097     Q_ASSERT(d->m_jobIndex < d->m_jobCount); // invariant
0098     // Note: isCurrentlyRunningSubjob(job) must be checked before calling removeSubjob(job).
0099     if (!d->isCurrentlyRunningSubjob(job)) {
0100         qCDebug(UTIL) << "unstarted subjob finished:" << job;
0101         removeSubjob(job);
0102         return;
0103     }
0104 
0105     const bool registeredSubjob = removeSubjob(job);
0106     Q_ASSERT(registeredSubjob); // because isCurrentlyRunningSubjob(job) returned true
0107 
0108     Q_ASSERT(d->m_jobIndex >= 0); // because isCurrentlyRunningSubjob(job) returned true
0109     const unsigned long totalPercent = 100.0 * (d->m_jobIndex + 1) / d->m_jobCount;
0110     qCDebug(UTIL) << "subjob finished:" << job << "; total percent:" << totalPercent;
0111     setPercent(totalPercent);
0112 
0113     int error = job->error();
0114     if (!error && d->m_killingFailed) {
0115         error = KilledJobError;
0116     }
0117 
0118     // Abort if job is the subjob we failed to kill and in case of error.
0119     const bool abort = d->m_killingFailed || (d->m_abortOnSubjobError && error);
0120     if (abort) {
0121         qCDebug(UTIL) << "aborting on subjob error:" << error << job->errorText();
0122     }
0123 
0124     // Finish in order to abort, or if all subjobs have finished. Propagate the last-run subjob's error.
0125     if (abort || d->m_subjobs.empty()) {
0126         setError(error);
0127         setErrorText(job->errorText());
0128         emitResult();
0129         return;
0130     }
0131 
0132     qCDebug(UTIL) << "remaining subjobs:" << d->m_subjobs;
0133     d->startNextSubjob();
0134 }
0135 
0136 bool KSequentialCompoundJob::addSubjob(KJob *job)
0137 {
0138     Q_D(KSequentialCompoundJob);
0139     if (!KCompoundJob::addSubjob(job)) {
0140         return false;
0141     }
0142     ++d->m_jobCount;
0143     connect(job, &KJob::percentChanged, this, &KSequentialCompoundJob::subjobPercentChanged);
0144     return true;
0145 }
0146 
0147 bool KSequentialCompoundJob::doKill()
0148 {
0149     Q_D(KSequentialCompoundJob);
0150     // Don't check isFinished() here, because KJob::kill() calls doKill() only if the job has not finished.
0151     if (d->m_killingSubjob) {
0152         qCDebug(UTIL) << "killing sequential compound job recursively fails";
0153         return false;
0154     }
0155     if (d->m_jobIndex == -1) {
0156         qCDebug(UTIL) << "killing unstarted sequential compound job";
0157         // Any unstarted subjobs will be deleted along with this compound job, which is their parent.
0158         return true;
0159     }
0160     if (d->m_subjobs.empty()) {
0161         qCDebug(UTIL) << "killing sequential compound job with zero remaining subjobs";
0162         return true;
0163     }
0164 
0165     auto *const job = d->m_subjobs.front();
0166     qCDebug(UTIL) << "killing running subjob" << job;
0167 
0168     d->m_killingSubjob = true;
0169     const bool killed = job->kill();
0170     d->m_killingSubjob = false;
0171 
0172     d->m_killingFailed = !killed;
0173     if (d->m_killingFailed) {
0174         qCDebug(UTIL) << "failed to kill subjob" << job;
0175         if (d->m_subjobs.empty() || d->m_subjobs.constFirst() != job) {
0176             qCDebug(UTIL) << "... but the subjob finished or was removed, assume killed. Remaining subjobs:" << d->m_subjobs;
0177             return true;
0178         }
0179     }
0180 
0181     return killed;
0182 }
0183 
0184 #include "moc_ksequentialcompoundjob.cpp"
0185 #include "moc_ksimplesequentialcompoundjob.cpp"