diff --git a/src/akonadi/CMakeLists.txt b/src/akonadi/CMakeLists.txt --- a/src/akonadi/CMakeLists.txt +++ b/src/akonadi/CMakeLists.txt @@ -2,6 +2,7 @@ akonadiapplicationselectedattribute.cpp akonadiartifactqueries.cpp akonadicache.cpp + akonadicachejob.cpp akonadicachingstorage.cpp akonadicollectionfetchjobinterface.cpp akonadicollectionsearchjobinterface.cpp diff --git a/src/akonadi/akonadicachejob.h b/src/akonadi/akonadicachejob.h new file mode 100644 --- /dev/null +++ b/src/akonadi/akonadicachejob.h @@ -0,0 +1,109 @@ +/* This file is part of Zanshin + + Copyright 2015 Mario Bensi + + 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_CACHEJOB_H +#define AKONADI_CACHEJOB_H + +#include +#include "akonadicollectionfetchjobinterface.h" +#include "akonadicollectionsearchjobinterface.h" +#include "akonadiitemfetchjobinterface.h" +#include "akonaditagfetchjobinterface.h" + +namespace Akonadi { + +class CacheJob : public KJob +{ + Q_OBJECT +public: + explicit CacheJob(QObject *parent = Q_NULLPTR); + void start(); +}; + +class AkonadiCacheCollectionFetchJob : public CacheJob, public Akonadi::CollectionFetchJobInterface +{ + Q_OBJECT +public: + using CacheJob::CacheJob; + + void setCollections(const Akonadi::Collection::List &collections); + Akonadi::Collection::List collections() const; + + QString resource() const; + void setResource(const QString &resource) Q_DECL_OVERRIDE; + + bool filtered() const; + void setFiltered(bool filter); + +private: + Akonadi::Collection::List m_collections; + QString m_resource; + bool m_filter; +}; + +class AkonadiCacheCollectionSearchJob : public CacheJob, public Akonadi::CollectionSearchJobInterface +{ + Q_OBJECT +public: + using CacheJob::CacheJob; + + void setCollections(const Akonadi::Collection::List &collections); + Akonadi::Collection::List collections() const; + +private: + Akonadi::Collection::List m_collections; +}; + +class AkonadiCacheItemFetchJob : public CacheJob, public Akonadi::ItemFetchJobInterface +{ + Q_OBJECT +public: + using CacheJob::CacheJob; + + void setItems(const Akonadi::Item::List &items); + Akonadi::Item::List items() const; + + Akonadi::Collection collection() const; + void setCollection(const Akonadi::Collection &collection) Q_DECL_OVERRIDE; + +private: + Akonadi::Item::List m_items; + Akonadi::Collection m_collection; +}; + +class AkonadiCacheTagFetchJob : public CacheJob, public Akonadi::TagFetchJobInterface +{ + Q_OBJECT +public: + using CacheJob::CacheJob; + + void setTags(const Akonadi::Tag::List &tags); + Akonadi::Tag::List tags() const; + +private: + Akonadi::Tag::List m_tags; +}; + +} + +#endif // AKONADI_CACHESTORE diff --git a/src/akonadi/akonadicachejob.cpp b/src/akonadi/akonadicachejob.cpp new file mode 100644 --- /dev/null +++ b/src/akonadi/akonadicachejob.cpp @@ -0,0 +1,108 @@ +/* This file is part of Zanshin + + Copyright 2015 Mario Bensi + + 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 "akonadicachejob.h" +#include + +using namespace Akonadi; + +CacheJob::CacheJob(QObject *parent) + : KJob(parent) +{ +} + +void CacheJob::start() +{ + setError(KJob::NoError); + emitResult(); +} + +void AkonadiCacheCollectionFetchJob::setCollections(const Akonadi::Collection::List &collections) +{ + m_collections = collections; +} + +Akonadi::Collection::List AkonadiCacheCollectionFetchJob::collections() const +{ + return m_collections; +} + +QString AkonadiCacheCollectionFetchJob::resource() const +{ + return m_resource; +} + +void AkonadiCacheCollectionFetchJob::setResource(const QString &resource) +{ + m_resource = resource; +} + +bool AkonadiCacheCollectionFetchJob::filtered() const +{ + return m_filter; +} + +void AkonadiCacheCollectionFetchJob::setFiltered(bool filter) +{ + m_filter = filter; +} + +void AkonadiCacheCollectionSearchJob::setCollections(const Akonadi::Collection::List &collections) +{ + m_collections = collections; +} + +Akonadi::Collection::List AkonadiCacheCollectionSearchJob::collections() const +{ + return m_collections; +} + +void AkonadiCacheItemFetchJob::setItems(const Akonadi::Item::List &items) +{ + m_items = items; +} + +Akonadi::Item::List AkonadiCacheItemFetchJob::items() const +{ + return m_items; +} + +Akonadi::Collection AkonadiCacheItemFetchJob::collection() const +{ + return m_collection; +} + +void AkonadiCacheItemFetchJob::setCollection(const Akonadi::Collection &collection) +{ + m_collection = collection; +} + +void AkonadiCacheTagFetchJob::setTags(const Akonadi::Tag::List &tags) +{ + m_tags = tags; +} + +Akonadi::Tag::List AkonadiCacheTagFetchJob::tags() const +{ + return m_tags; +} diff --git a/src/akonadi/akonadicachingstorage.h b/src/akonadi/akonadicachingstorage.h --- a/src/akonadi/akonadicachingstorage.h +++ b/src/akonadi/akonadicachingstorage.h @@ -25,6 +25,7 @@ #define AKONADI_CACHING_STORAGE_H #include "akonadistorageinterface.h" +#include "akonadimemorystore.h" #include @@ -65,7 +66,13 @@ TagFetchJobInterface *fetchTags() Q_DECL_OVERRIDE; private: + Akonadi::Tag::Id findId(const Akonadi::Tag &tag); + Akonadi::Collection::Id findId(const Akonadi::Collection &collection); + Akonadi::Item::Id findId(const Akonadi::Item &item); + Akonadi::Collection::List collectChildren(const Akonadi::Collection &root); + StorageInterface::Ptr m_storage; + MemoryStore::Ptr m_cache; }; } diff --git a/src/akonadi/akonadicachingstorage.cpp b/src/akonadi/akonadicachingstorage.cpp --- a/src/akonadi/akonadicachingstorage.cpp +++ b/src/akonadi/akonadicachingstorage.cpp @@ -23,12 +23,19 @@ #include "akonadicachingstorage.h" +#include "akonadicache.h" +#include "akonadicachejob.h" +#include "akonadicollectionfetchjobinterface.h" #include "akonadistorage.h" +#include +#include + using namespace Akonadi; CachingStorage::CachingStorage(const StorageInterface::Ptr &storage) - : m_storage(storage) + : m_storage(storage), + m_cache(new Cache) { } @@ -113,32 +120,206 @@ CollectionFetchJobInterface *CachingStorage::fetchCollections(Collection collection, StorageInterface::FetchDepth depth, FetchContentTypes types) { - return m_storage->fetchCollections(collection, depth, types); + auto col = m_cache->collection(collection.id()); + if (!col.isValid()) + return m_storage->fetchCollections(collection, depth, types); + + auto children = Akonadi::Collection::List(); + + switch (depth) { + case Base: + children << m_cache->collection(findId(collection)); + break; + case FirstLevel: + children << m_cache->childCollections(findId(collection)); + break; + case Recursive: + children = collectChildren(collection); + break; + } + + auto job = new AkonadiCacheCollectionFetchJob; + auto collections = Akonadi::Collection::List(); + + if (types == Akonadi::StorageInterface::AllContent) { + collections = children; + } else { + std::copy_if(children.constBegin(), children.constEnd(), + std::back_inserter(collections), + [types] (const Akonadi::Collection &col) { + const auto mime = col.contentMimeTypes(); + return ((types & Akonadi::StorageInterface::Tasks) && mime.contains(KCalCore::Todo::todoMimeType())) + || ((types & Akonadi::StorageInterface::Notes) && mime.contains(Akonadi::NoteUtils::noteMimeType())); + }); + } + + if (depth != Base) { + // Replace the dummy parents in the ancestor chain with proper ones + // full of juicy data + using namespace std::placeholders; + auto completeCollection = std::bind(&Cache::reconstructAncestors, + m_cache, _1, collection); + std::transform(collections.begin(), collections.end(), + collections.begin(), completeCollection); + } + + job->setCollections(collections); + return job; } CollectionSearchJobInterface *CachingStorage::searchCollections(QString collectionName) { - return m_storage->searchCollections(collectionName); + const auto allCollections = m_cache->collections(); + auto foundCollections = Akonadi::Collection::List(); + + std::copy_if(allCollections.constBegin(), allCollections.constEnd(), + std::back_inserter(foundCollections), + [collectionName] (const Akonadi::Collection &col) { + const auto mime = col.contentMimeTypes(); + const bool supportedType = mime.contains(KCalCore::Todo::todoMimeType()) + || mime.contains(Akonadi::NoteUtils::noteMimeType()); + return supportedType && col.displayName().contains(collectionName, Qt::CaseInsensitive); + }); + + // Replace the dummy parents in the ancestor chain with proper ones + // full of juicy data + using namespace std::placeholders; + auto reconstructCollection = std::bind(&Cache::reconstructAncestors, + m_cache, _1, Akonadi::Collection::root()); + std::transform(foundCollections.begin(), foundCollections.end(), + foundCollections.begin(), reconstructCollection); + + if (foundCollections.isEmpty()) + return m_storage->searchCollections(collectionName); + + auto job = new AkonadiCacheCollectionSearchJob; + job->setCollections(foundCollections); + return job; } ItemFetchJobInterface *CachingStorage::fetchItems(Collection collection) { - return m_storage->fetchItems(collection); + auto items = m_cache->childItems(findId(collection)); + std::transform(items.begin(), items.end(), + items.begin(), + [this] (const Akonadi::Item &item) { + auto result = m_cache->reconstructItemDependencies(item); + // Force payload detach + result.setPayloadFromData(result.payloadData()); + return result; + }); + + if (items.isEmpty()) + return m_storage->fetchItems(collection); + + auto job = new AkonadiCacheItemFetchJob; + job->setItems(items); + return job; } ItemFetchJobInterface *CachingStorage::fetchItem(Akonadi::Item item) { - return m_storage->fetchItem(item); + auto fullItem = m_cache->item(findId(item)); + fullItem = m_cache->reconstructItemDependencies(fullItem); + // Force payload detach + fullItem.setPayloadFromData(fullItem.payloadData()); + + if (!fullItem.isValid()) + return m_storage->fetchItem(item); + + auto job = new AkonadiCacheItemFetchJob; + job->setItems(Akonadi::Item::List() << fullItem); + return job; } ItemFetchJobInterface *CachingStorage::fetchTagItems(Tag tag) { - return m_storage->fetchTagItems(tag); + auto items = m_cache->tagItems(findId(tag)); + std::transform(items.begin(), items.end(), + items.begin(), + [this] (const Akonadi::Item &item) { + auto collection = m_cache->reconstructAncestors(item.parentCollection()); + auto result = item; + result.setParentCollection(collection); + // Force payload detach + result.setPayloadFromData(result.payloadData()); + return result; + }); + + if (items.isEmpty()) + return m_storage->fetchTagItems(tag); + + auto job = new AkonadiCacheItemFetchJob; + job->setItems(items); + return job; } TagFetchJobInterface *CachingStorage::fetchTags() { - return m_storage->fetchTags(); + auto tags = m_cache->tags(); + if (tags.isEmpty()) + return m_storage->fetchTags(); + + auto job = new AkonadiCacheTagFetchJob; + job->setTags(tags); + return job; +} + +Akonadi::Tag::Id CachingStorage::findId(const Akonadi::Tag &tag) +{ + if (tag.isValid() || tag.gid().isEmpty()) + return tag.id(); + + const auto gid = tag.gid(); + auto tags = m_cache->tags(); + auto result = std::find_if(tags.constBegin(), tags.constEnd(), + [gid] (const Akonadi::Tag &tag) { + return tag.gid() == gid; + }); + return (result != tags.constEnd()) ? result->id() : tag.id(); +} + +Akonadi::Entity::Id CachingStorage::findId(const Akonadi::Collection &collection) +{ + if (collection.isValid() || collection.remoteId().isEmpty()) + return collection.id(); + + const auto remoteId = collection.remoteId(); + auto collections = m_cache->collections(); + auto result = std::find_if(collections.constBegin(), collections.constEnd(), + [remoteId] (const Akonadi::Collection &collection) { + return collection.remoteId() == remoteId; + }); + return (result != collections.constEnd()) ? result->id() : collection.id(); } +Akonadi::Entity::Id CachingStorage::findId(const Akonadi::Item &item) +{ + if (item.isValid() || item.remoteId().isEmpty()) + return item.id(); + + const auto remoteId = item.remoteId(); + auto items = m_cache->items(); + auto result = std::find_if(items.constBegin(), items.constEnd(), + [remoteId] (const Akonadi::Item &item) { + return item.remoteId() == remoteId; + }); + return (result != items.constEnd()) ? result->id() : item.id(); + +} + +Akonadi::Collection::List CachingStorage::collectChildren(const Akonadi::Collection &root) +{ + auto collections = Akonadi::Collection::List(); + + foreach (const auto &child, m_cache->childCollections(findId(root))) { + if (!child.enabled() && !child.referenced()) + continue; + + collections << m_cache->collection(findId(child)); + collections += collectChildren(child); + } + + return collections; +} diff --git a/src/akonadi/akonadilivequeryhelpers.h b/src/akonadi/akonadilivequeryhelpers.h --- a/src/akonadi/akonadilivequeryhelpers.h +++ b/src/akonadi/akonadilivequeryhelpers.h @@ -31,6 +31,8 @@ namespace Akonadi { +class CachingStorage; + class LiveQueryHelpers { public: @@ -42,6 +44,7 @@ LiveQueryHelpers(const SerializerInterface::Ptr &serializer, const StorageInterface::Ptr &storage); + ~LiveQueryHelpers(); CollectionFetchFunction fetchAllCollections(StorageInterface::FetchContentTypes contentTypes) const; CollectionFetchFunction fetchCollections(const Collection &root, StorageInterface::FetchContentTypes contentTypes) const; @@ -57,6 +60,7 @@ private: SerializerInterface::Ptr m_serializer; StorageInterface::Ptr m_storage; + CachingStorage* m_cachingStorage; }; } diff --git a/src/akonadi/akonadilivequeryhelpers.cpp b/src/akonadi/akonadilivequeryhelpers.cpp --- a/src/akonadi/akonadilivequeryhelpers.cpp +++ b/src/akonadi/akonadilivequeryhelpers.cpp @@ -24,6 +24,8 @@ #include "akonadilivequeryhelpers.h" +#include "akonadi/akonadimemorystore.h" +#include "akonadi/akonadicachingstorage.h" #include "akonadi/akonadicollectionfetchjobinterface.h" #include "akonadi/akonadicollectionsearchjobinterface.h" #include "akonadi/akonadiitemfetchjobinterface.h" @@ -36,14 +38,20 @@ LiveQueryHelpers::LiveQueryHelpers(const SerializerInterface::Ptr &serializer, const StorageInterface::Ptr &storage) : m_serializer(serializer), - m_storage(storage) + m_storage(storage), + m_cachingStorage(new CachingStorage(storage)) { } +LiveQueryHelpers::~LiveQueryHelpers() +{ + delete m_cachingStorage; +} + LiveQueryHelpers::CollectionFetchFunction LiveQueryHelpers::fetchAllCollections(StorageInterface::FetchContentTypes contentTypes) const { return [this, contentTypes] (const Domain::LiveQueryInput::AddFunction &add) { - auto job = m_storage->fetchCollections(Collection::root(), StorageInterface::Recursive, contentTypes); + auto job = m_cachingStorage->fetchCollections(Collection::root(), StorageInterface::Recursive, contentTypes); Utils::JobHandler::install(job->kjob(), [job, add] { if (job->kjob()->error()) return; @@ -57,7 +65,7 @@ LiveQueryHelpers::CollectionFetchFunction LiveQueryHelpers::fetchCollections(const Collection &root, StorageInterface::FetchContentTypes contentTypes) const { return [this, contentTypes, root] (const Domain::LiveQueryInput::AddFunction &add) { - auto job = m_storage->fetchCollections(root, StorageInterface::Recursive, contentTypes); + auto job = m_cachingStorage->fetchCollections(root, StorageInterface::Recursive, contentTypes); Utils::JobHandler::install(job->kjob(), [root, job, add] { if (job->kjob()->error()) return; @@ -83,7 +91,7 @@ if (searchTerm->isEmpty()) return; - auto job = m_storage->searchCollections(*searchTerm); + auto job = m_cachingStorage->searchCollections(*searchTerm); Utils::JobHandler::install(job->kjob(), [root, job, add] { if (job->kjob()->error()) return; @@ -108,7 +116,7 @@ LiveQueryHelpers::ItemFetchFunction LiveQueryHelpers::fetchItems(StorageInterface::FetchContentTypes contentTypes) const { return [this, contentTypes] (const Domain::LiveQueryInput::AddFunction &add) { - auto job = m_storage->fetchCollections(Akonadi::Collection::root(), + auto job = m_cachingStorage->fetchCollections(Akonadi::Collection::root(), StorageInterface::Recursive, contentTypes); Utils::JobHandler::install(job->kjob(), [this, job, add] { @@ -119,7 +127,7 @@ if (!m_serializer->isSelectedCollection(collection)) continue; - auto job = m_storage->fetchItems(collection); + auto job = m_cachingStorage->fetchItems(collection); Utils::JobHandler::install(job->kjob(), [this, job, add] { if (job->kjob()->error() != KJob::NoError) return; @@ -135,7 +143,7 @@ LiveQueryHelpers::ItemFetchFunction LiveQueryHelpers::fetchItems(const Tag &tag) const { return [this, tag] (const Domain::LiveQueryInput::AddFunction &add) { - auto job = m_storage->fetchTagItems(tag); + auto job = m_cachingStorage->fetchTagItems(tag); Utils::JobHandler::install(job->kjob(), [this, job, add] { if (job->kjob()->error() != KJob::NoError) return; @@ -149,7 +157,7 @@ LiveQueryHelpers::ItemFetchFunction LiveQueryHelpers::fetchSiblings(const Item &item) const { return [this, item] (const Domain::LiveQueryInput::AddFunction &add) { - auto job = m_storage->fetchItem(item); + auto job = m_cachingStorage->fetchItem(item); Utils::JobHandler::install(job->kjob(), [this, job, add] { if (job->kjob()->error() != KJob::NoError) return; @@ -157,7 +165,7 @@ Q_ASSERT(job->items().size() == 1); auto item = job->items()[0]; Q_ASSERT(item.parentCollection().isValid()); - auto job = m_storage->fetchItems(item.parentCollection()); + auto job = m_cachingStorage->fetchItems(item.parentCollection()); Utils::JobHandler::install(job->kjob(), [this, job, add] { if (job->kjob()->error() != KJob::NoError) return; @@ -172,7 +180,7 @@ LiveQueryHelpers::TagFetchFunction LiveQueryHelpers::fetchTags() const { return [this] (const Domain::LiveQueryInput::AddFunction &add) { - auto job = m_storage->fetchTags(); + auto job = m_cachingStorage->fetchTags(); Utils::JobHandler::install(job->kjob(), [this, job, add] { foreach (const auto &tag, job->tags()) add(tag); diff --git a/src/akonadi/akonadimemorystore.h b/src/akonadi/akonadimemorystore.h --- a/src/akonadi/akonadimemorystore.h +++ b/src/akonadi/akonadimemorystore.h @@ -34,6 +34,7 @@ class MemoryStore { public: + typedef QSharedPointer Ptr; MemoryStore(); MemoryStore(const MemoryStore& other); virtual ~MemoryStore(); diff --git a/src/akonadi/akonadimemorystore.cpp b/src/akonadi/akonadimemorystore.cpp --- a/src/akonadi/akonadimemorystore.cpp +++ b/src/akonadi/akonadimemorystore.cpp @@ -91,6 +91,7 @@ void MemoryStore::createCollection(const Akonadi::Collection &collection) { Q_ASSERT(!m_collections.contains(collection.id())); + m_collections[collection.id()] = collection; const auto parentId = findParentId(collection); @@ -164,6 +165,7 @@ void MemoryStore::createTag(const Akonadi::Tag &tag) { Q_ASSERT(!m_tags.contains(tag.id())); + m_tags[tag.id()] = tag; doCreateTag(tag); } @@ -247,6 +249,7 @@ void MemoryStore::createItem(const Akonadi::Item &item) { Q_ASSERT(!m_items.contains(item.id())); + m_items[item.id()] = item; const auto parentId = findParentId(item);