1 /*
2 * SPDX-FileCopyrightText: 2015 Kevin Ottens <ervin@kde.org>
3 SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
4 */
5
6
7 #include "akonadifakestorage.h"
8
9 #include <QTimer>
10
11 #include "akonadi/akonadistoragesettings.h"
12 #include "akonadifakedata.h"
13 #include "akonadifakejobs.h"
14
15 #include "utils/jobhandler.h"
16
17 using namespace Testlib;
18
19 class AkonadiFakeTransaction : public FakeJob
20 {
21 Q_OBJECT
22 public:
AkonadiFakeTransaction(QObject * parent=nullptr)23 explicit AkonadiFakeTransaction(QObject *parent = nullptr)
24 : FakeJob(parent),
25 m_nextIdx(0)
26 {
27 }
28
29 private slots:
onTimeout()30 void onTimeout() override
31 {
32 auto jobs = childJobs();
33 if (m_nextIdx == 0) {
34 const auto it = std::find_if(jobs.constBegin(), jobs.constEnd(),
35 [] (FakeJob *job) { return job->expectedError() != 0; });
36 if (it != jobs.constEnd()) {
37 setError((*it)->expectedError());
38 setErrorText((*it)->expectedErrorText());
39 emitResult();
40 return;
41 }
42 }
43
44 if (m_nextIdx >= jobs.size()) {
45 emitResult();
46 return;
47 }
48
49 auto job = jobs[m_nextIdx];
50 connect(job, &KJob::result, this, &AkonadiFakeTransaction::onTimeout);
51 job->start();
52 m_nextIdx++;
53 }
54
55 private:
childJobs() const56 QList<FakeJob*> childJobs() const
57 {
58 QList<FakeJob*> jobs = findChildren<FakeJob*>();
59 jobs.erase(std::remove_if(jobs.begin(), jobs.end(),
60 [this] (FakeJob *job) {
61 return job->parent() != this;
62 }),
63 jobs.end());
64 return jobs;
65 }
66
67 int m_nextIdx;
68 };
69
startModeForParent(QObject * parent)70 Utils::JobHandler::StartMode startModeForParent(QObject *parent)
71 {
72 bool isTransaction = qobject_cast<AkonadiFakeTransaction*>(parent);
73 return isTransaction ? Utils::JobHandler::ManualStart
74 : Utils::JobHandler::AutoStart;
75 }
76
noop()77 void noop() {}
78
AkonadiFakeStorage(AkonadiFakeData * data)79 AkonadiFakeStorage::AkonadiFakeStorage(AkonadiFakeData *data)
80 : m_data(data)
81 {
82 }
83
defaultCollection()84 Akonadi::Collection AkonadiFakeStorage::defaultCollection()
85 {
86 return Akonadi::StorageSettings::instance().defaultCollection();
87 }
88
createItem(Akonadi::Item item,Akonadi::Collection collection)89 KJob *AkonadiFakeStorage::createItem(Akonadi::Item item, Akonadi::Collection collection)
90 {
91 Q_ASSERT(!item.isValid());
92
93 auto job = new FakeJob;
94 if (!m_data->item(item.id()).isValid()) {
95 job->setExpectedError(m_data->storageBehavior().createNextItemErrorCode(),
96 m_data->storageBehavior().createNextItemErrorText());
97 Utils::JobHandler::install(job, [=] () mutable {
98 if (!job->error()) {
99 item.setId(m_data->maxItemId() + 1);
100 item.setParentCollection(collection);
101 // Force payload detach
102 item.setPayloadFromData(item.payloadData());
103 m_data->createItem(item);
104 }
105 });
106 } else {
107 job->setExpectedError(1, QStringLiteral("Item already exists"));
108 Utils::JobHandler::install(job, noop);
109 }
110 return job;
111 }
112
updateItem(Akonadi::Item item,QObject * parent)113 KJob *AkonadiFakeStorage::updateItem(Akonadi::Item item, QObject *parent)
114 {
115 auto job = new FakeJob(parent);
116 auto startMode = startModeForParent(parent);
117
118 if (m_data->item(item.id()).isValid()) {
119 job->setExpectedError(m_data->storageBehavior().updateNextItemErrorCode(),
120 m_data->storageBehavior().updateNextItemErrorText());
121 Utils::JobHandler::install(job, [=] () mutable {
122 if (!job->error()) {
123 // Force payload detach
124 item.setPayloadFromData(item.payloadData());
125 m_data->modifyItem(item);
126 }
127 }, startMode);
128 } else {
129 job->setExpectedError(1, QStringLiteral("Item doesn't exist"));
130 Utils::JobHandler::install(job, noop, startMode);
131 }
132 return job;
133 }
134
removeItem(Akonadi::Item item,QObject * parent)135 KJob *AkonadiFakeStorage::removeItem(Akonadi::Item item, QObject *parent)
136 {
137 auto job = new FakeJob(parent);
138 if (m_data->item(item.id()).isValid()) {
139 Utils::JobHandler::install(job, [=] {
140 if (!job->error()) {
141 m_data->removeItem(item);
142 }
143 });
144 } else {
145 job->setExpectedError(1, QStringLiteral("Item doesn't exist"));
146 Utils::JobHandler::install(job, noop);
147 }
148 return job;
149 }
150
removeItems(Akonadi::Item::List items,QObject * parent)151 KJob *AkonadiFakeStorage::removeItems(Akonadi::Item::List items, QObject *parent)
152 {
153 auto job = new FakeJob;
154 auto startMode = startModeForParent(parent);
155 bool allItemsExist = std::all_of(items.constBegin(), items.constEnd(),
156 [=] (const Akonadi::Item &item) {
157 return m_data->item(item.id()).isValid();
158 });
159
160 if (allItemsExist) {
161 job->setExpectedError(m_data->storageBehavior().deleteNextItemErrorCode(),
162 m_data->storageBehavior().deleteNextItemErrorText());
163 Utils::JobHandler::install(job, [=] {
164 if (!job->error()) {
165 foreach (const Akonadi::Item &item, items) {
166 m_data->removeItem(item);
167 }
168 }
169 }, startMode);
170 } else {
171 job->setExpectedError(1, QStringLiteral("At least one item doesn't exist"));
172 Utils::JobHandler::install(job, noop, startMode);
173 }
174 return job;
175 }
176
moveItem(Akonadi::Item item,Akonadi::Collection collection,QObject * parent)177 KJob *AkonadiFakeStorage::moveItem(Akonadi::Item item, Akonadi::Collection collection, QObject *parent)
178 {
179 auto job = new FakeJob(parent);
180 auto startMode = startModeForParent(parent);
181 if (m_data->item(item.id()).isValid()
182 && m_data->collection(collection.id()).isValid()) {
183 Utils::JobHandler::install(job, [=] () mutable {
184 if (!job->error()) {
185 item.setParentCollection(collection);
186 // Force payload detach
187 item.setPayloadFromData(item.payloadData());
188 m_data->modifyItem(item);
189 }
190 }, startMode);
191 } else {
192 job->setExpectedError(1, QStringLiteral("The item or the collection doesn't exist"));
193 Utils::JobHandler::install(job, noop, startMode);
194 }
195 return job;
196 }
197
moveItems(Akonadi::Item::List items,Akonadi::Collection collection,QObject * parent)198 KJob *AkonadiFakeStorage::moveItems(Akonadi::Item::List items, Akonadi::Collection collection, QObject *parent)
199 {
200 using namespace std::placeholders;
201
202 auto job = new FakeJob(parent);
203 auto startMode = startModeForParent(parent);
204 bool allItemsExist = std::all_of(items.constBegin(), items.constEnd(),
205 [=] (const Akonadi::Item &item) {
206 return m_data->item(item.id()).isValid();
207 });
208
209 if (allItemsExist
210 && m_data->collection(collection.id()).isValid()) {
211 Utils::JobHandler::install(job, [=] () mutable {
212 if (!job->error()) {
213 std::transform(items.constBegin(), items.constEnd(),
214 items.begin(),
215 [=] (const Akonadi::Item &item) {
216 auto result = item;
217 result.setParentCollection(collection);
218 // Force payload detach
219 result.setPayloadFromData(result.payloadData());
220 return result;
221 });
222
223 foreach (const Akonadi::Item &item, items) {
224 m_data->modifyItem(item);
225 }
226 }
227 }, startMode);
228 } else {
229 job->setExpectedError(1, QStringLiteral("One of the items or the collection doesn't exist"));
230 Utils::JobHandler::install(job, noop, startMode);
231 }
232 return job;
233 }
234
createCollection(Akonadi::Collection collection,QObject * parent)235 KJob *AkonadiFakeStorage::createCollection(Akonadi::Collection collection, QObject *parent)
236 {
237 Q_ASSERT(!collection.isValid());
238
239 auto job = new FakeJob(parent);
240 auto startMode = startModeForParent(parent);
241 if (!m_data->collection(collection.id()).isValid()) {
242 Utils::JobHandler::install(job, [=] () mutable {
243 if (!job->error()) {
244 collection.setId(m_data->maxCollectionId() + 1);
245 m_data->createCollection(collection);
246 }
247 }, startMode);
248 } else {
249 job->setExpectedError(1, QStringLiteral("The collection already exists"));
250 Utils::JobHandler::install(job, noop, startMode);
251 }
252 return job;
253 }
254
updateCollection(Akonadi::Collection collection,QObject * parent)255 KJob *AkonadiFakeStorage::updateCollection(Akonadi::Collection collection, QObject *parent)
256 {
257 auto job = new FakeJob(parent);
258 auto startMode = startModeForParent(parent);
259 if (m_data->collection(collection.id()).isValid()) {
260 Utils::JobHandler::install(job, [=] {
261 if (!job->error()) {
262 m_data->modifyCollection(collection);
263 }
264 }, startMode);
265 } else {
266 job->setExpectedError(1, QStringLiteral("The collection doesn't exist"));
267 Utils::JobHandler::install(job, noop, startMode);
268 }
269 return job;
270 }
271
removeCollection(Akonadi::Collection collection,QObject * parent)272 KJob *AkonadiFakeStorage::removeCollection(Akonadi::Collection collection, QObject *parent)
273 {
274 auto job = new FakeJob(parent);
275 auto startMode = startModeForParent(parent);
276 if (m_data->collection(collection.id()).isValid()) {
277 Utils::JobHandler::install(job, [=] {
278 if (!job->error()) {
279 m_data->removeCollection(collection);
280 }
281 }, startMode);
282 } else {
283 job->setExpectedError(1, QStringLiteral("The collection doesn't exist"));
284 Utils::JobHandler::install(job, noop, startMode);
285 }
286 return job;
287 }
288
createTransaction(QObject * parent)289 KJob *AkonadiFakeStorage::createTransaction(QObject *parent)
290 {
291 auto job = new AkonadiFakeTransaction(parent);
292 Utils::JobHandler::install(job, noop);
293 return job;
294 }
295
fetchCollections(Akonadi::Collection collection,Akonadi::StorageInterface::FetchDepth depth,QObject * parent)296 Akonadi::CollectionFetchJobInterface *AkonadiFakeStorage::fetchCollections(Akonadi::Collection collection,
297 Akonadi::StorageInterface::FetchDepth depth,
298 QObject *parent)
299 {
300 auto job = new AkonadiFakeCollectionFetchJob(parent);
301 auto children = Akonadi::Collection::List();
302
303 switch (depth) {
304 case Base:
305 children << m_data->collection(findId(collection));
306 break;
307 case FirstLevel:
308 children << m_data->childCollections(findId(collection));
309 break;
310 case Recursive:
311 children = collectChildren(collection);
312 break;
313 }
314
315 auto collections = children;
316
317 if (depth != Base) {
318 // Replace the dummy parents in the ancestor chain with proper ones
319 // full of juicy data
320 using namespace std::placeholders;
321 auto completeCollection = std::bind(&AkonadiFakeData::reconstructAncestors,
322 m_data, _1, collection);
323 std::transform(collections.begin(), collections.end(),
324 collections.begin(), completeCollection);
325 }
326
327 const auto behavior = m_data->storageBehavior().fetchCollectionsBehavior(collection.id());
328 if (behavior == AkonadiFakeStorageBehavior::NormalFetch)
329 job->setCollections(collections);
330 job->setExpectedError(m_data->storageBehavior().fetchCollectionsErrorCode(collection.id()));
331 Utils::JobHandler::install(job, noop);
332 return job;
333 }
334
fetchItems(Akonadi::Collection collection,QObject * parent)335 Akonadi::ItemFetchJobInterface *AkonadiFakeStorage::fetchItems(Akonadi::Collection collection, QObject *parent)
336 {
337 auto items = m_data->childItems(findId(collection));
338 std::transform(items.begin(), items.end(),
339 items.begin(),
340 [this] (const Akonadi::Item &item) {
341 auto result = m_data->reconstructItemDependencies(item);
342 // Force payload detach
343 result.setPayloadFromData(result.payloadData());
344 return result;
345 });
346
347 auto job = new AkonadiFakeItemFetchJob(parent);
348 const auto behavior = m_data->storageBehavior().fetchItemsBehavior(collection.id());
349 if (behavior == AkonadiFakeStorageBehavior::NormalFetch)
350 job->setItems(items);
351 job->setExpectedError(m_data->storageBehavior().fetchItemsErrorCode(collection.id()));
352 Utils::JobHandler::install(job, noop);
353 return job;
354 }
355
fetchItem(Akonadi::Item item,QObject * parent)356 Akonadi::ItemFetchJobInterface *AkonadiFakeStorage::fetchItem(Akonadi::Item item, QObject *parent)
357 {
358 auto fullItem = m_data->item(findId(item));
359 fullItem = m_data->reconstructItemDependencies(fullItem);
360 // Force payload detach
361 fullItem.setPayloadFromData(fullItem.payloadData());
362
363 auto job = new AkonadiFakeItemFetchJob(parent);
364 const auto behavior = m_data->storageBehavior().fetchItemBehavior(item.id());
365 if (behavior == AkonadiFakeStorageBehavior::NormalFetch)
366 job->setItems(Akonadi::Item::List() << fullItem);
367 job->setExpectedError(m_data->storageBehavior().fetchItemErrorCode(item.id()));
368 Utils::JobHandler::install(job, noop);
369 return job;
370 }
371
findId(const Akonadi::Collection & collection)372 Akonadi::Collection::Id AkonadiFakeStorage::findId(const Akonadi::Collection &collection)
373 {
374 if (collection.isValid() || collection.remoteId().isEmpty())
375 return collection.id();
376
377 const auto remoteId = collection.remoteId();
378 auto collections = m_data->collections();
379 auto result = std::find_if(collections.constBegin(), collections.constEnd(),
380 [remoteId] (const Akonadi::Collection &collection) {
381 return collection.remoteId() == remoteId;
382 });
383 return (result != collections.constEnd()) ? result->id() : collection.id();
384 }
385
findId(const Akonadi::Item & item)386 Akonadi::Item::Id AkonadiFakeStorage::findId(const Akonadi::Item &item)
387 {
388 if (item.isValid() || item.remoteId().isEmpty())
389 return item.id();
390
391 const auto remoteId = item.remoteId();
392 auto items = m_data->items();
393 auto result = std::find_if(items.constBegin(), items.constEnd(),
394 [remoteId] (const Akonadi::Item &item) {
395 return item.remoteId() == remoteId;
396 });
397 return (result != items.constEnd()) ? result->id() : item.id();
398
399 }
400
collectChildren(const Akonadi::Collection & root)401 Akonadi::Collection::List AkonadiFakeStorage::collectChildren(const Akonadi::Collection &root)
402 {
403 auto collections = Akonadi::Collection::List();
404
405 foreach (const auto &child, m_data->childCollections(findId(root))) {
406 if (child.enabled())
407 collections << m_data->collection(findId(child));
408 collections += collectChildren(child);
409 }
410
411 return collections;
412 }
413
414 #include "akonadifakestorage.moc"
415