1 /*
2  * SPDX-FileCopyrightText: 2015 Mario Bensi <mbensi@ipsquad.net>
3  * SPDX-FileCopyrightText: 2017 Kevin Ottens <ervin@kde.org>
4  * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
5  */
6 
7 
8 #include "akonadicachingstorage.h"
9 #include "akonadistorage.h"
10 
11 #include "akonadicollectionfetchjobinterface.h"
12 #include "akonadiitemfetchjobinterface.h"
13 
14 #include <QTimer>
15 
16 using namespace Akonadi;
17 
18 class CachingCollectionFetchJob : public KCompositeJob, public CollectionFetchJobInterface
19 {
20     Q_OBJECT
21 public:
CachingCollectionFetchJob(const StorageInterface::Ptr & storage,const Cache::Ptr & cache,const Collection & collection,StorageInterface::FetchDepth depth,QObject * parent=nullptr)22     CachingCollectionFetchJob(const StorageInterface::Ptr &storage,
23                               const Cache::Ptr &cache,
24                               const Collection &collection,
25                               StorageInterface::FetchDepth depth,
26                               QObject *parent = nullptr)
27         : KCompositeJob(parent),
28           m_started(false),
29           m_storage(storage),
30           m_cache(cache),
31           m_collection(collection),
32           m_depth(depth)
33     {
34         QTimer::singleShot(0, this, &CachingCollectionFetchJob::start);
35     }
36 
start()37     void start() override
38     {
39         if (m_started)
40             return;
41 
42         if (m_cache->isCollectionListPopulated()) {
43             QTimer::singleShot(0, this, &CachingCollectionFetchJob::retrieveFromCache);
44         } else {
45             auto job = m_storage->fetchCollections(Akonadi::Collection::root(),
46                                                    Akonadi::StorageInterface::Recursive,
47                                                    this);
48             job->setResource(m_resource);
49             addSubjob(job->kjob());
50         }
51 
52         m_started = true;
53     }
54 
55 
collections() const56     Collection::List collections() const override
57     {
58         const auto isInputCollection = [this] (const Collection &collection) {
59             return collection.id() == m_collection.id()
60                 || (!m_collection.remoteId().isEmpty() && collection.remoteId() == m_collection.remoteId());
61         };
62 
63         if (m_depth == StorageInterface::Base) {
64             auto it = std::find_if(m_collections.cbegin(), m_collections.cend(), isInputCollection);
65             if (it != m_collections.cend())
66                 return Collection::List() << *it;
67             else
68                 return Collection::List();
69         }
70 
71         auto collections = m_collections;
72         auto it = collections.begin();
73 
74         if (m_depth == StorageInterface::FirstLevel) {
75             it = std::remove_if(collections.begin(), collections.end(),
76                                 [isInputCollection] (const Collection &collection) {
77                                     return !isInputCollection(collection.parentCollection());
78                                 });
79         } else {
80             it = std::remove_if(collections.begin(), collections.end(),
81                                 [isInputCollection] (const Collection &collection) {
82                                     auto parent = collection.parentCollection();
83                                     while (parent.isValid() && !isInputCollection(parent))
84                                         parent = parent.parentCollection();
85                                     return !isInputCollection(parent);
86                                 });
87         }
88 
89         collections.erase(it, collections.end());
90         return collections;
91     }
92 
setResource(const QString & resource)93     void setResource(const QString &resource) override
94     {
95         m_resource = resource;
96     }
97 
98 private:
slotResult(KJob * kjob)99     void slotResult(KJob *kjob) override
100     {
101         if (kjob->error()) {
102             KCompositeJob::slotResult(kjob);
103             return;
104         }
105 
106         auto job = dynamic_cast<CollectionFetchJobInterface*>(kjob);
107         Q_ASSERT(job);
108         auto cachedCollections = job->collections();
109         for (const auto &collection : job->collections()) {
110             auto parent = collection.parentCollection();
111             while (parent.isValid() && parent != Akonadi::Collection::root()) {
112                 if (!cachedCollections.contains(parent)) {
113                     cachedCollections.append(parent);
114                 }
115                 parent = parent.parentCollection();
116             }
117         }
118         m_cache->setCollections(cachedCollections);
119         m_collections = job->collections();
120         emitResult();
121     }
122 
retrieveFromCache()123     void retrieveFromCache()
124     {
125         m_collections = m_cache->collections();
126         emitResult();
127     }
128 
129     bool m_started;
130     StorageInterface::Ptr m_storage;
131     Cache::Ptr m_cache;
132     QString m_resource;
133     const Collection m_collection;
134     const StorageInterface::FetchDepth m_depth;
135     Collection::List m_collections;
136 };
137 
138 class CachingCollectionItemsFetchJob : public KCompositeJob, public ItemFetchJobInterface
139 {
140     Q_OBJECT
141 public:
CachingCollectionItemsFetchJob(const StorageInterface::Ptr & storage,const Cache::Ptr & cache,const Collection & collection,QObject * parent=nullptr)142     CachingCollectionItemsFetchJob(const StorageInterface::Ptr &storage,
143                                    const Cache::Ptr &cache,
144                                    const Collection &collection,
145                                    QObject *parent = nullptr)
146         : KCompositeJob(parent),
147           m_started(false),
148           m_storage(storage),
149           m_cache(cache),
150           m_collection(collection)
151     {
152         QTimer::singleShot(0, this, &CachingCollectionItemsFetchJob::start);
153     }
154 
start()155     void start() override
156     {
157         if (m_started)
158             return;
159 
160         if (m_cache->isCollectionPopulated(m_collection.id())) {
161             QTimer::singleShot(0, this, &CachingCollectionItemsFetchJob::retrieveFromCache);
162         } else {
163             auto job = m_storage->fetchItems(m_collection, this);
164             addSubjob(job->kjob());
165         }
166 
167         m_started = true;
168     }
169 
170 
items() const171     Item::List items() const override
172     {
173         return m_items;
174     }
175 
setCollection(const Collection & collection)176     void setCollection(const Collection &collection) override
177     {
178         m_collection = collection;
179     }
180 
181 private:
slotResult(KJob * kjob)182     void slotResult(KJob *kjob) override
183     {
184         if (kjob->error()) {
185             KCompositeJob::slotResult(kjob);
186             return;
187         }
188 
189         auto job = dynamic_cast<ItemFetchJobInterface*>(kjob);
190         Q_ASSERT(job);
191         m_items = job->items();
192         m_cache->populateCollection(m_collection, m_items);
193         emitResult();
194     }
195 
retrieveFromCache()196     void retrieveFromCache()
197     {
198         m_items = m_cache->items(m_collection);
199         emitResult();
200     }
201 
202     bool m_started;
203     StorageInterface::Ptr m_storage;
204     Cache::Ptr m_cache;
205     Collection m_collection;
206     Item::List m_items;
207 };
208 
209 class CachingSingleItemFetchJob : public KCompositeJob, public ItemFetchJobInterface
210 {
211     Q_OBJECT
212 public:
CachingSingleItemFetchJob(const StorageInterface::Ptr & storage,const Cache::Ptr & cache,const Item & item,QObject * parent=nullptr)213     CachingSingleItemFetchJob(const StorageInterface::Ptr &storage,
214                               const Cache::Ptr &cache,
215                               const Item &item,
216                                    QObject *parent = nullptr)
217         : KCompositeJob(parent),
218           m_started(false),
219           m_storage(storage),
220           m_cache(cache),
221           m_item(item)
222     {
223         QTimer::singleShot(0, this, &CachingSingleItemFetchJob::start);
224     }
225 
start()226     void start() override
227     {
228         if (m_started)
229             return;
230 
231         const auto item = m_cache->item(m_item.id());
232         if (item.isValid()) {
233             QTimer::singleShot(0, this, [this, item] {
234                 retrieveFromCache(item);
235             });
236         } else {
237             auto job = m_storage->fetchItem(m_item, this);
238             job->setCollection(m_collection);
239             addSubjob(job->kjob());
240         }
241 
242         m_started = true;
243     }
244 
245 
items() const246     Item::List items() const override
247     {
248         return m_items;
249     }
250 
setCollection(const Collection & collection)251     void setCollection(const Collection &collection) override
252     {
253         m_collection = collection;
254     }
255 
256 private:
slotResult(KJob * kjob)257     void slotResult(KJob *kjob) override
258     {
259         if (kjob->error()) {
260             KCompositeJob::slotResult(kjob);
261             return;
262         }
263 
264         auto job = dynamic_cast<ItemFetchJobInterface*>(kjob);
265         Q_ASSERT(job);
266         m_items = job->items();
267         emitResult();
268     }
269 
retrieveFromCache(const Item & item)270     void retrieveFromCache(const Item &item)
271     {
272         m_items = Item::List() << item;
273         emitResult();
274     }
275 
276     bool m_started;
277     StorageInterface::Ptr m_storage;
278     Cache::Ptr m_cache;
279     Item m_item;
280     Collection m_collection;
281     Item::List m_items;
282 };
283 
CachingStorage(const Cache::Ptr & cache,const StorageInterface::Ptr & storage)284 CachingStorage::CachingStorage(const Cache::Ptr &cache, const StorageInterface::Ptr &storage)
285     : m_cache(cache),
286       m_storage(storage)
287 {
288 }
289 
~CachingStorage()290 CachingStorage::~CachingStorage()
291 {
292 }
293 
defaultCollection()294 Collection CachingStorage::defaultCollection()
295 {
296     return m_storage->defaultCollection();
297 }
298 
createItem(Item item,Collection collection)299 KJob *CachingStorage::createItem(Item item, Collection collection)
300 {
301     return m_storage->createItem(item, collection);
302 }
303 
updateItem(Item item,QObject * parent)304 KJob *CachingStorage::updateItem(Item item, QObject *parent)
305 {
306     return m_storage->updateItem(item, parent);
307 }
308 
removeItem(Item item,QObject * parent)309 KJob *CachingStorage::removeItem(Item item, QObject *parent)
310 {
311     return m_storage->removeItem(item, parent);
312 }
313 
removeItems(Item::List items,QObject * parent)314 KJob *CachingStorage::removeItems(Item::List items, QObject *parent)
315 {
316     return m_storage->removeItems(items, parent);
317 }
318 
moveItem(Item item,Collection collection,QObject * parent)319 KJob *CachingStorage::moveItem(Item item, Collection collection, QObject *parent)
320 {
321     return m_storage->moveItem(item, collection, parent);
322 }
323 
moveItems(Item::List items,Collection collection,QObject * parent)324 KJob *CachingStorage::moveItems(Item::List items, Collection collection, QObject *parent)
325 {
326     return m_storage->moveItems(items, collection, parent);
327 }
328 
createCollection(Collection collection,QObject * parent)329 KJob *CachingStorage::createCollection(Collection collection, QObject *parent)
330 {
331     return m_storage->createCollection(collection, parent);
332 }
333 
updateCollection(Collection collection,QObject * parent)334 KJob *CachingStorage::updateCollection(Collection collection, QObject *parent)
335 {
336     return m_storage->updateCollection(collection, parent);
337 }
338 
removeCollection(Collection collection,QObject * parent)339 KJob *CachingStorage::removeCollection(Collection collection, QObject *parent)
340 {
341     return m_storage->removeCollection(collection, parent);
342 }
343 
createTransaction(QObject * parent)344 KJob *CachingStorage::createTransaction(QObject *parent)
345 {
346     return m_storage->createTransaction(parent);
347 }
348 
fetchCollections(Collection collection,StorageInterface::FetchDepth depth,QObject * parent)349 CollectionFetchJobInterface *CachingStorage::fetchCollections(Collection collection, StorageInterface::FetchDepth depth, QObject *parent)
350 {
351     return new CachingCollectionFetchJob(m_storage, m_cache, collection, depth, parent);
352 }
353 
fetchItems(Collection collection,QObject * parent)354 ItemFetchJobInterface *CachingStorage::fetchItems(Collection collection, QObject *parent)
355 {
356     return new CachingCollectionItemsFetchJob(m_storage, m_cache, collection, parent);
357 }
358 
fetchItem(Akonadi::Item item,QObject * parent)359 ItemFetchJobInterface *CachingStorage::fetchItem(Akonadi::Item item, QObject *parent)
360 {
361     return new CachingSingleItemFetchJob(m_storage, m_cache, item, parent);
362 }
363 
364 #include "akonadicachingstorage.moc"
365