diff --git a/src/akonadi/akonaditaskqueries.h b/src/akonadi/akonaditaskqueries.h --- a/src/akonadi/akonaditaskqueries.h +++ b/src/akonadi/akonaditaskqueries.h @@ -45,8 +45,8 @@ typedef Domain::QueryResultProvider TaskProvider; typedef Domain::QueryResult TaskResult; - typedef Domain::QueryResultProvider ContextProvider; typedef Domain::QueryResult ContextResult; + typedef Domain::LiveQueryOutput ContextQueryOutput; typedef Domain::QueryResult ProjectResult; typedef Domain::LiveQueryOutput ProjectQueryOutput; @@ -77,6 +77,7 @@ private: SerializerInterface::Ptr m_serializer; + MonitorInterface::Ptr m_monitor; Cache::Ptr m_cache; LiveQueryHelpers::Ptr m_helpers; LiveQueryIntegrator::Ptr m_integrator; @@ -86,6 +87,8 @@ mutable TaskQueryOutput::Ptr m_findAll; mutable QHash m_findChildren; mutable QHash m_findProject; + mutable QHash m_findContexts; + mutable QHash m_findContextsItem; mutable QHash m_findDataSource; mutable TaskQueryOutput::Ptr m_findTopLevel; mutable TaskQueryOutput::Ptr m_findInboxTopLevel; diff --git a/src/akonadi/akonaditaskqueries.cpp b/src/akonadi/akonaditaskqueries.cpp --- a/src/akonadi/akonaditaskqueries.cpp +++ b/src/akonadi/akonaditaskqueries.cpp @@ -35,6 +35,7 @@ const MonitorInterface::Ptr &monitor, const Cache::Ptr &cache) : m_serializer(serializer), + m_monitor(monitor), m_cache(cache), m_helpers(new LiveQueryHelpers(serializer, storage)), m_integrator(new LiveQueryIntegrator(serializer, monitor)), @@ -45,6 +46,16 @@ m_integrator->addRemoveHandler([this] (const Item &item) { m_findChildren.remove(item.id()); + m_findContexts.remove(item.id()); + }); + + connect(m_monitor.data(), &MonitorInterface::itemChanged, this, [this] (const Item &item) { + const auto it = m_findContexts.find(item.id()); + if (it == m_findContexts.cend()) + return; + + m_findContextsItem[item.id()] = item; + (*it)->reset(); }); } @@ -207,9 +218,22 @@ TaskQueries::ContextResult::Ptr TaskQueries::findContexts(Domain::Task::Ptr task) const { - qFatal("Not implemented yet"); - Q_UNUSED(task); - return ContextResult::Ptr(); + Akonadi::Item taskItem = m_serializer->createItemFromTask(task); + const auto taskItemId = taskItem.id(); + m_findContextsItem[taskItemId] = taskItem; + + auto &query = m_findContexts[taskItemId]; + auto fetch = m_helpers->fetchItems(); + auto predicate = [this, taskItemId] (const Akonadi::Item &contextItem) { + auto context = m_serializer->createContextFromItem(contextItem); + if (!context) + return false; + + const auto taskItem = m_findContextsItem[taskItemId]; + return m_serializer->isContextChild(context, taskItem); + }; + m_integrator->bind("TaskQueries::findContexts", query, fetch, predicate); + return query->result(); } void TaskQueries::onWorkdayPollTimeout() diff --git a/tests/units/akonadi/akonaditaskqueriestest.cpp b/tests/units/akonadi/akonaditaskqueriestest.cpp --- a/tests/units/akonadi/akonaditaskqueriestest.cpp +++ b/tests/units/akonadi/akonaditaskqueriestest.cpp @@ -1808,6 +1808,180 @@ QCOMPARE(result->data().size(), 0); } + void findContextsShouldLookInAllCollections() + { + // GIVEN + AkonadiFakeData data; + + // One top level collection + auto collection1 = GenCollection().withId(42).withRootAsParent().withTaskContent(); + data.createCollection(collection1); + auto collection2 = GenCollection().withId(43).withRootAsParent().withTaskContent(); + data.createCollection(collection2); + + // One task in collection1, one context in collection1, one context in collection2 + data.createItem(GenTodo().withId(42).withParent(42) + .withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42")) + .withContexts({ QStringLiteral("uid-43"), QStringLiteral("uid-44") })); + data.createItem(GenTodo().withId(43).withParent(42).asContext() + .withTitle(QStringLiteral("43")).withUid(QStringLiteral("uid-43"))); + data.createItem(GenTodo().withId(44).withParent(43).asContext() + .withTitle(QStringLiteral("44")).withUid(QStringLiteral("uid-44"))); + + // WHEN + auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); + + auto cache = Akonadi::Cache::Ptr::create(serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor())); + auto storage = createCachingStorage(data, cache); + QScopedPointer queries(new Akonadi::TaskQueries(storage, + serializer, + Akonadi::MonitorInterface::Ptr(data.createMonitor()), + cache)); + auto task = serializer->createTaskFromItem(data.item(42)); + // populate cache for collection + auto *fetchJob = storage->fetchItems(collection1); + QVERIFY2(fetchJob->kjob()->exec(), qPrintable(fetchJob->kjob()->errorString())); + fetchJob = storage->fetchItems(collection2); + QVERIFY2(fetchJob->kjob()->exec(), qPrintable(fetchJob->kjob()->errorString())); + + auto result = queries->findContexts(task); + + // THEN + QVERIFY(result); + TestHelpers::waitForEmptyJobQueue(); + QCOMPARE(result->data().size(), 2); + QCOMPARE(result->data().at(0)->name(), QStringLiteral("43")); + QCOMPARE(result->data().at(1)->name(), QStringLiteral("44")); + + // Should not change anything + result = queries->findContexts(task); + + QCOMPARE(result->data().size(), 2); + QCOMPARE(result->data().at(0)->name(), QStringLiteral("43")); + QCOMPARE(result->data().at(1)->name(), QStringLiteral("44")); + } + + void findContextsShouldReactToRelationshipChange() + { + // GIVEN + AkonadiFakeData data; + + // One top level collection + auto collection1 = GenCollection().withId(42).withRootAsParent().withTaskContent(); + data.createCollection(collection1); + auto collection2 = GenCollection().withId(43).withRootAsParent().withTaskContent(); + data.createCollection(collection2); + + // One task in collection1, one context in collection1, two contexts in collection2 + data.createItem(GenTodo().withId(42).withParent(42) + .withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42")) + .withContexts({ QStringLiteral("uid-43"), QStringLiteral("uid-44") })); + data.createItem(GenTodo().withId(43).withParent(42).asContext() + .withTitle(QStringLiteral("43")).withUid(QStringLiteral("uid-43"))); + data.createItem(GenTodo().withId(44).withParent(43).asContext() + .withTitle(QStringLiteral("44")).withUid(QStringLiteral("uid-44"))); + data.createItem(GenTodo().withId(45).withParent(43).asContext() + .withTitle(QStringLiteral("45")).withUid(QStringLiteral("uid-45"))); + + auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); + + auto cache = Akonadi::Cache::Ptr::create(serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor())); + auto storage = createCachingStorage(data, cache); + QScopedPointer queries(new Akonadi::TaskQueries(storage, + serializer, + Akonadi::MonitorInterface::Ptr(data.createMonitor()), + cache)); + auto task = serializer->createTaskFromItem(data.item(42)); + // populate cache for collection + auto *fetchJob = storage->fetchItems(collection1); + QVERIFY2(fetchJob->kjob()->exec(), qPrintable(fetchJob->kjob()->errorString())); + fetchJob = storage->fetchItems(collection2); + QVERIFY2(fetchJob->kjob()->exec(), qPrintable(fetchJob->kjob()->errorString())); + + auto result = queries->findContexts(task); + + QVERIFY(result); + TestHelpers::waitForEmptyJobQueue(); + QCOMPARE(result->data().size(), 2); + QCOMPARE(result->data().at(0)->name(), QStringLiteral("43")); + QCOMPARE(result->data().at(1)->name(), QStringLiteral("44")); + + // WHEN + data.modifyItem(GenTodo(data.item(42)).withContexts({ QStringLiteral("uid-43"), QStringLiteral("uid-45") })); + + // THEN + TestHelpers::waitForEmptyJobQueue(); + QCOMPARE(result->data().size(), 2); + QCOMPARE(result->data().at(0)->name(), QStringLiteral("43")); + QCOMPARE(result->data().at(1)->name(), QStringLiteral("45")); + + // WHEN + data.modifyItem(GenTodo(data.item(42)).withContexts({ QStringLiteral("uid-45") })); + + // THEN + TestHelpers::waitForEmptyJobQueue(); + QCOMPARE(result->data().size(), 1); + QCOMPARE(result->data().at(0)->name(), QStringLiteral("45")); + + // WHEN + data.modifyItem(GenTodo(data.item(42)).withContexts({})); + + // THEN + TestHelpers::waitForEmptyJobQueue(); + QCOMPARE(result->data().size(), 0); + } + + void findContextsShouldReactToItemRemoved() + { + // GIVEN + AkonadiFakeData data; + + // One top level collection + auto collection1 = GenCollection().withId(42).withRootAsParent().withTaskContent(); + data.createCollection(collection1); + auto collection2 = GenCollection().withId(43).withRootAsParent().withTaskContent(); + data.createCollection(collection2); + + // One task in collection1, one context in collection1, one context in collection2 + data.createItem(GenTodo().withId(42).withParent(42) + .withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42")) + .withContexts({ QStringLiteral("uid-43"), QStringLiteral("uid-44") })); + data.createItem(GenTodo().withId(43).withParent(42).asContext() + .withTitle(QStringLiteral("43")).withUid(QStringLiteral("uid-43"))); + data.createItem(GenTodo().withId(44).withParent(43).asContext() + .withTitle(QStringLiteral("44")).withUid(QStringLiteral("uid-44"))); + + auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); + + auto cache = Akonadi::Cache::Ptr::create(serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor())); + auto storage = createCachingStorage(data, cache); + QScopedPointer queries(new Akonadi::TaskQueries(storage, + serializer, + Akonadi::MonitorInterface::Ptr(data.createMonitor()), + cache)); + auto task = serializer->createTaskFromItem(data.item(42)); + // populate cache for collection + auto *fetchJob = storage->fetchItems(collection1); + QVERIFY2(fetchJob->kjob()->exec(), qPrintable(fetchJob->kjob()->errorString())); + fetchJob = storage->fetchItems(collection2); + QVERIFY2(fetchJob->kjob()->exec(), qPrintable(fetchJob->kjob()->errorString())); + + auto result = queries->findContexts(task); + + QVERIFY(result); + TestHelpers::waitForEmptyJobQueue(); + QCOMPARE(result->data().size(), 2); + QCOMPARE(result->data().at(0)->name(), QStringLiteral("43")); + QCOMPARE(result->data().at(1)->name(), QStringLiteral("44")); + + // WHEN + data.removeItem(Akonadi::Item(42)); + + // THEN + TestHelpers::waitForEmptyJobQueue(); + QCOMPARE(result->data().size(), 0); + } + void shouldOnlyReturnTopLevelTasks() { // GIVEN