diff --git a/src/akonadi/akonadicachingstorage.cpp b/src/akonadi/akonadicachingstorage.cpp --- a/src/akonadi/akonadicachingstorage.cpp +++ b/src/akonadi/akonadicachingstorage.cpp @@ -27,6 +27,7 @@ #include "akonadistorage.h" #include "akonadicollectionfetchjobinterface.h" +#include "akonadiitemfetchjobinterface.h" #include @@ -155,6 +156,77 @@ Collection::List m_collections; }; +class CachingCollectionItemsFetchJob : public KCompositeJob, public ItemFetchJobInterface +{ + Q_OBJECT +public: + CachingCollectionItemsFetchJob(const StorageInterface::Ptr &storage, + const Cache::Ptr &cache, + const Collection &collection, + QObject *parent = nullptr) + : KCompositeJob(parent), + m_started(false), + m_storage(storage), + m_cache(cache), + m_collection(collection) + { + QTimer::singleShot(0, this, &CachingCollectionItemsFetchJob::start); + } + + void start() override + { + if (m_started) + return; + + if (m_cache->isCollectionPopulated(m_collection.id())) { + QTimer::singleShot(0, this, &CachingCollectionItemsFetchJob::retrieveFromCache); + } else { + auto job = m_storage->fetchItems(m_collection); + addSubjob(job->kjob()); + } + + m_started = true; + } + + + Item::List items() const override + { + return m_items; + } + + void setCollection(const Collection &collection) override + { + m_collection = collection; + } + +private: + void slotResult(KJob *kjob) override + { + if (kjob->error()) { + KCompositeJob::slotResult(kjob); + return; + } + + auto job = dynamic_cast(kjob); + Q_ASSERT(job); + m_items = job->items(); + m_cache->populateCollection(m_collection, m_items); + emitResult(); + } + + void retrieveFromCache() + { + m_items = m_cache->items(m_collection); + emitResult(); + } + + bool m_started; + StorageInterface::Ptr m_storage; + Cache::Ptr m_cache; + Collection m_collection; + Item::List m_items; +}; + CachingStorage::CachingStorage(const Cache::Ptr &cache, const StorageInterface::Ptr &storage) : m_cache(cache), @@ -248,7 +320,7 @@ ItemFetchJobInterface *CachingStorage::fetchItems(Collection collection) { - return m_storage->fetchItems(collection); + return new CachingCollectionItemsFetchJob(m_storage, m_cache, collection); } ItemFetchJobInterface *CachingStorage::fetchItem(Akonadi::Item item) diff --git a/tests/units/akonadi/akonadicachingstoragetest.cpp b/tests/units/akonadi/akonadicachingstoragetest.cpp --- a/tests/units/akonadi/akonadicachingstoragetest.cpp +++ b/tests/units/akonadi/akonadicachingstoragetest.cpp @@ -243,6 +243,74 @@ QCOMPARE(collectionCachedNames, expectedCachedNames); } } + + void shouldCacheAllItemsPerCollection() + { + // GIVEN + AkonadiFakeData data; + + data.createCollection(GenCollection().withId(42).withName(QStringLiteral("42Col")).withRootAsParent().withTaskContent()); + data.createCollection(GenCollection().withId(43).withName(QStringLiteral("43Col")).withRootAsParent().withTaskContent()); + + data.createItem(GenTodo().withId(42).withTitle(QStringLiteral("42Task")).withParent(42)); + data.createItem(GenTodo().withId(45).withTitle(QStringLiteral("45Task")).withParent(42)); + data.createItem(GenTodo().withId(52).withTitle(QStringLiteral("52Task")).withParent(42)); + + data.createItem(GenTodo().withId(44).withTitle(QStringLiteral("44Task")).withParent(43)); + data.createItem(GenTodo().withId(48).withTitle(QStringLiteral("48Task")).withParent(43)); + data.createItem(GenTodo().withId(50).withTitle(QStringLiteral("50Task")).withParent(43)); + + auto cache = Akonadi::Cache::Ptr::create(Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer), + Akonadi::MonitorInterface::Ptr(data.createMonitor())); + Akonadi::CachingStorage storage(cache, Akonadi::StorageInterface::Ptr(data.createStorage())); + + // WHEN + auto job = storage.fetchItems(Akonadi::Collection(42)); + QVERIFY2(job->kjob()->exec(), job->kjob()->errorString().toUtf8().constData()); + + // THEN + const auto toItemIds = [](const Akonadi::Item::List &items) { + auto res = QVector(); + std::transform(items.cbegin(), items.cend(), + std::back_inserter(res), + std::mem_fn(&Akonadi::Item::id)); + std::sort(res.begin(), res.end()); + return res; + }; + + auto expectedIds = QVector() << 42 << 45 << 52; + { + const auto itemFetchIds = [job, toItemIds]{ + return toItemIds(job->items()); + }(); + QCOMPARE(itemFetchIds, expectedIds); + + const auto itemCachedIds = [cache, toItemIds]{ + const auto items = cache->items(Akonadi::Collection(42)); + return toItemIds(items); + }(); + QCOMPARE(itemCachedIds, expectedIds); + } + + // WHEN (second time shouldn't hit the original storage) + data.storageBehavior().setFetchItemsBehavior(42, AkonadiFakeStorageBehavior::EmptyFetch); + data.storageBehavior().setFetchItemsErrorCode(42, 128); + job = storage.fetchItems(Akonadi::Collection(42)); + QVERIFY2(job->kjob()->exec(), job->kjob()->errorString().toUtf8().constData()); + + { + const auto itemFetchIds = [job, toItemIds]{ + return toItemIds(job->items()); + }(); + QCOMPARE(itemFetchIds, expectedIds); + + const auto itemCachedIds = [cache, toItemIds]{ + const auto items = cache->items(Akonadi::Collection(42)); + return toItemIds(items); + }(); + QCOMPARE(itemCachedIds, expectedIds); + } + } }; ZANSHIN_TEST_MAIN(AkonadiCachingStorageTest)