Akonadi

transactionsequence.cpp
1 /*
2  SPDX-FileCopyrightText: 2006-2008 Volker Krause <[email protected]>
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 
15 using namespace Akonadi;
16 
17 class Akonadi::TransactionSequencePrivate : public JobPrivate
18 {
19 public:
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 
62 QString 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 
74 {
75 }
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 
101 void 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 {
112  Job::removeSubjob(job);
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, [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"
void setErrorText(const QString &errorText)
bool addSubjob(KJob *job) override
Adds the given job as a subjob to this job.
Definition: job.cpp:355
void clearSubjobs()
void result(KJob *job)
void commit()
Commits the transaction as soon as all pending sub-jobs finished successfully.
~TransactionSequence() override
Destroys the transaction sequence.
bool kill(KillVerbosity verbosity=Quietly)
void doStart() override
This method must be reimplemented in the concrete jobs.
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
Job that begins a session-global transaction.
const QList< KJob * > & subjobs() const
bool removeSubjob(KJob *job) override
Removes the given subjob of this job.
Definition: job.cpp:369
Job that aborts a session-global transaction.
bool addSubjob(KJob *job) override
Adds the given job as a subjob to this job.
QString errorText() const
Job that commits a session-global transaction.
Base class for all actions in the Akonadi storage.
Definition: job.h:80
Base class for jobs that need to run a sequence of sub-jobs in a transaction.
void setAutomaticCommittingEnabled(bool enable)
Disable automatic committing.
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
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 emitResult()
int error() const
void rollback()
Rolls back the current transaction as soon as possible.
bool hasSubjobs() const
@ UserCanceled
The user canceled this job.
Definition: job.h:101
void setError(int errorCode)
Q_D(Todo)
QVariant property(const char *name) const const
Helper integration between Akonadi and Qt.
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Tue Oct 3 2023 04:02:00 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.