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"