1/*
2 Copyright (c) 2006-2008 Volker Krause <vkrause@kde.org>
3
4 This library is free software; you can redistribute it and/or modify it
5 under the terms of the GNU Library General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or (at your
7 option) any later version.
8
9 This library is distributed in the hope that it will be useful, but WITHOUT
10 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
12 License for more details.
13
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to the
16 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17 02110-1301, USA.
18*/
19
20#include "transactionsequence.h"
21#include "transactionjobs.h"
22
23#include "job_p.h"
24
25#include <QtCore/QSet>
26#include <QtCore/QVariant>
27
28using namespace Akonadi;
29
30class Akonadi::TransactionSequencePrivate : public JobPrivate
31{
32public:
33 TransactionSequencePrivate(TransactionSequence *parent)
34 : JobPrivate(parent)
35 , mState(Idle)
36 , mAutoCommit(true)
37 {
38 }
39
40 enum TransactionState {
41 Idle,
42 Running,
43 WaitingForSubjobs,
44 RollingBack,
45 Committing
46 };
47
48 Q_DECLARE_PUBLIC(TransactionSequence)
49
50 TransactionState mState;
51 QSet<KJob *> mIgnoredErrorJobs;
52 bool mAutoCommit;
53
54 void commitResult(KJob *job)
55 {
56 Q_Q(TransactionSequence);
57
58 if (job->error()) {
59 q->setError(job->error());
60 q->setErrorText(job->errorText());
61 }
62 q->emitResult();
63 }
64
65 void rollbackResult(KJob *job)
66 {
67 Q_Q(TransactionSequence);
68
69 Q_UNUSED(job);
70 q->emitResult();
71 }
72};
73
74TransactionSequence::TransactionSequence(QObject *parent)
75 : Job(new TransactionSequencePrivate(this), parent)
76{
77}
78
79TransactionSequence::~TransactionSequence()
80{
81}
82
83bool TransactionSequence::addSubjob(KJob *job)
84{
85 Q_D(TransactionSequence);
86
87 // TODO KDE5: remove property hack once SpecialCollectionsRequestJob has been fixed
88 if (d->mState == TransactionSequencePrivate::Idle && !property("transactionsDisabled").toBool()) {
89 d->mState = TransactionSequencePrivate::Running; // needs to be set before creating the transaction job to avoid infinite recursion
90 new TransactionBeginJob(this);
91 } else {
92 d->mState = TransactionSequencePrivate::Running;
93 }
94 return Job::addSubjob(job);
95}
96
97void TransactionSequence::slotResult(KJob *job)
98{
99 Q_D(TransactionSequence);
100
101 if (!job->error() || d->mIgnoredErrorJobs.contains(job)) {
102 // If we have an error but want to ignore it, we can't call Job::slotResult
103 // because it would confuse the subjob queue processing logic. Just removing
104 // the subjob instead is fine.
105 if (!job->error()) {
106 Job::slotResult(job);
107 } else {
108 Job::removeSubjob(job);
109 }
110
111 if (!hasSubjobs() && d->mState == TransactionSequencePrivate::WaitingForSubjobs) {
112 if (property("transactionsDisabled").toBool()) {
113 emitResult();
114 return;
115 }
116 d->mState = TransactionSequencePrivate::Committing;
117 TransactionCommitJob *job = new TransactionCommitJob(this);
118 connect(job, SIGNAL(result(KJob*)), SLOT(commitResult(KJob*)));
119 }
120 } else {
121 setError(job->error());
122 setErrorText(job->errorText());
123 removeSubjob(job);
124
125 // cancel all subjobs in case someone else is listening (such as ItemSync), but without notifying ourselves again
126 foreach (KJob *job, subjobs()) {
127 disconnect(job, SIGNAL(result(KJob*)), this, SLOT(slotResult(KJob*)));
128 job->kill(EmitResult);
129 }
130 clearSubjobs();
131
132 if (d->mState == TransactionSequencePrivate::Running || d->mState == TransactionSequencePrivate::WaitingForSubjobs) {
133 if (property("transactionsDisabled").toBool()) {
134 emitResult();
135 return;
136 }
137 d->mState = TransactionSequencePrivate::RollingBack;
138 TransactionRollbackJob *job = new TransactionRollbackJob(this);
139 connect(job, SIGNAL(result(KJob*)), SLOT(rollbackResult(KJob*)));
140 }
141 }
142}
143
144void TransactionSequence::commit()
145{
146 Q_D(TransactionSequence);
147
148 if (d->mState == TransactionSequencePrivate::Running) {
149 d->mState = TransactionSequencePrivate::WaitingForSubjobs;
150 } else {
151 // we never got any subjobs, that means we never started a transaction
152 // so we can just quit here
153 if (d->mState == TransactionSequencePrivate::Idle) {
154 emitResult();
155 }
156 return;
157 }
158
159 if (subjobs().isEmpty()) {
160 if (property("transactionsDisabled").toBool()) {
161 emitResult();
162 return;
163 }
164 if (!error()) {
165 d->mState = TransactionSequencePrivate::Committing;
166 TransactionCommitJob *job = new TransactionCommitJob(this);
167 connect(job, SIGNAL(result(KJob*)), SLOT(commitResult(KJob*)));
168 } else {
169 d->mState = TransactionSequencePrivate::RollingBack;
170 TransactionRollbackJob *job = new TransactionRollbackJob(this);
171 connect(job, SIGNAL(result(KJob*)), SLOT(rollbackResult(KJob*)));
172 }
173 }
174}
175
176void TransactionSequence::setIgnoreJobFailure(KJob *job)
177{
178 Q_D(TransactionSequence);
179
180 // make sure this is one of our sub jobs
181 Q_ASSERT(subjobs().contains(job));
182
183 d->mIgnoredErrorJobs.insert(job);
184}
185
186void TransactionSequence::doStart()
187{
188 Q_D(TransactionSequence);
189
190 if (d->mAutoCommit) {
191 if (d->mState == TransactionSequencePrivate::Idle) {
192 emitResult();
193 } else {
194 commit();
195 }
196 }
197}
198
199void TransactionSequence::setAutomaticCommittingEnabled(bool enable)
200{
201 Q_D(TransactionSequence);
202 d->mAutoCommit = enable;
203}
204
205void TransactionSequence::rollback()
206{
207 Q_D(TransactionSequence);
208
209 setError(UserCanceled);
210 // we never really started
211 if (d->mState == TransactionSequencePrivate::Idle) {
212 emitResult();
213 return;
214 }
215
216 // TODO cancel not yet executed sub-jobs here
217
218 d->mState = TransactionSequencePrivate::RollingBack;
219 TransactionRollbackJob *job = new TransactionRollbackJob(this);
220 connect(job, SIGNAL(result(KJob*)), SLOT(rollbackResult(KJob*)));
221}
222
223#include "moc_transactionsequence.cpp"
224