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