diff --git a/src/akonadi/akonadicachingstorage.cpp b/src/akonadi/akonadicachingstorage.cpp --- a/src/akonadi/akonadicachingstorage.cpp +++ b/src/akonadi/akonadicachingstorage.cpp @@ -303,6 +303,79 @@ Item::List m_items; }; +class CachingTagItemsFetchJob : public KCompositeJob, public ItemFetchJobInterface +{ + Q_OBJECT +public: + CachingTagItemsFetchJob(const StorageInterface::Ptr &storage, + const Cache::Ptr &cache, + const Tag &tag, + QObject *parent = nullptr) + : KCompositeJob(parent), + m_started(false), + m_storage(storage), + m_cache(cache), + m_tag(tag) + { + QTimer::singleShot(0, this, &CachingTagItemsFetchJob::start); + } + + void start() override + { + if (m_started) + return; + + if (m_cache->isTagPopulated(m_tag.id())) { + QTimer::singleShot(0, this, &CachingTagItemsFetchJob::retrieveFromCache); + } else { + auto job = m_storage->fetchTagItems(m_tag); + job->setCollection(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->populateTag(m_tag, m_items); + emitResult(); + } + + void retrieveFromCache() + { + m_items = m_cache->items(m_tag); + emitResult(); + } + + bool m_started; + StorageInterface::Ptr m_storage; + Cache::Ptr m_cache; + Tag m_tag; + Collection m_collection; + Item::List m_items; +}; + class CachingTagFetchJob : public KCompositeJob, public TagFetchJobInterface { Q_OBJECT @@ -467,7 +540,7 @@ ItemFetchJobInterface *CachingStorage::fetchTagItems(Tag tag) { - return m_storage->fetchTagItems(tag); + return new CachingTagItemsFetchJob(m_storage, m_cache, tag); } TagFetchJobInterface *CachingStorage::fetchTags() 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 @@ -373,6 +373,77 @@ } } + void shouldCacheAllItemsPerTag() + { + // GIVEN + AkonadiFakeData data; + + data.createCollection(GenCollection().withId(42).withName(QStringLiteral("42Col")).withRootAsParent().withTaskContent()); + data.createCollection(GenCollection().withId(43).withName(QStringLiteral("43Col")).withRootAsParent().withTaskContent()); + + data.createTag(GenTag().withId(42).withName(QStringLiteral("42Plain")).asPlain()); + data.createTag(GenTag().withId(43).withName(QStringLiteral("43Context")).asContext()); + + data.createItem(GenTodo().withId(42).withTitle(QStringLiteral("42Task")).withParent(42).withTags({42})); + data.createItem(GenTodo().withId(45).withTitle(QStringLiteral("45Task")).withParent(42).withTags({42, 43})); + data.createItem(GenTodo().withId(52).withTitle(QStringLiteral("52Task")).withParent(42).withTags({43})); + + data.createItem(GenTodo().withId(44).withTitle(QStringLiteral("44Task")).withParent(43).withTags({42})); + data.createItem(GenTodo().withId(48).withTitle(QStringLiteral("48Task")).withParent(43).withTags({42, 43})); + data.createItem(GenTodo().withId(50).withTitle(QStringLiteral("50Task")).withParent(43).withTags({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.fetchTagItems(Akonadi::Tag(43)); + 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() << 45 << 48 << 50 << 52; + { + const auto itemFetchIds = [job, toItemIds]{ + return toItemIds(job->items()); + }(); + QCOMPARE(itemFetchIds, expectedIds); + + const auto itemCachedIds = [cache, toItemIds]{ + const auto items = cache->items(Akonadi::Tag(43)); + return toItemIds(items); + }(); + QCOMPARE(itemCachedIds, expectedIds); + } + + // WHEN (second time shouldn't hit the original storage) + data.storageBehavior().setFetchTagItemsBehavior(43, AkonadiFakeStorageBehavior::EmptyFetch); + data.storageBehavior().setFetchTagItemsErrorCode(43, 128); + job = storage.fetchTagItems(Akonadi::Tag(43)); + 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::Tag(43)); + return toItemIds(items); + }(); + QCOMPARE(itemCachedIds, expectedIds); + } + } + void shouldCacheTags() { // GIVEN