File indexing completed on 2024-11-10 04:40:32

0001 /*
0002     SPDX-FileCopyrightText: 2006-2008 Volker Krause <vkrause@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "transactionsequence.h"
0008 #include "transactionjobs.h"
0009 
0010 #include "job_p.h"
0011 
0012 #include <QSet>
0013 #include <QVariant>
0014 
0015 using namespace Akonadi;
0016 
0017 class Akonadi::TransactionSequencePrivate : public JobPrivate
0018 {
0019 public:
0020     explicit TransactionSequencePrivate(TransactionSequence *parent)
0021         : JobPrivate(parent)
0022         , mState(Idle)
0023     {
0024     }
0025 
0026     enum TransactionState {
0027         Idle,
0028         Running,
0029         WaitingForSubjobs,
0030         RollingBack,
0031         Committing,
0032     };
0033 
0034     Q_DECLARE_PUBLIC(TransactionSequence)
0035 
0036     TransactionState mState;
0037     QSet<KJob *> mIgnoredErrorJobs;
0038     bool mAutoCommit = true;
0039 
0040     void commitResult(KJob *job)
0041     {
0042         Q_Q(TransactionSequence);
0043 
0044         if (job->error()) {
0045             q->setError(job->error());
0046             q->setErrorText(job->errorText());
0047         }
0048         q->emitResult();
0049     }
0050 
0051     void rollbackResult(KJob *job)
0052     {
0053         Q_Q(TransactionSequence);
0054 
0055         Q_UNUSED(job)
0056         q->emitResult();
0057     }
0058 
0059     QString jobDebuggingString() const override;
0060 };
0061 
0062 QString Akonadi::TransactionSequencePrivate::jobDebuggingString() const
0063 {
0064     // TODO add state
0065     return QStringLiteral("autocommit %1").arg(mAutoCommit);
0066 }
0067 
0068 TransactionSequence::TransactionSequence(QObject *parent)
0069     : Job(new TransactionSequencePrivate(this), parent)
0070 {
0071 }
0072 
0073 TransactionSequence::~TransactionSequence()
0074 {
0075 }
0076 
0077 bool TransactionSequence::addSubjob(KJob *job)
0078 {
0079     Q_D(TransactionSequence);
0080 
0081     // Don't abort the rollback job, while keeping the state set.
0082     if (d->mState == TransactionSequencePrivate::RollingBack) {
0083         return Job::addSubjob(job);
0084     }
0085 
0086     if (error()) {
0087         // This can happen if a rollback is in progress, so make sure we don't set the state back to running.
0088         job->kill(EmitResult);
0089         return false;
0090     }
0091     // TODO KDE5: remove property hack once SpecialCollectionsRequestJob has been fixed
0092     if (d->mState == TransactionSequencePrivate::Idle && !property("transactionsDisabled").toBool()) {
0093         d->mState = TransactionSequencePrivate::Running; // needs to be set before creating the transaction job to avoid infinite recursion
0094         new TransactionBeginJob(this);
0095     } else {
0096         d->mState = TransactionSequencePrivate::Running;
0097     }
0098     return Job::addSubjob(job);
0099 }
0100 
0101 void TransactionSequence::slotResult(KJob *job)
0102 {
0103     Q_D(TransactionSequence);
0104 
0105     if (!job->error() || d->mIgnoredErrorJobs.contains(job)) {
0106         // If we have an error but want to ignore it, we can't call Job::slotResult
0107         // because it would confuse the subjob queue processing logic. Just removing
0108         // the subjob instead is fine.
0109         if (!job->error()) {
0110             Job::slotResult(job);
0111         } else {
0112             Job::removeSubjob(job);
0113         }
0114 
0115         if (!hasSubjobs()) {
0116             if (d->mState == TransactionSequencePrivate::WaitingForSubjobs) {
0117                 if (property("transactionsDisabled").toBool()) {
0118                     emitResult();
0119                     return;
0120                 }
0121                 d->mState = TransactionSequencePrivate::Committing;
0122                 auto job = new TransactionCommitJob(this);
0123                 connect(job, &TransactionCommitJob::result, this, [d](KJob *job) {
0124                     d->commitResult(job);
0125                 });
0126             }
0127         }
0128     } else if (job->error() == KJob::KilledJobError) {
0129         Job::slotResult(job);
0130     } else {
0131         setError(job->error());
0132         setErrorText(job->errorText());
0133         removeSubjob(job);
0134 
0135         // cancel all subjobs in case someone else is listening (such as ItemSync)
0136         const auto subjobs = this->subjobs();
0137         for (KJob *job : subjobs) {
0138             job->kill(KJob::EmitResult);
0139         }
0140         clearSubjobs();
0141 
0142         if (d->mState == TransactionSequencePrivate::Running || d->mState == TransactionSequencePrivate::WaitingForSubjobs) {
0143             if (property("transactionsDisabled").toBool()) {
0144                 emitResult();
0145                 return;
0146             }
0147             d->mState = TransactionSequencePrivate::RollingBack;
0148             auto job = new TransactionRollbackJob(this);
0149             connect(job, &TransactionRollbackJob::result, this, [d](KJob *job) {
0150                 d->rollbackResult(job);
0151             });
0152         }
0153     }
0154 }
0155 
0156 void TransactionSequence::commit()
0157 {
0158     Q_D(TransactionSequence);
0159 
0160     if (d->mState == TransactionSequencePrivate::Running) {
0161         d->mState = TransactionSequencePrivate::WaitingForSubjobs;
0162     } else if (d->mState == TransactionSequencePrivate::RollingBack) {
0163         return;
0164     } else {
0165         // we never got any subjobs, that means we never started a transaction
0166         // so we can just quit here
0167         if (d->mState == TransactionSequencePrivate::Idle) {
0168             emitResult();
0169         }
0170         return;
0171     }
0172 
0173     if (subjobs().isEmpty()) {
0174         if (property("transactionsDisabled").toBool()) {
0175             emitResult();
0176             return;
0177         }
0178         if (!error()) {
0179             d->mState = TransactionSequencePrivate::Committing;
0180             auto job = new TransactionCommitJob(this);
0181             connect(job, &TransactionCommitJob::result, this, [d](KJob *job) {
0182                 d->commitResult(job);
0183             });
0184         } else {
0185             d->mState = TransactionSequencePrivate::RollingBack;
0186             auto job = new TransactionRollbackJob(this);
0187             connect(job, &TransactionRollbackJob::result, this, [d](KJob *job) {
0188                 d->rollbackResult(job);
0189             });
0190         }
0191     }
0192 }
0193 
0194 void TransactionSequence::setIgnoreJobFailure(KJob *job)
0195 {
0196     Q_D(TransactionSequence);
0197 
0198     // make sure this is one of our sub jobs
0199     Q_ASSERT(subjobs().contains(job));
0200 
0201     d->mIgnoredErrorJobs.insert(job);
0202 }
0203 
0204 void TransactionSequence::doStart()
0205 {
0206     Q_D(TransactionSequence);
0207 
0208     if (d->mAutoCommit) {
0209         if (d->mState == TransactionSequencePrivate::Idle) {
0210             emitResult();
0211         } else {
0212             commit();
0213         }
0214     }
0215 }
0216 
0217 void TransactionSequence::setAutomaticCommittingEnabled(bool enable)
0218 {
0219     Q_D(TransactionSequence);
0220     d->mAutoCommit = enable;
0221 }
0222 
0223 void TransactionSequence::rollback()
0224 {
0225     Q_D(TransactionSequence);
0226 
0227     setError(UserCanceled);
0228     // we never really started
0229     if (d->mState == TransactionSequencePrivate::Idle) {
0230         emitResult();
0231         return;
0232     }
0233 
0234     const auto jobList = subjobs();
0235     for (KJob *job : jobList) {
0236         // Killing the current subjob means forcibly closing the akonadiserver socket
0237         // (with a bit of delay since it happens in a secondary thread)
0238         // which means the next job gets disconnected
0239         // and the itemsync finishes with error "Cannot connect to the Akonadi service.", not ideal
0240         if (job != d->mCurrentSubJob) {
0241             job->kill(KJob::EmitResult);
0242         }
0243     }
0244 
0245     d->mState = TransactionSequencePrivate::RollingBack;
0246     auto job = new TransactionRollbackJob(this);
0247     connect(job, &TransactionRollbackJob::result, this, [d](KJob *job) {
0248         d->rollbackResult(job);
0249     });
0250 }
0251 
0252 #include "moc_transactionsequence.cpp"