diff --git a/src/akonadi/akonaditaskqueries.cpp b/src/akonadi/akonaditaskqueries.cpp index 27884af7..28d65bf7 100644 --- a/src/akonadi/akonaditaskqueries.cpp +++ b/src/akonadi/akonaditaskqueries.cpp @@ -1,458 +1,476 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "akonaditaskqueries.h" #include "akonadicollectionfetchjobinterface.h" #include "akonadiitemfetchjobinterface.h" #include "akonadimonitorimpl.h" #include "akonadiserializer.h" #include "akonadistorage.h" #include #include #include "utils/jobhandler.h" using namespace Akonadi; +static QString getParent(const Item &item) +{ + QString parent; + try { + auto todo = item.payload(); + parent = todo->relatedTo(); + } catch (...) { + + } + return parent; +} + +static QString getUid(const Item &item) +{ + QString uid; + try { + auto todo = item.payload(); + uid = todo->uid(); + } catch (...) { + + } + return uid; +} + + AkonadiItemSource::AkonadiItemSource(MonitorInterface *monitor) : QObject(), m_monitor(monitor), m_populated(false), m_populationInProgress(false) { isToplevel = [](const Akonadi::Collection &col) { if (col == Akonadi::Collection::root()) { return true; } // if (col.name() == "Other Users") { // return true; // } return false; }; } Akonadi::Collection::Id AkonadiItemSource::id(const Akonadi::Collection &col) const { if (isToplevel(col)) { return 0; } return col.id(); } void AkonadiItemSource::findChildren(const Item &item) { - QString parent; - try { - auto todo = item.payload(); - parent = todo->uid(); - } catch (...) { - - } + auto parent = getUid(item); populate([this, parent] { for (auto item : m_items[parent]) { emit added(item, parent); } }); } void AkonadiItemSource::setFilter(const std::function &filter) { isWantedItem = filter; } void AkonadiItemSource::setItemFetcher(const std::function &)> &fetcher) { fetchItems = fetcher; } void AkonadiItemSource::populate(const std::function &callback) { if (m_populated) { callback(); } else if (m_populationInProgress) { m_pendingCallbacks << callback; } else { m_populationInProgress = true; m_pendingCallbacks << callback; QPointer handle(this); fetchItems([this, handle](bool error, const Akonadi::Item::List &items){ //Since the this pointer might be invalid meanwhile, we have to double check if (!handle) { return; } if (error) { m_pendingCallbacks.clear(); return; } for (auto item : items) { try { auto todo = item.payload(); - m_items[todo->relatedTo()].append(item); + auto parent = todo->relatedTo(); + m_items[parent].append(item); + m_parents.insert(item.id(), parent); } catch (...) { } } m_populated = true; if (m_monitor) { connect(m_monitor, SIGNAL(itemAdded(Akonadi::Item)), this, SLOT(onAdded(Akonadi::Item))); connect(m_monitor, SIGNAL(itemRemoved(Akonadi::Item)), this, SLOT(onRemoved(Akonadi::Item))); connect(m_monitor, SIGNAL(itemChanged(Akonadi::Item)), this, SLOT(onChanged(Akonadi::Item))); connect(m_monitor, SIGNAL(itemMoved(Akonadi::Item)), this, SLOT(onChanged(Akonadi::Item))); } for (auto callback : m_pendingCallbacks) { callback(); } m_pendingCallbacks.clear(); }); } } void AkonadiItemSource::onAdded(const Item &item) { if (!isWantedItem(item)) { return; } - QString parent; - try { - auto todo = item.payload(); - parent = todo->relatedTo(); - } catch (...) { - - } + auto parent = getParent(item); + m_items[parent].append(item); + m_parents.insert(item.id(), parent); emit added(item, parent); } void AkonadiItemSource::onRemoved(const Item &item) { - QString parent; + // auto parent = getParent(item); + auto parent = m_parents.value(item.id()); + m_items[parent].removeOne(item); emit removed(item, parent); } void AkonadiItemSource::onChanged(const Item &item) { - QString parent; - try { - auto todo = item.payload(); - parent = todo->relatedTo(); - } catch (...) { - - } - emit changed(item, parent); + auto newParent = getParent(item); + auto oldParent = m_parents.value(item.id()); + m_items[oldParent].removeOne(item); + m_parents.remove(item.id()); + m_items[newParent].append(item); + m_parents.insert(item.id(), newParent); + emit changed(item, newParent); } TaskTreeQuery::TaskTreeQuery(SerializerInterface *serializer, const QSharedPointer &source) : QObject(), m_serializer(serializer), m_source(source) { connect(m_source.data(), SIGNAL(added(Akonadi::Item, QString)), this, SLOT(onAdded(Akonadi::Item, QString))); connect(m_source.data(), SIGNAL(removed(Akonadi::Item, QString)), this, SLOT(onRemoved(Akonadi::Item, QString))); connect(m_source.data(), SIGNAL(changed(Akonadi::Item, QString)), this, SLOT(onChanged(Akonadi::Item, QString))); } void TaskTreeQuery::findChildren(const Akonadi::Item &item) { if (m_source) { m_source->findChildren(item); } } void TaskTreeQuery::reset(const QSharedPointer &source) { disconnect(m_source.data(), SIGNAL(added(Akonadi::Item, QString)), this, SLOT(onAdded(Akonadi::Item, QString))); disconnect(m_source.data(), SIGNAL(removed(Akonadi::Item, QString)), this, SLOT(onRemoved(Akonadi::Item, QString))); disconnect(m_source.data(), SIGNAL(changed(Akonadi::Item, QString)), this, SLOT(onChanged(Akonadi::Item, QString))); m_source = source; foreach(auto query, m_findChildren.values()) { query->reset(); } connect(m_source.data(), SIGNAL(added(Akonadi::Item, QString)), this, SLOT(onAdded(Akonadi::Item, QString))); connect(m_source.data(), SIGNAL(removed(Akonadi::Item, QString)), this, SLOT(onRemoved(Akonadi::Item, QString))); connect(m_source.data(), SIGNAL(changed(Akonadi::Item, QString)), this, SLOT(onChanged(Akonadi::Item, QString))); } TaskTreeQuery::Result::Ptr TaskTreeQuery::findChildren(Domain::Task::Ptr parent, const std::function &setupFunction) { const auto parentUid = parent->property("todoUid").toString(); const auto item = m_serializer->createItemFromTask(parent); if (!m_findChildren.contains(parentUid)) { auto query = Query::Ptr::create(); m_findChildren.insert(parentUid, query); setupFunction(query, item.parentCollection()); } return m_findChildren.value(parentUid)->result(); } void TaskTreeQuery::onAdded(const Akonadi::Item &item, const QString &parent) { auto query = m_findChildren.find(parent); if (query != m_findChildren.end()) { (*query)->onAdded(item); } } void TaskTreeQuery::onRemoved(const Akonadi::Item &item, const QString &parent) { for (auto query : m_findChildren.values()) { query->onRemoved(item); } } void TaskTreeQuery::onChanged(const Akonadi::Item &item, const QString &parent) { for (auto query : m_findChildren.values()) { query->onChanged(item); } } TaskQueries::TaskQueries(QObject *parent) : QObject(parent), m_storage(new Storage), m_serializer(new Serializer), m_monitor(new MonitorImpl), m_ownInterfaces(true) { connect(m_monitor, SIGNAL(itemAdded(Akonadi::Item)), this, SLOT(onItemAdded(Akonadi::Item))); connect(m_monitor, SIGNAL(itemRemoved(Akonadi::Item)), this, SLOT(onItemRemoved(Akonadi::Item))); connect(m_monitor, SIGNAL(itemChanged(Akonadi::Item)), this, SLOT(onItemChanged(Akonadi::Item))); } TaskQueries::TaskQueries(StorageInterface *storage, SerializerInterface *serializer, MonitorInterface *monitor) : m_storage(storage), m_serializer(serializer), m_monitor(monitor), m_ownInterfaces(false) { connect(monitor, SIGNAL(itemAdded(Akonadi::Item)), this, SLOT(onItemAdded(Akonadi::Item))); connect(monitor, SIGNAL(itemRemoved(Akonadi::Item)), this, SLOT(onItemRemoved(Akonadi::Item))); connect(monitor, SIGNAL(itemChanged(Akonadi::Item)), this, SLOT(onItemChanged(Akonadi::Item))); } TaskQueries::~TaskQueries() { if (m_ownInterfaces) { delete m_storage; delete m_serializer; delete m_monitor; } } QSharedPointer TaskQueries::getTaskTree(const Akonadi::Collection &parent) const { if (!m_treeQueries.contains(parent.id())) { TaskQueries *self = const_cast(this); self->m_treeQueries.insert(parent.id(), QSharedPointer(new TaskTreeQuery(m_serializer, findTaskTree(parent)))); } return m_treeQueries.value(parent.id()); } QSharedPointer TaskQueries::findTaskTree(const Akonadi::Collection &parentCollection) const { auto source = QSharedPointer(new AkonadiItemSource(m_monitor)); source->setFilter([this, parentCollection](const Item &item) { return (parentCollection.id() == item.parentCollection().id()); }); source->setItemFetcher([this, parentCollection](const std::function &resultHandler) { auto job = m_storage->fetchItems(parentCollection); Utils::JobHandler::install(job->kjob(), [job, resultHandler] { if (job->kjob()->error()) { kWarning() << "Failed to fetch collections " << job->kjob()->errorString(); resultHandler(true, Akonadi::Item::List()); return; } resultHandler(false, job->items()); }); }); return source; } TaskQueries::TaskResult::Ptr TaskQueries::findAll() const { if (!m_findAll) { { TaskQueries *self = const_cast(this); self->m_findAll = self->createTaskQuery(); } m_findAll->setFetchFunction([this] (const TaskQuery::AddFunction &add) { CollectionFetchJobInterface *job = m_storage->fetchCollections(Akonadi::Collection::root(), StorageInterface::Recursive, StorageInterface::Tasks); Utils::JobHandler::install(job->kjob(), [this, job, add] { if (job->kjob()->error() != KJob::NoError) return; for (auto collection : job->collections()) { if (!m_serializer->isSelectedCollection(collection)) continue; ItemFetchJobInterface *job = m_storage->fetchItems(collection); Utils::JobHandler::install(job->kjob(), [this, job, add] { if (job->kjob()->error() != KJob::NoError) return; for (auto item : job->items()) { add(item); } }); } }); }); m_findAll->setConvertFunction([this] (const Akonadi::Item &item) { return m_serializer->createTaskFromItem(item); }); m_findAll->setUpdateFunction([this] (const Akonadi::Item &item, Domain::Task::Ptr &task) { m_serializer->updateTaskFromItem(task, item); }); m_findAll->setPredicateFunction([this] (const Akonadi::Item &item) { return m_serializer->isTaskItem(item); }); m_findAll->setRepresentsFunction([this] (const Akonadi::Item &item, const Domain::Task::Ptr &task) { return m_serializer->representsItem(task, item); }); } return m_findAll->result(); } TaskQueries::TaskResult::Ptr TaskQueries::findChildren(Domain::Task::Ptr task) const { auto item = m_serializer->createItemFromTask(task); Q_ASSERT(item.parentCollection().isValid()); auto treeQuery = getTaskTree(item.parentCollection()); return treeQuery->findChildren(task, [this, item, task, treeQuery](TaskQuery::Ptr query, const Akonadi::Collection &root) { query->setFetchFunction([this, item, treeQuery] (const TaskQuery::AddFunction &add) { //We don't need the add function because we use the usual delivery method via the onAdded slot used for updates Q_UNUSED(add); treeQuery->findChildren(item); }); query->setConvertFunction([this] (const Akonadi::Item &item) { return m_serializer->createTaskFromItem(item); }); query->setUpdateFunction([this] (const Akonadi::Item &item, Domain::Task::Ptr &task) { m_serializer->updateTaskFromItem(task, item); }); query->setPredicateFunction([this, task] (const Akonadi::Item &item) { return m_serializer->isTaskChild(task, item); }); query->setRepresentsFunction([this] (const Akonadi::Item &item, const Domain::Task::Ptr &task) { return m_serializer->representsItem(task, item); }); }); } TaskQueries::TaskResult::Ptr TaskQueries::findTopLevel() const { if (!m_findTopLevel) { { TaskQueries *self = const_cast(this); self->m_findTopLevel = self->createTaskQuery(); } m_findTopLevel->setFetchFunction([this] (const TaskQuery::AddFunction &add) { CollectionFetchJobInterface *job = m_storage->fetchCollections(Akonadi::Collection::root(), StorageInterface::Recursive, StorageInterface::Tasks); Utils::JobHandler::install(job->kjob(), [this, job, add] { if (job->kjob()->error() != KJob::NoError) return; for (auto collection : job->collections()) { if (!m_serializer->isSelectedCollection(collection)) continue; ItemFetchJobInterface *job = m_storage->fetchItems(collection); Utils::JobHandler::install(job->kjob(), [this, job, add] { if (job->kjob()->error() != KJob::NoError) return; for (auto item : job->items()) { add(item); } }); } }); }); m_findTopLevel->setConvertFunction([this] (const Akonadi::Item &item) { return m_serializer->createTaskFromItem(item); }); m_findTopLevel->setUpdateFunction([this] (const Akonadi::Item &item, Domain::Task::Ptr &task) { m_serializer->updateTaskFromItem(task, item); }); m_findTopLevel->setPredicateFunction([this] (const Akonadi::Item &item) { return m_serializer->relatedUidFromItem(item).isEmpty(); }); m_findTopLevel->setRepresentsFunction([this] (const Akonadi::Item &item, const Domain::Task::Ptr &task) { return m_serializer->representsItem(task, item); }); } return m_findTopLevel->result(); } TaskQueries::ContextResult::Ptr TaskQueries::findContexts(Domain::Task::Ptr task) const { qFatal("Not implemented yet"); Q_UNUSED(task); return ContextResult::Ptr(); } void TaskQueries::onItemAdded(const Item &item) { foreach (const TaskQuery::Ptr &query, m_taskQueries) query->onAdded(item); } void TaskQueries::onItemRemoved(const Item &item) { foreach (const TaskQuery::Ptr &query, m_taskQueries) query->onRemoved(item); if (m_findChildren.contains(item.id())) { auto query = m_findChildren.take(item.id()); m_taskQueries.removeAll(query); } } void TaskQueries::onItemChanged(const Item &item) { foreach (const TaskQuery::Ptr &query, m_taskQueries) query->onChanged(item); } TaskQueries::TaskQuery::Ptr TaskQueries::createTaskQuery() { auto query = TaskQueries::TaskQuery::Ptr::create(); m_taskQueries << query; return query; } diff --git a/src/akonadi/akonaditaskqueries.h b/src/akonadi/akonaditaskqueries.h index 60ba3a7b..57dc8a26 100644 --- a/src/akonadi/akonaditaskqueries.h +++ b/src/akonadi/akonaditaskqueries.h @@ -1,160 +1,161 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef AKONADI_TASKQUERIES_H #define AKONADI_TASKQUERIES_H #include #include #include #include "domain/livequery.h" #include "domain/taskqueries.h" class KJob; namespace Akonadi { class Item; class MonitorInterface; class SerializerInterface; class StorageInterface; class TaskTreeQuery; class AkonadiItemSource; class TaskQueries : public QObject, public Domain::TaskQueries { Q_OBJECT public: typedef Domain::LiveQuery TaskQuery; typedef Domain::QueryResultProvider TaskProvider; typedef Domain::QueryResult TaskResult; typedef Domain::QueryResultProvider ContextProvider; typedef Domain::QueryResult ContextResult; explicit TaskQueries(QObject *parent = 0); TaskQueries(StorageInterface *storage, SerializerInterface *serializer, MonitorInterface *monitor); virtual ~TaskQueries(); TaskResult::Ptr findAll() const; TaskResult::Ptr findChildren(Domain::Task::Ptr task) const; TaskResult::Ptr findTopLevel() const; ContextResult::Ptr findContexts(Domain::Task::Ptr task) const; private slots: void onItemAdded(const Akonadi::Item &item); void onItemRemoved(const Akonadi::Item &item); void onItemChanged(const Akonadi::Item &item); private: TaskQuery::Ptr createTaskQuery(); QSharedPointer getTaskTree(const Akonadi::Collection &) const; QSharedPointer findTaskTree(const Akonadi::Collection &) const; StorageInterface *m_storage; SerializerInterface *m_serializer; MonitorInterface *m_monitor; bool m_ownInterfaces; TaskQuery::Ptr m_findAll; QHash m_findChildren; TaskQuery::Ptr m_findTopLevel; TaskQuery::List m_taskQueries; QHash > m_treeQueries; }; // This represents a reactive result set for an akonadi query, which supports being recursively queried. // (and currently omits any other way of querying) // The result set will automatically update itself on changes in the store (therefore the monitor) // This is essentilally what akonadi should provide with a proper query interface. class AkonadiItemSource : public QObject { Q_OBJECT public: AkonadiItemSource(MonitorInterface *monitor); void findChildren(const Akonadi::Item &parent); //Defines what parts match the tree. Implement to filter monitor notifications outside of the tree. //The filter is recursive, meaning that if a parent is filtered, children will automatically match the filter as well. void setFilter(const std::function &); //Defines what is initially fetched to populate the tree. void setItemFetcher(const std::function &)> &fetcher); signals: void added(Akonadi::Item, QString); void removed(Akonadi::Item, QString); void changed(Akonadi::Item, QString); private slots: void onAdded(const Akonadi::Item &); void onRemoved(const Akonadi::Item &); void onChanged(const Akonadi::Item &); private: Akonadi::Collection::Id id(const Akonadi::Collection &col) const; //Internally trigger the fetchFunction and then call the appropriate signals/callbacks void populate(const std::function &callback); QHash m_items; + QHash m_parents; MonitorInterface *m_monitor; bool m_populated; bool m_populationInProgress; QList > m_pendingCallbacks; std::function isWantedItem; std::function isToplevel; std::function &)> fetchItems; }; //Maps the signals to the appropriate query class TaskTreeQuery : public QObject { Q_OBJECT public: typedef Domain::LiveQuery Query; typedef Domain::QueryResultProvider Provider; typedef Domain::QueryResult Result; TaskTreeQuery(SerializerInterface *, const QSharedPointer &source); void reset(const QSharedPointer &source); Result::Ptr findChildren(Domain::Task::Ptr source, const std::function &setupFunction); void findChildren(const Item &parent); private slots: void onAdded(const Akonadi::Item &, const QString &parent); void onRemoved(const Akonadi::Item &, const QString &parent); void onChanged(const Akonadi::Item &, const QString &parent); private: QHash m_findChildren; SerializerInterface *m_serializer; QSharedPointer m_source; }; } #endif // AKONADI_TASKQUERIES_H