Akonadi

transactionsequence.cpp
1/*
2 SPDX-FileCopyrightText: 2006-2008 Volker Krause <vkrause@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "transactionsequence.h"
8#include "transactionjobs.h"
9
10#include "job_p.h"
11
12#include <QSet>
13#include <QVariant>
14
15using namespace Akonadi;
16
17class Akonadi::TransactionSequencePrivate : public JobPrivate
18{
19public:
20 explicit TransactionSequencePrivate(TransactionSequence *parent)
21 : JobPrivate(parent)
22 , mState(Idle)
23 {
24 }
25
26 enum TransactionState {
27 Idle,
28 Running,
29 WaitingForSubjobs,
30 RollingBack,
31 Committing,
32 };
33
34 Q_DECLARE_PUBLIC(TransactionSequence)
35
36 TransactionState mState;
37 QSet<KJob *> mIgnoredErrorJobs;
38 bool mAutoCommit = true;
39
40 void commitResult(KJob *job)
41 {
43
44 if (job->error()) {
45 q->setError(job->error());
46 q->setErrorText(job->errorText());
47 }
48 q->emitResult();
49 }
50
51 void rollbackResult(KJob *job)
52 {
54
55 Q_UNUSED(job)
56 q->emitResult();
57 }
58
59 QString jobDebuggingString() const override;
60};
61
62QString Akonadi::TransactionSequencePrivate::jobDebuggingString() const
63{
64 // TODO add state
65 return QStringLiteral("autocommit %1").arg(mAutoCommit);
66}
67
69 : Job(new TransactionSequencePrivate(this), parent)
70{
71}
72
76
78{
80
81 // Don't abort the rollback job, while keeping the state set.
82 if (d->mState == TransactionSequencePrivate::RollingBack) {
83 return Job::addSubjob(job);
84 }
85
86 if (error()) {
87 // This can happen if a rollback is in progress, so make sure we don't set the state back to running.
88 job->kill(EmitResult);
89 return false;
90 }
91 // TODO KDE5: remove property hack once SpecialCollectionsRequestJob has been fixed
92 if (d->mState == TransactionSequencePrivate::Idle && !property("transactionsDisabled").toBool()) {
93 d->mState = TransactionSequencePrivate::Running; // needs to be set before creating the transaction job to avoid infinite recursion
94 new TransactionBeginJob(this);
95 } else {
96 d->mState = TransactionSequencePrivate::Running;
97 }
98 return Job::addSubjob(job);
99}
100
101void TransactionSequence::slotResult(KJob *job)
102{
104
105 if (!job->error() || d->mIgnoredErrorJobs.contains(job)) {
106 // If we have an error but want to ignore it, we can't call Job::slotResult
107 // because it would confuse the subjob queue processing logic. Just removing
108 // the subjob instead is fine.
109 if (!job->error()) {
110 Job::slotResult(job);
111 } else {
113 }
114
115 if (!hasSubjobs()) {
116 if (d->mState == TransactionSequencePrivate::WaitingForSubjobs) {
117 if (property("transactionsDisabled").toBool()) {
118 emitResult();
119 return;
120 }
121 d->mState = TransactionSequencePrivate::Committing;
122 auto job = new TransactionCommitJob(this);
123 connect(job, &TransactionCommitJob::result, this, [d](KJob *job) {
124 d->commitResult(job);
125 });
126 }
127 }
128 } else if (job->error() == KJob::KilledJobError) {
129 Job::slotResult(job);
130 } else {
131 setError(job->error());
132 setErrorText(job->errorText());
133 removeSubjob(job);
134
135 // cancel all subjobs in case someone else is listening (such as ItemSync)
136 const auto subjobs = this->subjobs();
137 for (KJob *job : subjobs) {
138 job->kill(KJob::EmitResult);
139 }
140 clearSubjobs();
141
142 if (d->mState == TransactionSequencePrivate::Running || d->mState == TransactionSequencePrivate::WaitingForSubjobs) {
143 if (property("transactionsDisabled").toBool()) {
144 emitResult();
145 return;
146 }
147 d->mState = TransactionSequencePrivate::RollingBack;
148 auto job = new TransactionRollbackJob(this);
149 connect(job, &TransactionRollbackJob::result, this, [d](KJob *job) {
150 d->rollbackResult(job);
151 });
152 }
153 }
154}
155
157{
159
160 if (d->mState == TransactionSequencePrivate::Running) {
161 d->mState = TransactionSequencePrivate::WaitingForSubjobs;
162 } else if (d->mState == TransactionSequencePrivate::RollingBack) {
163 return;
164 } else {
165 // we never got any subjobs, that means we never started a transaction
166 // so we can just quit here
167 if (d->mState == TransactionSequencePrivate::Idle) {
168 emitResult();
169 }
170 return;
171 }
172
173 if (subjobs().isEmpty()) {
174 if (property("transactionsDisabled").toBool()) {
175 emitResult();
176 return;
177 }
178 if (!error()) {
179 d->mState = TransactionSequencePrivate::Committing;
180 auto job = new TransactionCommitJob(this);
181 connect(job, &TransactionCommitJob::result, this, [d](KJob *job) {
182 d->commitResult(job);
183 });
184 } else {
185 d->mState = TransactionSequencePrivate::RollingBack;
186 auto job = new TransactionRollbackJob(this);
187 connect(job, &TransactionRollbackJob::result, this, [d](KJob *job) {
188 d->rollbackResult(job);
189 });
190 }
191 }
192}
193
195{
197
198 // make sure this is one of our sub jobs
199 Q_ASSERT(subjobs().contains(job));
200
201 d->mIgnoredErrorJobs.insert(job);
202}
203
205{
207
208 if (d->mAutoCommit) {
209 if (d->mState == TransactionSequencePrivate::Idle) {
210 emitResult();
211 } else {
212 commit();
213 }
214 }
215}
216
218{
220 d->mAutoCommit = enable;
221}
222
224{
226
228 // we never really started
229 if (d->mState == TransactionSequencePrivate::Idle) {
230 emitResult();
231 return;
232 }
233
234 const auto jobList = subjobs();
235 for (KJob *job : jobList) {
236 // Killing the current subjob means forcibly closing the akonadiserver socket
237 // (with a bit of delay since it happens in a secondary thread)
238 // which means the next job gets disconnected
239 // and the itemsync finishes with error "Cannot connect to the Akonadi service.", not ideal
240 if (job != d->mCurrentSubJob) {
241 job->kill(KJob::EmitResult);
242 }
243 }
244
245 d->mState = TransactionSequencePrivate::RollingBack;
246 auto job = new TransactionRollbackJob(this);
247 connect(job, &TransactionRollbackJob::result, this, [d](KJob *job) {
248 d->rollbackResult(job);
249 });
250}
251
252#include "moc_transactionsequence.cpp"
Base class for all actions in the Akonadi storage.
Definition job.h:81
@ UserCanceled
The user canceled this job.
Definition job.h:101
bool removeSubjob(KJob *job) override
Removes the given subjob of this job.
Definition job.cpp:369
bool addSubjob(KJob *job) override
Adds the given job as a subjob to this job.
Definition job.cpp:355
Job that begins a session-global transaction.
Job that commits a session-global transaction.
Job that aborts a session-global transaction.
Base class for jobs that need to run a sequence of sub-jobs in a transaction.
~TransactionSequence() override
Destroys the transaction sequence.
void commit()
Commits the transaction as soon as all pending sub-jobs finished successfully.
bool addSubjob(KJob *job) override
Adds the given job as a subjob to this job.
TransactionSequence(QObject *parent=nullptr)
Creates a new transaction sequence.
void setIgnoreJobFailure(KJob *job)
Sets which job of the sequence might fail without rolling back the complete transaction.
void doStart() override
This method must be reimplemented in the concrete jobs.
void rollback()
Rolls back the current transaction as soon as possible.
void setAutomaticCommittingEnabled(bool enable)
Disable automatic committing.
bool hasSubjobs() const
const QList< KJob * > & subjobs() const
void clearSubjobs()
void setErrorText(const QString &errorText)
void emitResult()
int error() const
void result(KJob *job)
void setError(int errorCode)
QString errorText() const
bool kill(KJob::KillVerbosity verbosity=KJob::Quietly)
Helper integration between Akonadi and Qt.
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QVariant property(const char *name) const const
QString arg(Args &&... args) const const
Q_D(Todo)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:08:29 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.