diff --git a/src/akonadi/akonadicachingstorage.cpp b/src/akonadi/akonadicachingstorage.cpp index 819b71ff..96fdd893 100644 --- a/src/akonadi/akonadicachingstorage.cpp +++ b/src/akonadi/akonadicachingstorage.cpp @@ -1,542 +1,527 @@ /* This file is part of Zanshin Copyright 2015 Mario Bensi Copyright 2017 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 "akonadicachingstorage.h" #include "akonadistorage.h" #include "akonadicollectionfetchjobinterface.h" #include "akonadiitemfetchjobinterface.h" #include "akonaditagfetchjobinterface.h" #include using namespace Akonadi; class CachingCollectionFetchJob : public KCompositeJob, public CollectionFetchJobInterface { Q_OBJECT public: CachingCollectionFetchJob(const StorageInterface::Ptr &storage, const Cache::Ptr &cache, const Collection &collection, StorageInterface::FetchDepth depth, QObject *parent = nullptr) : KCompositeJob(parent), m_started(false), m_storage(storage), m_cache(cache), m_collection(collection), m_depth(depth) { QTimer::singleShot(0, this, &CachingCollectionFetchJob::start); } void start() override { if (m_started) return; if (m_cache->isCollectionListPopulated()) { QTimer::singleShot(0, this, &CachingCollectionFetchJob::retrieveFromCache); } else { auto job = m_storage->fetchCollections(Akonadi::Collection::root(), Akonadi::StorageInterface::Recursive); job->setResource(m_resource); addSubjob(job->kjob()); } m_started = true; } Collection::List collections() const override { const auto isInputCollection = [this] (const Collection &collection) { return collection.id() == m_collection.id() || (!m_collection.remoteId().isEmpty() && collection.remoteId() == m_collection.remoteId()); }; if (m_depth == StorageInterface::Base) { auto it = std::find_if(m_collections.cbegin(), m_collections.cend(), isInputCollection); if (it != m_collections.cend()) return Collection::List() << *it; else return Collection::List(); } auto collections = m_collections; auto it = collections.begin(); if (m_depth == StorageInterface::FirstLevel) { it = std::remove_if(collections.begin(), collections.end(), [isInputCollection] (const Collection &collection) { return !isInputCollection(collection.parentCollection()); }); } else { it = std::remove_if(collections.begin(), collections.end(), [isInputCollection] (const Collection &collection) { auto parent = collection.parentCollection(); while (parent.isValid() && !isInputCollection(parent)) parent = parent.parentCollection(); return !isInputCollection(parent); }); } collections.erase(it, collections.end()); return collections; } void setResource(const QString &resource) override { m_resource = resource; } private: void slotResult(KJob *kjob) override { if (kjob->error()) { KCompositeJob::slotResult(kjob); return; } auto job = dynamic_cast(kjob); Q_ASSERT(job); auto cachedCollections = job->collections(); for (const auto &collection : job->collections()) { auto parent = collection.parentCollection(); while (parent.isValid() && parent != Akonadi::Collection::root()) { if (!cachedCollections.contains(parent)) { cachedCollections.append(parent); } parent = parent.parentCollection(); } } m_cache->setCollections(cachedCollections); m_collections = job->collections(); emitResult(); } void retrieveFromCache() { m_collections = m_cache->collections(); emitResult(); } bool m_started; StorageInterface::Ptr m_storage; Cache::Ptr m_cache; QString m_resource; const Collection m_collection; const StorageInterface::FetchDepth m_depth; 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; }; class CachingSingleItemFetchJob : public KCompositeJob, public ItemFetchJobInterface { Q_OBJECT public: CachingSingleItemFetchJob(const StorageInterface::Ptr &storage, const Cache::Ptr &cache, const Item &item, QObject *parent = nullptr) : KCompositeJob(parent), m_started(false), m_storage(storage), m_cache(cache), m_item(item) { QTimer::singleShot(0, this, &CachingSingleItemFetchJob::start); } void start() override { if (m_started) return; const auto item = m_cache->item(m_item.id()); if (item.isValid()) { QTimer::singleShot(0, this, [this, item] { retrieveFromCache(item); }); } else { auto job = m_storage->fetchItem(m_item); 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(); emitResult(); } void retrieveFromCache(const Item &item) { m_items = Item::List() << item; emitResult(); } bool m_started; StorageInterface::Ptr m_storage; Cache::Ptr m_cache; Item m_item; Collection m_collection; 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 public: CachingTagFetchJob(const StorageInterface::Ptr &storage, const Cache::Ptr &cache, QObject *parent = nullptr) : KCompositeJob(parent), m_started(false), m_storage(storage), m_cache(cache) { QTimer::singleShot(0, this, &CachingTagFetchJob::start); } void start() override { if (m_started) return; if (m_cache->isTagListPopulated()) { QTimer::singleShot(0, this, &CachingTagFetchJob::retrieveFromCache); } else { auto job = m_storage->fetchTags(); addSubjob(job->kjob()); } m_started = true; } Tag::List tags() const override { return m_tags; } private: void slotResult(KJob *kjob) override { if (kjob->error()) { KCompositeJob::slotResult(kjob); return; } auto job = dynamic_cast(kjob); Q_ASSERT(job); m_tags = job->tags(); m_cache->setTags(m_tags); emitResult(); } void retrieveFromCache() { m_tags = m_cache->tags(); emitResult(); } bool m_started; StorageInterface::Ptr m_storage; Cache::Ptr m_cache; Tag::List m_tags; }; CachingStorage::CachingStorage(const Cache::Ptr &cache, const StorageInterface::Ptr &storage) : m_cache(cache), m_storage(storage) { } CachingStorage::~CachingStorage() { } Collection CachingStorage::defaultCollection() { return m_storage->defaultCollection(); } KJob *CachingStorage::createItem(Item item, Collection collection) { return m_storage->createItem(item, collection); } KJob *CachingStorage::updateItem(Item item, QObject *parent) { return m_storage->updateItem(item, parent); } KJob *CachingStorage::removeItem(Item item) { return m_storage->removeItem(item); } KJob *CachingStorage::removeItems(Item::List items, QObject *parent) { return m_storage->removeItems(items, parent); } KJob *CachingStorage::moveItem(Item item, Collection collection, QObject *parent) { return m_storage->moveItem(item, collection, parent); } KJob *CachingStorage::moveItems(Item::List items, Collection collection, QObject *parent) { return m_storage->moveItems(items, collection, parent); } KJob *CachingStorage::createCollection(Collection collection, QObject *parent) { return m_storage->createCollection(collection, parent); } KJob *CachingStorage::updateCollection(Collection collection, QObject *parent) { return m_storage->updateCollection(collection, parent); } KJob *CachingStorage::removeCollection(Collection collection, QObject *parent) { return m_storage->removeCollection(collection, parent); } KJob *CachingStorage::createTransaction() { return m_storage->createTransaction(); } -KJob *CachingStorage::createTag(Tag tag) -{ - return m_storage->createTag(tag); -} - -KJob *CachingStorage::updateTag(Tag tag) -{ - return m_storage->updateTag(tag); -} - -KJob *CachingStorage::removeTag(Tag tag) -{ - return m_storage->removeTag(tag); -} - CollectionFetchJobInterface *CachingStorage::fetchCollections(Collection collection, StorageInterface::FetchDepth depth) { return new CachingCollectionFetchJob(m_storage, m_cache, collection, depth); } ItemFetchJobInterface *CachingStorage::fetchItems(Collection collection) { return new CachingCollectionItemsFetchJob(m_storage, m_cache, collection); } ItemFetchJobInterface *CachingStorage::fetchItem(Akonadi::Item item) { return new CachingSingleItemFetchJob(m_storage, m_cache, item); } ItemFetchJobInterface *CachingStorage::fetchTagItems(Tag tag) { return new CachingTagItemsFetchJob(m_storage, m_cache, tag); } TagFetchJobInterface *CachingStorage::fetchTags() { return new CachingTagFetchJob(m_storage, m_cache); } #include "akonadicachingstorage.moc" diff --git a/src/akonadi/akonadicachingstorage.h b/src/akonadi/akonadicachingstorage.h index 85de4673..78777014 100644 --- a/src/akonadi/akonadicachingstorage.h +++ b/src/akonadi/akonadicachingstorage.h @@ -1,71 +1,67 @@ /* This file is part of Zanshin Copyright 2015 Mario Bensi Copyright 2017 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_CACHING_STORAGE_H #define AKONADI_CACHING_STORAGE_H #include "akonadistorageinterface.h" #include "akonadicache.h" namespace Akonadi { class CachingStorage : public StorageInterface { public: explicit CachingStorage(const Cache::Ptr &cache, const StorageInterface::Ptr &storage); virtual ~CachingStorage(); Akonadi::Collection defaultCollection() override; KJob *createItem(Item item, Collection collection) override; KJob *updateItem(Item item, QObject *parent = nullptr) override; KJob *removeItem(Akonadi::Item item) override; KJob *removeItems(Item::List items, QObject *parent = nullptr) override; KJob *moveItem(Item item, Collection collection, QObject *parent = nullptr) override; KJob *moveItems(Item::List item, Collection collection, QObject *parent = nullptr) override; KJob *createCollection(Collection collection, QObject *parent = nullptr) override; KJob *updateCollection(Collection collection, QObject *parent = nullptr) override; KJob *removeCollection(Collection collection, QObject *parent = nullptr) override; KJob *createTransaction() override; - KJob *createTag(Akonadi::Tag tag) override; - KJob *updateTag(Akonadi::Tag tag) override; - KJob *removeTag(Akonadi::Tag tag) override; - CollectionFetchJobInterface *fetchCollections(Akonadi::Collection collection, FetchDepth depth) override; ItemFetchJobInterface *fetchItems(Akonadi::Collection collection) override; ItemFetchJobInterface *fetchItem(Akonadi::Item item) override; ItemFetchJobInterface *fetchTagItems(Akonadi::Tag tag) override; TagFetchJobInterface *fetchTags() override; private: Cache::Ptr m_cache; StorageInterface::Ptr m_storage; }; } #endif // AKONADI_CACHING_STORAGE_H diff --git a/src/akonadi/akonadiserializer.cpp b/src/akonadi/akonadiserializer.cpp index 42b16320..6942a7cf 100644 --- a/src/akonadi/akonadiserializer.cpp +++ b/src/akonadi/akonadiserializer.cpp @@ -1,637 +1,641 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens Copyright 2014 Rémi Benoit 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 "akonadiserializer.h" #include #include #include #include #include #include #include "utils/mem_fn.h" #include "akonadi/akonadiapplicationselectedattribute.h" #include "akonadi/akonaditimestampattribute.h" using namespace Akonadi; static const char s_contextListProperty[] = "ContextList"; static const char s_appName[] = "Zanshin"; Serializer::Serializer() { } Serializer::~Serializer() { } bool Serializer::representsCollection(SerializerInterface::QObjectPtr object, Collection collection) { return object->property("collectionId").toLongLong() == collection.id(); } bool Serializer::representsItem(QObjectPtr object, Item item) { return object->property("itemId").toLongLong() == item.id(); } QString Serializer::itemUid(const Item &item) { if (item.hasPayload()) { const auto todo = item.payload(); return todo->uid(); } else { return QString(); } } Domain::DataSource::Ptr Serializer::createDataSourceFromCollection(Collection collection, DataSourceNameScheme naming) { if (!collection.isValid()) return Domain::DataSource::Ptr(); auto dataSource = Domain::DataSource::Ptr::create(); updateDataSourceFromCollection(dataSource, collection, naming); return dataSource; } void Serializer::updateDataSourceFromCollection(Domain::DataSource::Ptr dataSource, Collection collection, DataSourceNameScheme naming) { if (!collection.isValid()) return; QString name = collection.displayName(); if (naming == FullPath) { auto parent = collection.parentCollection(); while (parent.isValid() && parent != Akonadi::Collection::root()) { name = parent.displayName() + " » " + name; parent = parent.parentCollection(); } } dataSource->setName(name); const auto mimeTypes = collection.contentMimeTypes(); auto types = Domain::DataSource::ContentTypes(); if (mimeTypes.contains(KCalCore::Todo::todoMimeType())) types |= Domain::DataSource::Tasks; dataSource->setContentTypes(types); if (collection.hasAttribute()) { auto iconName = collection.attribute()->iconName(); dataSource->setIconName(iconName); } if (!collection.hasAttribute()) { dataSource->setSelected(true); } else { auto isSelected = collection.attribute()->isSelected(); dataSource->setSelected(isSelected); } dataSource->setProperty("collectionId", collection.id()); } Collection Serializer::createCollectionFromDataSource(Domain::DataSource::Ptr dataSource) { const auto id = dataSource->property("collectionId").value(); auto collection = Collection(id); collection.attribute(Akonadi::Collection::AddIfMissing); auto selectedAttribute = collection.attribute(Akonadi::Collection::AddIfMissing); selectedAttribute->setSelected(dataSource->isSelected()); return collection; } bool Serializer::isSelectedCollection(Collection collection) { if (!isTaskCollection(collection)) return false; if (!collection.hasAttribute()) return true; return collection.attribute()->isSelected(); } bool Akonadi::Serializer::isTaskCollection(Akonadi::Collection collection) { return collection.contentMimeTypes().contains(KCalCore::Todo::todoMimeType()); } bool Serializer::isTaskItem(Item item) { if (!item.hasPayload()) return false; return !isProjectItem(item) && !isContext(item); } Domain::Task::Ptr Serializer::createTaskFromItem(Item item) { if (!isTaskItem(item)) return Domain::Task::Ptr(); auto task = Domain::Task::Ptr::create(); updateTaskFromItem(task, item); return task; } void Serializer::updateTaskFromItem(Domain::Task::Ptr task, Item item) { if (!isTaskItem(item)) return; auto todo = item.payload(); task->setTitle(todo->summary()); task->setText(todo->description()); task->setDone(todo->isCompleted()); task->setDoneDate(todo->completed().toLocalTime().date()); task->setStartDate(todo->dtStart().toLocalTime().date()); task->setDueDate(todo->dtDue().toLocalTime().date()); task->setProperty("itemId", item.id()); task->setProperty("parentCollectionId", item.parentCollection().id()); task->setProperty("todoUid", todo->uid()); task->setProperty("relatedUid", todo->relatedTo()); task->setRunning(todo->customProperty("Zanshin", "Running") == QLatin1String("1")); switch (todo->recurrence()->recurrenceType()) { case KCalCore::Recurrence::rDaily: task->setRecurrence(Domain::Task::RecursDaily); break; case KCalCore::Recurrence::rWeekly: task->setRecurrence(Domain::Task::RecursWeekly); break; case KCalCore::Recurrence::rMonthlyDay: task->setRecurrence(Domain::Task::RecursMonthly); break; default: // Other cases are not supported for now and as such just ignored break; } QMimeDatabase mimeDb; const auto attachmentsInput = todo->attachments(); Domain::Task::Attachments attachments; attachments.reserve(attachmentsInput.size()); std::transform(attachmentsInput.cbegin(), attachmentsInput.cend(), std::back_inserter(attachments), [&mimeDb] (const KCalCore::Attachment::Ptr &attach) { Domain::Task::Attachment attachment; if (attach->isUri()) attachment.setUri(QUrl(attach->uri())); else attachment.setData(attach->decodedData()); attachment.setLabel(attach->label()); attachment.setMimeType(attach->mimeType()); attachment.setIconName(mimeDb.mimeTypeForName(attach->mimeType()).iconName()); return attachment; }); task->setAttachments(attachments); } bool Serializer::isTaskChild(Domain::Task::Ptr task, Akonadi::Item item) { if (!isTaskItem(item)) return false; auto todo = item.payload(); if (todo->relatedTo() == task->property("todoUid")) return true; return false; } Akonadi::Item Serializer::createItemFromTask(Domain::Task::Ptr task) { auto todo = KCalCore::Todo::Ptr::create(); todo->setSummary(task->title()); todo->setDescription(task->text()); // We only support all-day todos, so ignore timezone information and possible effect from timezone on dates // KCalCore reads "DUE;VALUE=DATE:20171130" as QDateTime(QDate(2017, 11, 30), QTime(), Qt::LocalTime), for lack of timezone information // so we should never call toUtc() on that, it would mess up the date. // If one day we want to support time information, we need to add a task->isAllDay()/setAllDay(). todo->setDtStart(QDateTime(task->startDate())); todo->setDtDue(QDateTime(task->dueDate())); todo->setAllDay(true); if (task->property("todoUid").isValid()) { todo->setUid(task->property("todoUid").toString()); } if (task->property("relatedUid").isValid()) { todo->setRelatedTo(task->property("relatedUid").toString()); } switch (task->recurrence()) { case Domain::Task::NoRecurrence: break; case Domain::Task::RecursDaily: todo->recurrence()->setDaily(1); break; case Domain::Task::RecursWeekly: todo->recurrence()->setWeekly(1); break; case Domain::Task::RecursMonthly: todo->recurrence()->setMonthly(1); break; } for (const auto &attachment : task->attachments()) { KCalCore::Attachment::Ptr attach(new KCalCore::Attachment(QByteArray())); if (attachment.isUri()) attach->setUri(attachment.uri().toString()); else attach->setDecodedData(attachment.data()); attach->setMimeType(attachment.mimeType()); attach->setLabel(attachment.label()); todo->addAttachment(attach); } if (task->isRunning()) { todo->setCustomProperty("Zanshin", "Running", "1"); } else { todo->removeCustomProperty("Zanshin", "Running"); } // Needs to be done after all other dates are positioned // since this applies the recurrence logic if (task->isDone()) todo->setCompleted(QDateTime(task->doneDate(), QTime(), Qt::UTC)); else todo->setCompleted(false); Akonadi::Item item; if (task->property("itemId").isValid()) { item.setId(task->property("itemId").value()); } if (task->property("parentCollectionId").isValid()) { auto parentId = task->property("parentCollectionId").value(); item.setParentCollection(Akonadi::Collection(parentId)); } item.setMimeType(KCalCore::Todo::todoMimeType()); item.setPayload(todo); return item; } QString Serializer::relatedUidFromItem(Akonadi::Item item) { if (isTaskItem(item)) { const auto todo = item.payload(); return todo->relatedTo(); } else { return QString(); } } void Serializer::updateItemParent(Akonadi::Item item, Domain::Task::Ptr parent) { if (!isTaskItem(item)) return; auto todo = item.payload(); todo->setRelatedTo(parent->property("todoUid").toString()); } void Serializer::updateItemProject(Item item, Domain::Project::Ptr project) { if (isTaskItem(item)) { auto todo = item.payload(); todo->setRelatedTo(project->property("todoUid").toString()); } } void Serializer::removeItemParent(Akonadi::Item item) { if (!isTaskItem(item)) return; auto todo = item.payload(); todo->setRelatedTo(QString()); } void Serializer::promoteItemToProject(Akonadi::Item item) { if (!isTaskItem(item)) return; auto todo = item.payload(); todo->setRelatedTo(QString()); todo->setCustomProperty("Zanshin", "Project", QStringLiteral("1")); } void Serializer::clearItem(Akonadi::Item *item) { Q_ASSERT(item); if (!isTaskItem(*item)) return; auto todo = item->payload(); todo->removeCustomProperty(s_appName, s_contextListProperty); } Akonadi::Item::List Serializer::filterDescendantItems(const Akonadi::Item::List &potentialChildren, const Akonadi::Item &ancestorItem) { if (potentialChildren.isEmpty()) return Akonadi::Item::List(); Akonadi::Item::List itemsToProcess = potentialChildren; Q_ASSERT(ancestorItem.isValid() && ancestorItem.hasPayload()); KCalCore::Todo::Ptr todo = ancestorItem.payload(); const auto bound = std::partition(itemsToProcess.begin(), itemsToProcess.end(), [ancestorItem, todo](Akonadi::Item currentItem) { return (!currentItem.hasPayload() || currentItem == ancestorItem || currentItem.payload()->relatedTo() != todo->uid()); }); Akonadi::Item::List itemsRemoved; itemsRemoved.reserve(std::distance(itemsToProcess.begin(), bound)); std::copy(itemsToProcess.begin(), bound, std::back_inserter(itemsRemoved)); itemsToProcess.erase(itemsToProcess.begin(), bound); auto result = std::accumulate(itemsToProcess.begin(), itemsToProcess.end(), Akonadi::Item::List(), [this, itemsRemoved](Akonadi::Item::List result, Akonadi::Item currentItem) { result << currentItem; return result += filterDescendantItems(itemsRemoved, currentItem); }); return result; } bool Serializer::isProjectItem(Item item) { if (!item.hasPayload()) return false; auto todo = item.payload(); return !todo->customProperty(s_appName, "Project").isEmpty(); } Domain::Project::Ptr Serializer::createProjectFromItem(Item item) { if (!isProjectItem(item)) return Domain::Project::Ptr(); auto project = Domain::Project::Ptr::create(); updateProjectFromItem(project, item); return project; } void Serializer::updateProjectFromItem(Domain::Project::Ptr project, Item item) { if (!isProjectItem(item)) return; auto todo = item.payload(); project->setName(todo->summary()); project->setProperty("itemId", item.id()); project->setProperty("parentCollectionId", item.parentCollection().id()); project->setProperty("todoUid", todo->uid()); } Item Serializer::createItemFromProject(Domain::Project::Ptr project) { auto todo = KCalCore::Todo::Ptr::create(); todo->setSummary(project->name()); todo->setCustomProperty("Zanshin", "Project", QStringLiteral("1")); if (project->property("todoUid").isValid()) { todo->setUid(project->property("todoUid").toString()); } Akonadi::Item item; if (project->property("itemId").isValid()) { item.setId(project->property("itemId").value()); } if (project->property("parentCollectionId").isValid()) { auto parentId = project->property("parentCollectionId").value(); item.setParentCollection(Akonadi::Collection(parentId)); } item.setMimeType(KCalCore::Todo::todoMimeType()); item.setPayload(todo); return item; } bool Serializer::isProjectChild(Domain::Project::Ptr project, Item item) { const QString todoUid = project->property("todoUid").toString(); const QString relatedUid = relatedUidFromItem(item); return !todoUid.isEmpty() && !relatedUid.isEmpty() && todoUid == relatedUid; } Domain::Context::Ptr Serializer::createContextFromTag(Akonadi::Tag tag) { if (!isContext(tag)) return Domain::Context::Ptr(); auto context = Domain::Context::Ptr::create(); updateContextFromTag(context, tag); return context; } Akonadi::Tag Serializer::createTagFromContext(Domain::Context::Ptr context) { auto tag = Akonadi::Tag(); tag.setName(context->name()); tag.setType(Akonadi::SerializerInterface::contextTagType()); tag.setGid(QByteArray(context->name().toLatin1())); if (context->property("tagId").isValid()) tag.setId(context->property("tagId").value()); return tag; } void Serializer::updateContextFromTag(Domain::Context::Ptr context, Akonadi::Tag tag) { if (!isContext(tag)) return; context->setProperty("tagId", tag.id()); context->setName(tag.name()); } bool Serializer::isContext(const Akonadi::Tag &tag) const { return (tag.type() == Akonadi::SerializerInterface::contextTagType()); } bool Serializer::isContextTag(const Domain::Context::Ptr &context, const Akonadi::Tag &tag) const { return (context->property("tagId").value() == tag.id()); } static QStringList extractContexts(KCalCore::Todo::Ptr todo) { const auto contexts = todo->customProperty(s_appName, s_contextListProperty); return contexts.split(',', QString::SkipEmptyParts); } bool Serializer::isContextChild(Domain::Context::Ptr context, Item item) const { if (!context->property("todoUid").isValid()) return false; if (!item.hasPayload()) return false; auto contextUid = context->property("todoUid").toString(); auto todo = item.payload(); const auto contextList = extractContexts(todo); return contextList.contains(contextUid); } bool Serializer::isContext(Item item) { if (!item.hasPayload()) return false; auto todo = item.payload(); return !todo->customProperty(s_appName, "Context").isEmpty(); } bool Serializer::itemRepresentsContext(const Domain::Context::Ptr &context, Item item) const { if (!item.hasPayload()) return false; const auto todo = item.payload(); const auto contextUid = context->property("todoUid").toString(); return !todo->customProperty(s_appName, "Context").isEmpty() && todo->uid() == contextUid; } Domain::Context::Ptr Serializer::createContextFromItem(Item item) { if (!isContext(item)) return {}; auto context = Domain::Context::Ptr::create(); updateContextFromItem(context, item); return context; } Akonadi::Item Serializer::createItemFromContext(Domain::Context::Ptr context) { auto todo = KCalCore::Todo::Ptr::create(); todo->setSummary(context->name()); todo->setCustomProperty(s_appName, "Context", QStringLiteral("1")); if (context->property("todoUid").isValid()) { todo->setUid(context->property("todoUid").toString()); } auto item = Akonadi::Item(); if (context->property("itemId").isValid()) { item.setId(context->property("itemId").value()); } if (context->property("parentCollectionId").isValid()) { auto parentId = context->property("parentCollectionId").value(); item.setParentCollection(Akonadi::Collection(parentId)); } item.setMimeType(KCalCore::Todo::todoMimeType()); item.setPayload(todo); return item; } void Serializer::updateContextFromItem(Domain::Context::Ptr context, Item item) { if (!isContext(item)) return; auto todo = item.payload(); context->setName(todo->summary()); context->setProperty("itemId", item.id()); context->setProperty("parentCollectionId", item.parentCollection().id()); context->setProperty("todoUid", todo->uid()); } void Serializer::addContextToTask(Domain::Context::Ptr context, Item item) { - if (!isTaskItem(item)) + if (!isTaskItem(item)) { + qWarning() << "Cannot add context to a non-task" << item.id(); return; + } auto todo = item.payload(); if (!context->property("todoUid").isValid()) return; auto contextUid = context->property("todoUid").toString(); auto contextList = extractContexts(todo); if (!contextList.contains(contextUid)) contextList.append(contextUid); todo->setCustomProperty(s_appName, s_contextListProperty, contextList.join(',')); item.setPayload(todo); } void Serializer::removeContextFromTask(Domain::Context::Ptr context, Item item) { - if (!isTaskItem(item)) + if (!isTaskItem(item)) { + qWarning() << "Cannot remove context from a non-task" << item.id(); return; + } auto todo = item.payload(); if (!context->property("todoUid").isValid()) return; auto contextUid = context->property("todoUid").toString(); QStringList contextList = extractContexts(todo); contextList.removeAll(contextUid); if (contextList.isEmpty()) todo->removeCustomProperty(s_appName, s_contextListProperty); else todo->setCustomProperty(s_appName, s_contextListProperty, contextList.join(',')); item.setPayload(todo); } QString Serializer::contextUid(Item item) { if (!isContext(item)) return {}; auto todo = item.payload(); return todo->uid(); } diff --git a/src/akonadi/akonadistorage.cpp b/src/akonadi/akonadistorage.cpp index 05237a27..cce6cd14 100644 --- a/src/akonadi/akonadistorage.cpp +++ b/src/akonadi/akonadistorage.cpp @@ -1,305 +1,287 @@ /* 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 "akonadistorage.h" #include #include #include #include #include #include #include #include #include #include #include #include #include -#include -#include #include #include -#include #include #include "akonadi/akonadicollectionfetchjobinterface.h" #include "akonadi/akonadiitemfetchjobinterface.h" #include "akonadi/akonaditagfetchjobinterface.h" #include "akonadi/akonadistoragesettings.h" using namespace Akonadi; class CollectionJob : public CollectionFetchJob, public CollectionFetchJobInterface { Q_OBJECT public: CollectionJob(const Collection &collection, Type type = FirstLevel, QObject *parent = nullptr) : CollectionFetchJob(collection, type, parent), m_collection(collection), m_type(type) { } Collection::List collections() const override { auto collections = CollectionFetchJob::collections(); // Memorize them to reconstruct the ancestor chain later QMap collectionsMap; collectionsMap[m_collection.id()] = m_collection; foreach (const auto &collection, collections) { collectionsMap[collection.id()] = collection; } // Why the hell isn't fetchScope() const and returning a reference??? auto self = const_cast(this); const auto allowedMimeTypes = self->fetchScope().contentMimeTypes().toSet(); if (!allowedMimeTypes.isEmpty()) { collections.erase(std::remove_if(collections.begin(), collections.end(), [allowedMimeTypes] (const Collection &collection) { auto mimeTypes = collection.contentMimeTypes().toSet(); return !mimeTypes.intersects(allowedMimeTypes); }), collections.end()); } if (m_type != Base) { // Replace the dummy parents in the ancestor chain with proper ones // full of juicy data std::function reconstructAncestors = [collectionsMap, &reconstructAncestors, this] (const Collection &collection) -> Collection { Q_ASSERT(collection.isValid()); if (collection == m_collection) return collection; auto parent = collection.parentCollection(); auto reconstructedParent = reconstructAncestors(collectionsMap[parent.id()]); auto result = collection; result.setParentCollection(reconstructedParent); return result; }; std::transform(collections.begin(), collections.end(), collections.begin(), reconstructAncestors); } return collections; } void setResource(const QString &resource) override { fetchScope().setResource(resource); } private: const Collection m_collection; const Type m_type; }; class ItemJob : public ItemFetchJob, public ItemFetchJobInterface { Q_OBJECT public: using ItemFetchJob::ItemFetchJob; Item::List items() const override { return ItemFetchJob::items(); } void setCollection(const Collection &collection) override { ItemFetchJob::setCollection(collection); } }; class TagJob : public TagFetchJob, public TagFetchJobInterface { Q_OBJECT public: using TagFetchJob::TagFetchJob; Tag::List tags() const override { return TagFetchJob::tags(); } }; Storage::Storage() { } Storage::~Storage() { } Collection Storage::defaultCollection() { return StorageSettings::instance().defaultCollection(); } KJob *Storage::createItem(Item item, Collection collection) { return new ItemCreateJob(item, collection); } KJob *Storage::updateItem(Item item, QObject *parent) { return new ItemModifyJob(item, parent); } KJob *Storage::removeItem(Item item) { return new ItemDeleteJob(item); } KJob *Storage::removeItems(Item::List items, QObject *parent) { return new ItemDeleteJob(items, parent); } KJob *Storage::moveItem(Item item, Collection collection, QObject *parent) { return new ItemMoveJob(item, collection, parent); } KJob *Storage::moveItems(Item::List items, Collection collection, QObject *parent) { return new ItemMoveJob(items, collection, parent); } KJob *Storage::createCollection(Collection collection, QObject *parent) { return new CollectionCreateJob(collection, parent); } KJob *Storage::updateCollection(Collection collection, QObject *parent) { return new CollectionModifyJob(collection, parent); } KJob *Storage::removeCollection(Collection collection, QObject *parent) { return new CollectionDeleteJob(collection, parent); } KJob *Storage::createTransaction() { return new TransactionSequence(); } -KJob *Storage::createTag(Tag tag) -{ - return new TagCreateJob(tag); -} - -KJob *Storage::updateTag(Tag tag) -{ - return new TagModifyJob(tag); -} - -KJob *Storage::removeTag(Tag tag) -{ - return new Akonadi::TagDeleteJob(tag); -} - CollectionFetchJobInterface *Storage::fetchCollections(Collection collection, StorageInterface::FetchDepth depth) { auto job = new CollectionJob(collection, jobTypeFromDepth(depth)); auto scope = job->fetchScope(); scope.setContentMimeTypes({KCalCore::Todo::todoMimeType()}); scope.setIncludeStatistics(true); scope.setAncestorRetrieval(CollectionFetchScope::All); scope.setListFilter(Akonadi::CollectionFetchScope::Display); job->setFetchScope(scope); return job; } ItemFetchJobInterface *Storage::fetchItems(Collection collection) { auto job = new ItemJob(collection); configureItemFetchJob(job); return job; } ItemFetchJobInterface *Storage::fetchItem(Akonadi::Item item) { auto job = new ItemJob(item); configureItemFetchJob(job); return job; } ItemFetchJobInterface *Storage::fetchTagItems(Tag tag) { auto job = new ItemJob(tag); configureItemFetchJob(job); return job; } TagFetchJobInterface *Storage::fetchTags() { return new TagJob; } CollectionFetchJob::Type Storage::jobTypeFromDepth(StorageInterface::FetchDepth depth) { auto jobType = CollectionJob::Base; switch (depth) { case Base: jobType = CollectionJob::Base; break; case FirstLevel: jobType = CollectionJob::FirstLevel; break; case Recursive: jobType = CollectionJob::Recursive; break; default: qFatal("Unexpected enum value"); break; } return jobType; } void Storage::configureItemFetchJob(ItemJob *job) { auto scope = job->fetchScope(); scope.fetchFullPayload(); scope.fetchAllAttributes(); scope.setFetchTags(true); scope.tagFetchScope().setFetchIdOnly(false); scope.setAncestorRetrieval(ItemFetchScope::All); job->setFetchScope(scope); } #include "akonadistorage.moc" diff --git a/src/akonadi/akonadistorage.h b/src/akonadi/akonadistorage.h index 4f8fe3ab..a27060fe 100644 --- a/src/akonadi/akonadistorage.h +++ b/src/akonadi/akonadistorage.h @@ -1,72 +1,68 @@ /* 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_STORAGE_H #define AKONADI_STORAGE_H #include "akonadistorageinterface.h" #include class ItemJob; namespace Akonadi { class Storage : public StorageInterface { public: Storage(); virtual ~Storage(); Akonadi::Collection defaultCollection() override; KJob *createItem(Item item, Collection collection) override; KJob *updateItem(Item item, QObject *parent = nullptr) override; KJob *removeItem(Akonadi::Item item) override; KJob *removeItems(Item::List items, QObject *parent = nullptr) override; KJob *moveItem(Item item, Collection collection, QObject *parent = nullptr) override; KJob *moveItems(Item::List item, Collection collection, QObject *parent = nullptr) override; KJob *createCollection(Collection collection, QObject *parent = nullptr) override; KJob *updateCollection(Collection collection, QObject *parent = nullptr) override; KJob *removeCollection(Collection collection, QObject *parent = nullptr) override; KJob *createTransaction() override; - KJob *createTag(Akonadi::Tag tag) override; - KJob *updateTag(Akonadi::Tag tag) override; - KJob *removeTag(Akonadi::Tag tag) override; - CollectionFetchJobInterface *fetchCollections(Akonadi::Collection collection, FetchDepth depth) override; ItemFetchJobInterface *fetchItems(Akonadi::Collection collection) override; ItemFetchJobInterface *fetchItem(Akonadi::Item item) override; ItemFetchJobInterface *fetchTagItems(Akonadi::Tag tag) override; TagFetchJobInterface *fetchTags() override; private: CollectionFetchJob::Type jobTypeFromDepth(StorageInterface::FetchDepth depth); void configureItemFetchJob(ItemJob *job); }; } #endif // AKONADI_STORAGE_H diff --git a/src/akonadi/akonadistorageinterface.h b/src/akonadi/akonadistorageinterface.h index fa1ea47a..46728334 100644 --- a/src/akonadi/akonadistorageinterface.h +++ b/src/akonadi/akonadistorageinterface.h @@ -1,85 +1,81 @@ /* 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_STORAGEINTERFACE_H #define AKONADI_STORAGEINTERFACE_H #include #include #include class KJob; class QObject; class QByteArray; namespace Akonadi { class Collection; class CollectionFetchJobInterface; class ItemFetchJobInterface; class TagFetchJobInterface; class StorageInterface { public: typedef QSharedPointer Ptr; enum FetchDepth { Base, FirstLevel, Recursive }; StorageInterface(); virtual ~StorageInterface(); virtual Akonadi::Collection defaultCollection() = 0; virtual KJob *createItem(Akonadi::Item item, Akonadi::Collection collection) = 0; virtual KJob *updateItem(Akonadi::Item item, QObject *parent = nullptr) = 0; virtual KJob *removeItem(Akonadi::Item item) = 0; virtual KJob *removeItems(Item::List items, QObject *parent = nullptr) = 0; virtual KJob *moveItem(Item item, Collection collection, QObject *parent = nullptr) = 0; virtual KJob *moveItems(Item::List item, Collection collection, QObject *parent = nullptr) = 0; virtual KJob *createCollection(Collection collection, QObject *parent = nullptr) = 0; virtual KJob *updateCollection(Collection collection, QObject *parent = nullptr) = 0; virtual KJob *removeCollection(Collection collection, QObject *parent = nullptr) = 0; virtual KJob *createTransaction() = 0; - virtual KJob *createTag(Akonadi::Tag tag) = 0; - virtual KJob *updateTag(Akonadi::Tag tag) = 0; - virtual KJob *removeTag(Akonadi::Tag tag) = 0; - virtual CollectionFetchJobInterface *fetchCollections(Akonadi::Collection collection, FetchDepth depth) = 0; virtual ItemFetchJobInterface *fetchItems(Akonadi::Collection collection) = 0; virtual ItemFetchJobInterface *fetchItem(Akonadi::Item item) = 0; virtual ItemFetchJobInterface *fetchTagItems(Akonadi::Tag tag) = 0; virtual TagFetchJobInterface *fetchTags() = 0; }; } #endif // AKONADI_STORAGEINTERFACE_H diff --git a/src/akonadi/akonaditagfetchjobinterface.cpp b/src/akonadi/akonaditagfetchjobinterface.cpp deleted file mode 100644 index 72faa5d3..00000000 --- a/src/akonadi/akonaditagfetchjobinterface.cpp +++ /dev/null @@ -1,44 +0,0 @@ -/* This file is part of Zanshin - - Copyright 2014 Franck Arrecot - - 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 "akonaditagfetchjobinterface.h" -#include - -using namespace Akonadi ; - -TagFetchJobInterface::TagFetchJobInterface() -{ -} - -TagFetchJobInterface::~TagFetchJobInterface() -{ -} - -KJob *TagFetchJobInterface::kjob() -{ - KJob *job = dynamic_cast(this); - Q_ASSERT(job); - return job; -} - - diff --git a/src/akonadi/akonaditagfetchjobinterface.h b/src/akonadi/akonaditagfetchjobinterface.h deleted file mode 100644 index a60846d2..00000000 --- a/src/akonadi/akonaditagfetchjobinterface.h +++ /dev/null @@ -1,48 +0,0 @@ -/* This file is part of Zanshin - - Copyright 2014 Franck Arrecot - - 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_TAGFETCHJOBINTERFACE_H -#define AKONADI_TAGFETCHJOBINTERFACE_H - -#include - -class KJob; - -namespace Akonadi { - -class TagFetchJobInterface -{ - public: - TagFetchJobInterface(); - virtual ~TagFetchJobInterface(); - - KJob *kjob(); - - virtual Tag::List tags() const = 0; -}; - -} // Akonadi namespace - - -#endif // AKONADI_TAGFETCHJOBINTERFACE_H diff --git a/tests/testlib/akonadifakedata.cpp b/tests/testlib/akonadifakedata.cpp index a268cdfb..dd7b8123 100644 --- a/tests/testlib/akonadifakedata.cpp +++ b/tests/testlib/akonadifakedata.cpp @@ -1,440 +1,480 @@ /* This file is part of Zanshin Copyright 2015 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 "akonadifakedata.h" #include "akonadifakemonitor.h" #include "akonadifakestorage.h" #include #include "akonadi/akonadiapplicationselectedattribute.h" #include using namespace Testlib; template static Akonadi::Collection::Id findParentId(const Entity &entity) { const auto parent = entity.parentCollection(); return parent.isValid() ? parent.id() : Akonadi::Collection::root().id(); } +static const char s_contextListProperty[] = "ContextList"; +static const char s_appName[] = "Zanshin"; + +// Should be in the serializer ideally ... but we don't link to that from here anyway. +static QStringList extractContextUids(const Akonadi::Item &taskItem) +{ + if (!taskItem.hasPayload()) + return {}; + auto todo = taskItem.payload(); + const QString contexts = todo->customProperty(s_appName, s_contextListProperty); + return contexts.split(',', QString::SkipEmptyParts); +} + +// Duplicated from the serializer +static QString contextUid(const Akonadi::Item &contextItem) +{ + auto contextTodo = contextItem.payload(); + return contextTodo->uid(); +} + +// Somewhat duplicated from the serializer +static void removeContextFromTask(const QString &contextUid, Akonadi::Item &item) +{ + auto todo = item.payload(); + const QString contexts = todo->customProperty(s_appName, s_contextListProperty); + QStringList contextList = contexts.split(',', QString::SkipEmptyParts); + contextList.removeAll(contextUid); + if (contextList.isEmpty()) + todo->removeCustomProperty(s_appName, s_contextListProperty); + else + todo->setCustomProperty(s_appName, s_contextListProperty, contextList.join(',')); + item.setPayload(todo); + Q_ASSERT(contextList == extractContextUids(item)); +} + +// Duplicate from the serializer +static bool isContext(const Akonadi::Item &item) +{ + if (!item.hasPayload()) + return false; + + auto todo = item.payload(); + return !todo->customProperty(s_appName, "Context").isEmpty(); +} + AkonadiFakeData::AkonadiFakeData() : m_monitor(new AkonadiFakeMonitor) { } AkonadiFakeData::AkonadiFakeData(const AkonadiFakeData &other) : m_collections(other.m_collections), m_childCollections(other.m_childCollections), m_items(other.m_items), m_childItems(other.m_childItems), m_monitor(new AkonadiFakeMonitor) { } AkonadiFakeData::~AkonadiFakeData() { } AkonadiFakeData &AkonadiFakeData::operator=(const AkonadiFakeData &other) { m_collections = other.m_collections; m_childCollections = other.m_childCollections; m_items = other.m_items; m_childItems = other.m_childItems; return *this; } Akonadi::Collection::List AkonadiFakeData::collections() const { return m_collections.values().toVector(); } Akonadi::Collection::List AkonadiFakeData::childCollections(Akonadi::Collection::Id parentId) const { if (!m_childCollections.contains(parentId)) return {}; const auto ids = m_childCollections.value(parentId); auto result = Akonadi::Collection::List(); std::transform(std::begin(ids), std::end(ids), std::back_inserter(result), [this] (Akonadi::Collection::Id id) { Q_ASSERT(m_collections.contains(id)); return m_collections.value(id); }); return result; } Akonadi::Collection AkonadiFakeData::collection(Akonadi::Collection::Id id) const { if (!m_collections.contains(id)) return Akonadi::Collection(); return m_collections.value(id); } void AkonadiFakeData::createCollection(const Akonadi::Collection &collection) { Q_ASSERT(!m_collections.contains(collection.id())); m_collections[collection.id()] = collection; const auto parentId = findParentId(collection); m_childCollections[parentId] << collection.id(); m_monitor->addCollection(reconstructAncestors(collection)); } void AkonadiFakeData::modifyCollection(const Akonadi::Collection &collection) { Q_ASSERT(m_collections.contains(collection.id())); const auto oldParentId = findParentId(m_collections[collection.id()]); const auto oldCollection = m_collections.take(collection.id()); auto newCollection = collection; newCollection.setRemoteId(oldCollection.remoteId()); if (!newCollection.parentCollection().isValid()) newCollection.setParentCollection(oldCollection.parentCollection()); if (newCollection.name().isEmpty()) newCollection.setName(oldCollection.name()); if (newCollection.contentMimeTypes().isEmpty()) newCollection.setContentMimeTypes(oldCollection.contentMimeTypes()); m_collections[newCollection.id()] = newCollection; const auto parentId = findParentId(newCollection); if (oldParentId != parentId) { m_childCollections[oldParentId].removeAll(newCollection.id()); m_childCollections[parentId] << newCollection.id(); } auto notifiedCollection = reconstructAncestors(newCollection); m_monitor->changeCollection(notifiedCollection); const auto mimeTypes = collection.contentMimeTypes(); if (mimeTypes.contains(KCalCore::Todo::todoMimeType())) { const auto oldAttribute = oldCollection.attribute(); const auto oldSelected = oldAttribute ? oldAttribute->isSelected() : true; const auto newAttribute = newCollection.attribute(); const auto newSelected = newAttribute ? newAttribute->isSelected() : true; if (oldSelected != newSelected) { m_monitor->changeCollectionSelection(notifiedCollection); } } } void AkonadiFakeData::removeCollection(const Akonadi::Collection &collection) { Q_ASSERT(m_collections.contains(collection.id())); const auto childCollections = m_childCollections[collection.id()]; foreach (const auto &childId, childCollections) { removeCollection(Akonadi::Collection(childId)); } m_childCollections.remove(collection.id()); const auto childItems = m_childItems[collection.id()]; foreach (const auto &childId, childItems) { removeItem(Akonadi::Item(childId)); } m_childItems.remove(collection.id()); const auto parentId = findParentId(m_collections[collection.id()]); const auto col = m_collections.take(collection.id()); m_childCollections[parentId].removeAll(collection.id()); m_monitor->removeCollection(col); } -Akonadi::Tag::List AkonadiFakeData::tags() const +QStringList AkonadiFakeData::contextsUids() const { - return m_tags.values().toVector(); + return m_contexts.keys(); } -Akonadi::Tag AkonadiFakeData::tag(Akonadi::Tag::Id id) const +Akonadi::Item::List AkonadiFakeData::contexts() const { - if (!m_tags.contains(id)) - return Akonadi::Tag(); + return m_contexts.values().toVector(); +} - return m_tags.value(id); +Akonadi::Item AkonadiFakeData::contextItem(const QString &uid) const +{ + return m_contexts.value(uid); } -void AkonadiFakeData::createTag(const Akonadi::Tag &tag) +void AkonadiFakeData::createContext(const Akonadi::Item &contextItem) { - Q_ASSERT(!m_tags.contains(tag.id())); - m_tags[tag.id()] = tag; - m_monitor->addTag(tag); + const QString uid = contextUid(contextItem); + Q_ASSERT(!m_contexts.contains(uid)); + m_contexts[uid] = contextItem; + m_monitor->addItem(contextItem); } -void AkonadiFakeData::modifyTag(const Akonadi::Tag &tag) +void AkonadiFakeData::modifyContext(const Akonadi::Item &contextItem) { - Q_ASSERT(m_tags.contains(tag.id())); - const auto oldTag = m_tags.take(tag.id()); - auto newTag = tag; - newTag.setGid(oldTag.gid()); - newTag.setRemoteId(oldTag.remoteId()); - m_tags[tag.id()] = newTag; - m_monitor->changeTag(newTag); + const QString uid = contextUid(contextItem); + Q_ASSERT(m_contexts.contains(uid)); + const auto oldContextItem = m_contexts.take(uid); + auto newItem = contextItem; + newItem.setGid(oldContextItem.gid()); + newItem.setRemoteId(oldContextItem.remoteId()); + m_contexts[uid] = newItem; + m_monitor->changeItem(newItem); } -void AkonadiFakeData::removeTag(const Akonadi::Tag &tag) +void AkonadiFakeData::removeContext(const Akonadi::Item &contextItem) { - Q_ASSERT(m_tags.contains(tag.id())); + const QString uid = contextUid(contextItem); + Q_ASSERT(m_contexts.contains(uid)); - const auto ids = m_tagItems[tag.id()]; + const auto ids = m_contextItems[uid]; foreach (const auto &id, ids) { Q_ASSERT(m_items.contains(id)); auto item = m_items.value(id); - item.clearTag(tag); + removeContextFromTask(uid, item); m_items[id] = item; m_monitor->changeItem(item); } - m_tagItems.remove(tag.id()); + m_contextItems.remove(uid); - m_tags.remove(tag.id()); - m_monitor->removeTag(tag); + m_contexts.remove(uid); + m_monitor->removeItem(contextItem); } Akonadi::Item::List AkonadiFakeData::items() const { return m_items.values().toVector(); } Akonadi::Item::List AkonadiFakeData::childItems(Akonadi::Collection::Id parentId) const { if (!m_childItems.contains(parentId)) return {}; const auto ids = m_childItems.value(parentId); auto result = Akonadi::Item::List(); std::transform(std::begin(ids), std::end(ids), std::back_inserter(result), [this] (Akonadi::Item::Id id) { Q_ASSERT(m_items.contains(id)); return m_items.value(id); }); return result; } -Akonadi::Item::List AkonadiFakeData::tagItems(Akonadi::Tag::Id tagId) const +Akonadi::Item::List AkonadiFakeData::contextItems(const QString &contextUid) const { - if (!m_tagItems.contains(tagId)) - return {}; - - const auto ids = m_tagItems.value(tagId); + const auto ids = m_contextItems.value(contextUid); auto result = Akonadi::Item::List(); + result.reserve(ids.size()); std::transform(std::begin(ids), std::end(ids), std::back_inserter(result), [this] (Akonadi::Item::Id id) { Q_ASSERT(m_items.contains(id)); return m_items.value(id); }); return result; } Akonadi::Item AkonadiFakeData::item(Akonadi::Item::Id id) const { if (!m_items.contains(id)) return {}; return m_items.value(id); } void AkonadiFakeData::createItem(const Akonadi::Item &item) { + Q_ASSERT(item.isValid()); Q_ASSERT(!m_items.contains(item.id())); m_items[item.id()] = item; const auto parentId = findParentId(item); m_childItems[parentId] << item.id(); - foreach (const auto &tag, item.tags()) { - Q_ASSERT(m_tags.contains(tag.id())); - m_tagItems[tag.id()] << item.id(); + const QStringList contextUids = extractContextUids(item); + foreach (const auto &contextUid, contextUids) { + Q_ASSERT(m_contexts.contains(contextUid)); + m_contextItems[contextUid] << item.id(); } - m_monitor->addItem(reconstructItemDependencies(item)); + if (isContext(item)) { + createContext(item); + } else { + m_monitor->addItem(reconstructItemDependencies(item)); + } } void AkonadiFakeData::modifyItem(const Akonadi::Item &item) { Q_ASSERT(m_items.contains(item.id())); + Q_ASSERT(item.isValid()); - const auto oldTags = m_items[item.id()].tags(); + const auto oldContexts = extractContextUids(m_items[item.id()]); + const auto newContexts = extractContextUids(item); const auto oldParentId = findParentId(m_items[item.id()]); const auto oldItem = m_items.take(item.id()); auto newItem = item; newItem.setRemoteId(oldItem.remoteId()); if (!newItem.parentCollection().isValid()) newItem.setParentCollection(oldItem.parentCollection()); m_items[newItem.id()] = newItem; const auto parentId = findParentId(newItem); if (oldParentId != parentId) { m_childItems[oldParentId].removeAll(newItem.id()); m_childItems[parentId] << newItem.id(); m_monitor->moveItem(reconstructItemDependencies(newItem)); } - foreach (const auto &tag, oldTags) { - m_tagItems[tag.id()].removeAll(newItem.id()); + foreach (const auto &contextUid, oldContexts) { + m_contextItems[contextUid].removeAll(newItem.id()); } - foreach (const auto &tag, newItem.tags()) { - Q_ASSERT(m_tags.contains(tag.id())); - m_tagItems[tag.id()] << newItem.id(); + foreach (const auto &contextUid, newContexts) { + Q_ASSERT(m_contexts.contains(contextUid)); + m_contextItems[contextUid] << newItem.id(); } - m_monitor->changeItem(reconstructItemDependencies(newItem)); + if (isContext(item)) { + modifyContext(item); + } else { + m_monitor->changeItem(reconstructItemDependencies(newItem)); + } } void AkonadiFakeData::removeItem(const Akonadi::Item &item) { + Q_ASSERT(item.isValid()); Q_ASSERT(m_items.contains(item.id())); const auto parentId = findParentId(m_items[item.id()]); const auto i = m_items.take(item.id()); m_childItems[parentId].removeAll(item.id()); - foreach (const Akonadi::Tag &tag, item.tags()) { - m_tagItems[tag.id()].removeAll(item.id()); + foreach (const auto &contextUid, extractContextUids(item)) { + m_contextItems[contextUid].removeAll(item.id()); } - m_monitor->removeItem(reconstructItemDependencies(i)); + if (isContext(item)) { + removeContext(item); + } else { + m_monitor->removeItem(reconstructItemDependencies(i)); + } } Akonadi::MonitorInterface *AkonadiFakeData::createMonitor() { auto monitor = new AkonadiFakeMonitor; QObject::connect(m_monitor.data(), &AkonadiFakeMonitor::collectionAdded, monitor, &AkonadiFakeMonitor::addCollection); QObject::connect(m_monitor.data(), &AkonadiFakeMonitor::collectionChanged, monitor, &AkonadiFakeMonitor::changeCollection); QObject::connect(m_monitor.data(), &AkonadiFakeMonitor::collectionSelectionChanged, monitor, &AkonadiFakeMonitor::changeCollectionSelection); QObject::connect(m_monitor.data(), &AkonadiFakeMonitor::collectionRemoved, monitor, &AkonadiFakeMonitor::removeCollection); - QObject::connect(m_monitor.data(), &AkonadiFakeMonitor::tagAdded, - monitor, &AkonadiFakeMonitor::addTag); - QObject::connect(m_monitor.data(), &AkonadiFakeMonitor::tagChanged, - monitor, &AkonadiFakeMonitor::changeTag); - QObject::connect(m_monitor.data(), &AkonadiFakeMonitor::tagRemoved, - monitor, &AkonadiFakeMonitor::removeTag); QObject::connect(m_monitor.data(), &AkonadiFakeMonitor::itemAdded, monitor, &AkonadiFakeMonitor::addItem); QObject::connect(m_monitor.data(), &AkonadiFakeMonitor::itemChanged, monitor, &AkonadiFakeMonitor::changeItem); QObject::connect(m_monitor.data(), &AkonadiFakeMonitor::itemRemoved, monitor, &AkonadiFakeMonitor::removeItem); QObject::connect(m_monitor.data(), &AkonadiFakeMonitor::itemMoved, monitor, &AkonadiFakeMonitor::moveItem); return monitor; } Akonadi::StorageInterface *AkonadiFakeData::createStorage() { return new AkonadiFakeStorage(this); } template bool idLessThan(const T &left, const T &right) { return left.id() < right.id(); } Akonadi::Collection::Id AkonadiFakeData::maxCollectionId() const { if (m_collections.isEmpty()) return 0; auto it = std::max_element(m_collections.constBegin(), m_collections.constEnd(), idLessThan); return it.key(); } Akonadi::Item::Id AkonadiFakeData::maxItemId() const { if (m_items.isEmpty()) return 0; auto it = std::max_element(m_items.constBegin(), m_items.constEnd(), idLessThan); return it.key(); } -Akonadi::Tag::Id AkonadiFakeData::maxTagId() const -{ - if (m_tags.isEmpty()) - return 0; - - auto it = std::max_element(m_tags.constBegin(), m_tags.constEnd(), - idLessThan); - return it.key(); -} - Akonadi::Collection AkonadiFakeData::reconstructAncestors(const Akonadi::Collection &collection, const Akonadi::Collection &root) const { if (!collection.isValid()) return Akonadi::Collection::root(); if (collection == root) return collection; auto parent = collection.parentCollection(); auto reconstructedParent = reconstructAncestors(m_collections.value(parent.id()), root); auto result = collection; result.setParentCollection(reconstructedParent); return result; } Akonadi::Item AkonadiFakeData::reconstructItemDependencies(const Akonadi::Item &item, const Akonadi::Collection &root) const { auto result = item; result.setParentCollection(reconstructAncestors(item.parentCollection(), root)); - - auto tags = item.tags(); - std::transform(tags.constBegin(), tags.constEnd(), - tags.begin(), - [=] (const Akonadi::Tag &t) { - return tag(t.id()); - }); - result.setTags(tags); - return result; } const AkonadiFakeStorageBehavior &AkonadiFakeData::storageBehavior() const { return m_storageBehavior; } AkonadiFakeStorageBehavior &AkonadiFakeData::storageBehavior() { return m_storageBehavior; } diff --git a/tests/testlib/akonadifakedata.h b/tests/testlib/akonadifakedata.h index cbd02536..d8a5d432 100644 --- a/tests/testlib/akonadifakedata.h +++ b/tests/testlib/akonadifakedata.h @@ -1,106 +1,106 @@ /* This file is part of Zanshin Copyright 2015 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 TESTLIB_AKONADIFAKEDATA_H #define TESTLIB_AKONADIFAKEDATA_H #include #include #include -#include #include "testlib/akonadifakestoragebehavior.h" namespace Akonadi { class MonitorInterface; class StorageInterface; } namespace Testlib { class AkonadiFakeMonitor; class AkonadiFakeData { public: AkonadiFakeData(); AkonadiFakeData(const AkonadiFakeData &other); ~AkonadiFakeData(); AkonadiFakeData &operator=(const AkonadiFakeData &other); Akonadi::Collection::List collections() const; Akonadi::Collection::List childCollections(Akonadi::Collection::Id parentId) const; Akonadi::Collection collection(Akonadi::Collection::Id id) const; void createCollection(const Akonadi::Collection &collection); void modifyCollection(const Akonadi::Collection &collection); void removeCollection(const Akonadi::Collection &collection); - Akonadi::Tag::List tags() const; - Akonadi::Tag tag(Akonadi::Tag::Id id) const; - void createTag(const Akonadi::Tag &tag); - void modifyTag(const Akonadi::Tag &tag); - void removeTag(const Akonadi::Tag &tag); + QStringList contextsUids() const; + Akonadi::Item::List contexts() const; + Akonadi::Item contextItem(const QString &uid) const; + void createContext(const Akonadi::Item &item); + void modifyContext(const Akonadi::Item &item); + void removeContext(const Akonadi::Item &item); Akonadi::Item::List items() const; Akonadi::Item::List childItems(Akonadi::Collection::Id parentId) const; - Akonadi::Item::List tagItems(Akonadi::Tag::Id tagId) const; + Akonadi::Item::List contextItems(const QString& contextUid) const; Akonadi::Item item(Akonadi::Item::Id id) const; void createItem(const Akonadi::Item &item); void modifyItem(const Akonadi::Item &item); void removeItem(const Akonadi::Item &item); Akonadi::MonitorInterface *createMonitor(); Akonadi::StorageInterface *createStorage(); Akonadi::Collection::Id maxCollectionId() const; Akonadi::Item::Id maxItemId() const; - Akonadi::Tag::Id maxTagId() const; Akonadi::Collection reconstructAncestors(const Akonadi::Collection &collection, const Akonadi::Collection &root = Akonadi::Collection::root()) const; Akonadi::Item reconstructItemDependencies(const Akonadi::Item &item, const Akonadi::Collection &root = Akonadi::Collection::root()) const; const AkonadiFakeStorageBehavior &storageBehavior() const; AkonadiFakeStorageBehavior &storageBehavior(); private: QHash m_collections; QHash> m_childCollections; - QHash m_tags; + using ContextUid = QString; + QHash m_contexts; QHash m_items; QHash> m_childItems; - QHash> m_tagItems; + QHash> m_contextItems; QScopedPointer m_monitor; AkonadiFakeStorageBehavior m_storageBehavior; }; } #endif // TESTLIB_AKONADIFAKEDATA_H diff --git a/tests/testlib/akonadifakedataxmlloader.cpp b/tests/testlib/akonadifakedataxmlloader.cpp index e3d9baa7..42cfe43c 100644 --- a/tests/testlib/akonadifakedataxmlloader.cpp +++ b/tests/testlib/akonadifakedataxmlloader.cpp @@ -1,104 +1,87 @@ /* This file is part of Zanshin Copyright 2015 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 "akonadifakedataxmlloader.h" #include "akonadifakedata.h" #include #include #include using namespace Testlib; AkonadiFakeDataXmlLoader::AkonadiFakeDataXmlLoader(AkonadiFakeData *data) : m_data(data) { } void AkonadiFakeDataXmlLoader::load(const QString &fileName) const { Akonadi::XmlDocument doc(fileName); Q_ASSERT(doc.isValid()); - Akonadi::Tag::Id tagId = m_data->maxTagId() + 1; Akonadi::Collection::Id collectionId = m_data->maxCollectionId() + 1; Akonadi::Item::Id itemId = m_data->maxItemId() + 1; - QHash tagByRid; - - foreach (const Akonadi::Tag &tag, doc.tags()) { - auto t = tag; - t.setId(tagId++); - m_data->createTag(t); - tagByRid[t.remoteId()] = t; - } - QHash collectionByRid; foreach (const Akonadi::Collection &c, doc.collections()) { collectionByRid[c.remoteId()] = c; } std::function depth = [&depth, &collectionByRid] (const Akonadi::Collection &c) { if (c.parentCollection().remoteId().isEmpty()) { return 0; } auto parent = collectionByRid.value(c.parentCollection().remoteId()); return depth(parent) + 1; }; auto depthLessThan = [&depth] (const Akonadi::Collection &left, const Akonadi::Collection &right) { return depth(left) < depth(right); }; auto collectionsByDepth = doc.collections(); std::sort(collectionsByDepth.begin(), collectionsByDepth.end(), depthLessThan); foreach (const Akonadi::Collection &collection, collectionsByDepth) { auto c = collection; c.setId(collectionId++); collectionByRid[c.remoteId()] = c; c.setParentCollection(collectionByRid.value(c.parentCollection().remoteId())); m_data->createCollection(c); foreach (const Akonadi::Item &item, doc.items(collection)) { auto i = item; i.setId(itemId++); i.setParentCollection(c); i.setModificationTime(QDateTime::currentDateTime()); - auto tags = i.tags(); - std::transform(tags.constBegin(), tags.constEnd(), - tags.begin(), - [&tagByRid] (const Akonadi::Tag &tag) { - return tagByRid.value(tag.remoteId()); - }); - i.setTags(tags); m_data->createItem(i); } } } diff --git a/tests/testlib/akonadifakestorage.cpp b/tests/testlib/akonadifakestorage.cpp index f0732bea..f286ab66 100644 --- a/tests/testlib/akonadifakestorage.cpp +++ b/tests/testlib/akonadifakestorage.cpp @@ -1,499 +1,440 @@ /* This file is part of Zanshin Copyright 2015 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 "akonadifakestorage.h" #include #include "akonadi/akonadistoragesettings.h" #include "akonadifakedata.h" #include "akonadifakejobs.h" #include "utils/jobhandler.h" using namespace Testlib; class AkonadiFakeTransaction : public FakeJob { Q_OBJECT public: explicit AkonadiFakeTransaction() : FakeJob(), m_nextIdx(0) { } private slots: void onTimeout() override { auto jobs = childJobs(); if (m_nextIdx == 0) { const auto it = std::find_if(jobs.constBegin(), jobs.constEnd(), [] (FakeJob *job) { return job->expectedError() != 0; }); if (it != jobs.constEnd()) { setError((*it)->expectedError()); setErrorText((*it)->expectedErrorText()); emitResult(); return; } } if (m_nextIdx >= jobs.size()) { emitResult(); return; } auto job = jobs[m_nextIdx]; connect(job, &KJob::result, this, &AkonadiFakeTransaction::onTimeout); job->start(); m_nextIdx++; } private: QList childJobs() const { QList jobs = findChildren(); jobs.erase(std::remove_if(jobs.begin(), jobs.end(), [this] (FakeJob *job) { return job->parent() != this; }), jobs.end()); return jobs; } int m_nextIdx; }; Utils::JobHandler::StartMode startModeForParent(QObject *parent) { bool isTransaction = qobject_cast(parent); return isTransaction ? Utils::JobHandler::ManualStart : Utils::JobHandler::AutoStart; } void noop() {} AkonadiFakeStorage::AkonadiFakeStorage(AkonadiFakeData *data) : m_data(data) { } Akonadi::Collection AkonadiFakeStorage::defaultCollection() { return Akonadi::StorageSettings::instance().defaultCollection(); } KJob *AkonadiFakeStorage::createItem(Akonadi::Item item, Akonadi::Collection collection) { Q_ASSERT(!item.isValid()); auto job = new FakeJob; if (!m_data->item(item.id()).isValid()) { Utils::JobHandler::install(job, [=] () mutable { item.setId(m_data->maxItemId() + 1); item.setParentCollection(collection); // Force payload detach item.setPayloadFromData(item.payloadData()); m_data->createItem(item); }); } else { job->setExpectedError(1, QStringLiteral("Item already exists")); Utils::JobHandler::install(job, noop); } return job; } KJob *AkonadiFakeStorage::updateItem(Akonadi::Item item, QObject *parent) { auto job = new FakeJob(parent); auto startMode = startModeForParent(parent); if (m_data->item(item.id()).isValid()) { Utils::JobHandler::install(job, [=] () mutable { // Force payload detach item.setPayloadFromData(item.payloadData()); m_data->modifyItem(item); }, startMode); } else { job->setExpectedError(1, QStringLiteral("Item doesn't exist")); Utils::JobHandler::install(job, noop, startMode); } return job; } KJob *AkonadiFakeStorage::removeItem(Akonadi::Item item) { auto job = new FakeJob; if (m_data->item(item.id()).isValid()) { Utils::JobHandler::install(job, [=] { m_data->removeItem(item); }); } else { job->setExpectedError(1, QStringLiteral("Item doesn't exist")); Utils::JobHandler::install(job, noop); } return job; } KJob *AkonadiFakeStorage::removeItems(Akonadi::Item::List items, QObject *parent) { auto job = new FakeJob; auto startMode = startModeForParent(parent); bool allItemsExist = std::all_of(items.constBegin(), items.constEnd(), [=] (const Akonadi::Item &item) { return m_data->item(item.id()).isValid(); }); if (allItemsExist) { Utils::JobHandler::install(job, [=] { foreach (const Akonadi::Item &item, items) { m_data->removeItem(item); } }, startMode); } else { job->setExpectedError(1, QStringLiteral("At least one item doesn't exist")); Utils::JobHandler::install(job, noop, startMode); } return job; } KJob *AkonadiFakeStorage::moveItem(Akonadi::Item item, Akonadi::Collection collection, QObject *parent) { auto job = new FakeJob(parent); auto startMode = startModeForParent(parent); if (m_data->item(item.id()).isValid() && m_data->collection(collection.id()).isValid()) { Utils::JobHandler::install(job, [=] () mutable { item.setParentCollection(collection); // Force payload detach item.setPayloadFromData(item.payloadData()); m_data->modifyItem(item); }, startMode); } else { job->setExpectedError(1, QStringLiteral("The item or the collection doesn't exist")); Utils::JobHandler::install(job, noop, startMode); } return job; } KJob *AkonadiFakeStorage::moveItems(Akonadi::Item::List items, Akonadi::Collection collection, QObject *parent) { using namespace std::placeholders; auto job = new FakeJob(parent); auto startMode = startModeForParent(parent); bool allItemsExist = std::all_of(items.constBegin(), items.constEnd(), [=] (const Akonadi::Item &item) { return m_data->item(item.id()).isValid(); }); if (allItemsExist && m_data->collection(collection.id()).isValid()) { Utils::JobHandler::install(job, [=] () mutable { std::transform(items.constBegin(), items.constEnd(), items.begin(), [=] (const Akonadi::Item &item) { auto result = item; result.setParentCollection(collection); // Force payload detach result.setPayloadFromData(result.payloadData()); return result; }); foreach (const Akonadi::Item &item, items) { m_data->modifyItem(item); } }, startMode); } else { job->setExpectedError(1, QStringLiteral("One of the items or the collection doesn't exist")); Utils::JobHandler::install(job, noop, startMode); } return job; } KJob *AkonadiFakeStorage::createCollection(Akonadi::Collection collection, QObject *parent) { Q_ASSERT(!collection.isValid()); auto job = new FakeJob(parent); auto startMode = startModeForParent(parent); if (!m_data->collection(collection.id()).isValid()) { Utils::JobHandler::install(job, [=] () mutable { collection.setId(m_data->maxCollectionId() + 1); m_data->createCollection(collection); }, startMode); } else { job->setExpectedError(1, QStringLiteral("The collection already exists")); Utils::JobHandler::install(job, noop, startMode); } return job; } KJob *AkonadiFakeStorage::updateCollection(Akonadi::Collection collection, QObject *parent) { auto job = new FakeJob(parent); auto startMode = startModeForParent(parent); if (m_data->collection(collection.id()).isValid()) { Utils::JobHandler::install(job, [=] { m_data->modifyCollection(collection); }, startMode); } else { job->setExpectedError(1, QStringLiteral("The collection doesn't exist")); Utils::JobHandler::install(job, noop, startMode); } return job; } KJob *AkonadiFakeStorage::removeCollection(Akonadi::Collection collection, QObject *parent) { auto job = new FakeJob(parent); auto startMode = startModeForParent(parent); if (m_data->collection(collection.id()).isValid()) { Utils::JobHandler::install(job, [=] { m_data->removeCollection(collection); }, startMode); } else { job->setExpectedError(1, QStringLiteral("The collection doesn't exist")); Utils::JobHandler::install(job, noop, startMode); } return job; } KJob *AkonadiFakeStorage::createTransaction() { auto job = new AkonadiFakeTransaction; Utils::JobHandler::install(job, noop); return job; } -KJob *AkonadiFakeStorage::createTag(Akonadi::Tag tag) -{ - Q_ASSERT(!tag.isValid()); - - auto job = new FakeJob; - if (!m_data->tag(tag.id()).isValid()) { - Utils::JobHandler::install(job, [=] () mutable { - tag.setId(m_data->maxTagId() + 1); - m_data->createTag(tag); - }); - } else { - job->setExpectedError(1, QStringLiteral("The tag already exists")); - Utils::JobHandler::install(job, noop); - } - return job; -} - -KJob *AkonadiFakeStorage::updateTag(Akonadi::Tag tag) -{ - auto job = new FakeJob; - if (m_data->tag(tag.id()).isValid()) { - Utils::JobHandler::install(job, [=] { - m_data->modifyTag(tag); - }); - } else { - job->setExpectedError(1, QStringLiteral("The tag doesn't exist")); - Utils::JobHandler::install(job, noop); - } - return job; -} - -KJob *AkonadiFakeStorage::removeTag(Akonadi::Tag tag) -{ - auto job = new FakeJob; - if (m_data->tag(tag.id()).isValid()) { - Utils::JobHandler::install(job, [=] { - m_data->removeTag(tag); - }); - } else { - job->setExpectedError(1, QStringLiteral("The tag doesn't exist")); - Utils::JobHandler::install(job, noop); - } - return job; -} - Akonadi::CollectionFetchJobInterface *AkonadiFakeStorage::fetchCollections(Akonadi::Collection collection, Akonadi::StorageInterface::FetchDepth depth) { auto job = new AkonadiFakeCollectionFetchJob; auto children = Akonadi::Collection::List(); switch (depth) { case Base: children << m_data->collection(findId(collection)); break; case FirstLevel: children << m_data->childCollections(findId(collection)); break; case Recursive: children = collectChildren(collection); break; } auto collections = children; 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(&AkonadiFakeData::reconstructAncestors, m_data, _1, collection); std::transform(collections.begin(), collections.end(), collections.begin(), completeCollection); } const auto behavior = m_data->storageBehavior().fetchCollectionsBehavior(collection.id()); if (behavior == AkonadiFakeStorageBehavior::NormalFetch) job->setCollections(collections); job->setExpectedError(m_data->storageBehavior().fetchCollectionsErrorCode(collection.id())); Utils::JobHandler::install(job, noop); return job; } Akonadi::ItemFetchJobInterface *AkonadiFakeStorage::fetchItems(Akonadi::Collection collection) { auto items = m_data->childItems(findId(collection)); std::transform(items.begin(), items.end(), items.begin(), [this] (const Akonadi::Item &item) { auto result = m_data->reconstructItemDependencies(item); // Force payload detach result.setPayloadFromData(result.payloadData()); return result; }); auto job = new AkonadiFakeItemFetchJob; const auto behavior = m_data->storageBehavior().fetchItemsBehavior(collection.id()); if (behavior == AkonadiFakeStorageBehavior::NormalFetch) job->setItems(items); job->setExpectedError(m_data->storageBehavior().fetchItemsErrorCode(collection.id())); Utils::JobHandler::install(job, noop); return job; } Akonadi::ItemFetchJobInterface *AkonadiFakeStorage::fetchItem(Akonadi::Item item) { auto fullItem = m_data->item(findId(item)); fullItem = m_data->reconstructItemDependencies(fullItem); // Force payload detach fullItem.setPayloadFromData(fullItem.payloadData()); auto job = new AkonadiFakeItemFetchJob; const auto behavior = m_data->storageBehavior().fetchItemBehavior(item.id()); if (behavior == AkonadiFakeStorageBehavior::NormalFetch) job->setItems(Akonadi::Item::List() << fullItem); job->setExpectedError(m_data->storageBehavior().fetchItemErrorCode(item.id())); Utils::JobHandler::install(job, noop); return job; } Akonadi::ItemFetchJobInterface *AkonadiFakeStorage::fetchTagItems(Akonadi::Tag tag) { - auto items = m_data->tagItems(findId(tag)); + Akonadi::Item::List items; // TODO PORT = m_data->contextItems(findId(tag)); std::transform(items.begin(), items.end(), items.begin(), [this] (const Akonadi::Item &item) { auto collection = m_data->reconstructAncestors(item.parentCollection()); auto result = item; result.setParentCollection(collection); // Force payload detach result.setPayloadFromData(result.payloadData()); return result; }); auto job = new AkonadiFakeItemFetchJob; const auto behavior = m_data->storageBehavior().fetchTagItemsBehavior(tag.id()); if (behavior == AkonadiFakeStorageBehavior::NormalFetch) job->setItems(items); job->setExpectedError(m_data->storageBehavior().fetchTagItemsErrorCode(tag.id())); Utils::JobHandler::install(job, noop); return job; } Akonadi::TagFetchJobInterface *AkonadiFakeStorage::fetchTags() { auto job = new AkonadiFakeTagFetchJob; const auto behavior = m_data->storageBehavior().fetchTagsBehavior(); - if (behavior == AkonadiFakeStorageBehavior::NormalFetch) - job->setTags(m_data->tags()); + //if (behavior == AkonadiFakeStorageBehavior::NormalFetch) + // TODO PORT job->setTags(m_data->contexts()); job->setExpectedError(m_data->storageBehavior().fetchTagsErrorCode()); Utils::JobHandler::install(job, noop); return job; } -Akonadi::Tag::Id AkonadiFakeStorage::findId(const Akonadi::Tag &tag) -{ - if (tag.isValid() || tag.gid().isEmpty()) - return tag.id(); - - const auto gid = tag.gid(); - auto tags = m_data->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::Collection::Id AkonadiFakeStorage::findId(const Akonadi::Collection &collection) { if (collection.isValid() || collection.remoteId().isEmpty()) return collection.id(); const auto remoteId = collection.remoteId(); auto collections = m_data->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::Item::Id AkonadiFakeStorage::findId(const Akonadi::Item &item) { if (item.isValid() || item.remoteId().isEmpty()) return item.id(); const auto remoteId = item.remoteId(); auto items = m_data->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 AkonadiFakeStorage::collectChildren(const Akonadi::Collection &root) { auto collections = Akonadi::Collection::List(); foreach (const auto &child, m_data->childCollections(findId(root))) { if (child.enabled() || child.referenced()) collections << m_data->collection(findId(child)); collections += collectChildren(child); } return collections; } #include "akonadifakestorage.moc" diff --git a/tests/testlib/akonadifakestorage.h b/tests/testlib/akonadifakestorage.h index 6e777457..55367b21 100644 --- a/tests/testlib/akonadifakestorage.h +++ b/tests/testlib/akonadifakestorage.h @@ -1,74 +1,69 @@ /* This file is part of Zanshin Copyright 2015 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 TESTLIB_AKONADIFAKESTORAGE_H #define TESTLIB_AKONADIFAKESTORAGE_H #include "akonadi/akonadistorageinterface.h" namespace Testlib { class AkonadiFakeData; class AkonadiFakeStorage : public Akonadi::StorageInterface { public: explicit AkonadiFakeStorage(AkonadiFakeData *data); Akonadi::Collection defaultCollection() override; KJob *createItem(Akonadi::Item item, Akonadi::Collection collection) override; KJob *updateItem(Akonadi::Item item, QObject *parent = nullptr) override; KJob *removeItem(Akonadi::Item item) override; KJob *removeItems(Akonadi::Item::List items, QObject *parent = nullptr) override; KJob *moveItem(Akonadi::Item item, Akonadi::Collection collection, QObject *parent = nullptr) override; KJob *moveItems(Akonadi::Item::List items, Akonadi::Collection collection, QObject *parent = nullptr) override; KJob *createCollection(Akonadi::Collection collection, QObject *parent = nullptr) override; KJob *updateCollection(Akonadi::Collection collection, QObject *parent = nullptr) override; KJob *removeCollection(Akonadi::Collection collection, QObject *parent = nullptr) override; KJob *createTransaction() override; - KJob *createTag(Akonadi::Tag tag) override; - KJob *updateTag(Akonadi::Tag tag) override; - KJob *removeTag(Akonadi::Tag tag) override; - Akonadi::CollectionFetchJobInterface *fetchCollections(Akonadi::Collection collection, FetchDepth depth) override; Akonadi::ItemFetchJobInterface *fetchItems(Akonadi::Collection collection) override; Akonadi::ItemFetchJobInterface *fetchItem(Akonadi::Item item) override; Akonadi::ItemFetchJobInterface *fetchTagItems(Akonadi::Tag tag) override; Akonadi::TagFetchJobInterface *fetchTags() 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); AkonadiFakeData *m_data; }; } #endif // TESTLIB_AKONADIFAKESTORAGE_H diff --git a/tests/testlib/akonadistoragetestbase.cpp b/tests/testlib/akonadistoragetestbase.cpp index 0beb7b4a..4966901e 100644 --- a/tests/testlib/akonadistoragetestbase.cpp +++ b/tests/testlib/akonadistoragetestbase.cpp @@ -1,1181 +1,915 @@ /* This file is part of Zanshin Copyright 2014-2015 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 "akonadistoragetestbase.h" #include #include #include #include #include #include #include "utils/mem_fn.h" #include "AkonadiCore/qtest_akonadi.h" #include #include "akonadi/akonadiapplicationselectedattribute.h" #include "akonadi/akonadicollectionfetchjobinterface.h" #include "akonadi/akonadiitemfetchjobinterface.h" #include "akonadi/akonadimonitorimpl.h" #include "akonadi/akonadistorage.h" #include "akonadi/akonadistoragesettings.h" #include "akonadi/akonaditagfetchjobinterface.h" #include "akonadi/akonaditimestampattribute.h" using namespace Testlib; AkonadiStorageTestBase::AkonadiStorageTestBase(QObject *parent) : QObject(parent) { qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); } void AkonadiStorageTestBase::cleanupTestCase() { // Give a chance for jobs still waiting for an event loop // run to be deleted through deleteLater() QTest::qWait(10); } void AkonadiStorageTestBase::dumpTree() { TestLib::AkonadiDebug::dumpTree(createStorage()); } void AkonadiStorageTestBase::shouldListCollections_data() { QTest::addColumn("collection"); QTest::addColumn("expectedNames"); QTest::addColumn("depth"); QTest::newRow("all") << Akonadi::Collection::root() << QStringList({ "Calendar1", "Calendar2", "Calendar3", "Change me!", "Destroy me!" }) << Akonadi::Storage::Recursive; QTest::newRow("base type") << calendar2() << QStringList({"Calendar2"}) << Akonadi::Storage::Base; QTest::newRow("firstLevel type") << calendar1() << QStringList({"Calendar2"}) << Akonadi::Storage::FirstLevel; QTest::newRow("recursive type") << calendar1() << QStringList({"Calendar2", "Calendar3"}) << Akonadi::Storage::Recursive; } void AkonadiStorageTestBase::shouldListCollections() { // GIVEN QFETCH(Akonadi::Collection, collection); QFETCH(QStringList, expectedNames); QFETCH(Akonadi::StorageInterface::FetchDepth, depth); auto storage = createStorage(); // WHEN auto job = storage->fetchCollections(collection, depth); AKVERIFYEXEC(job->kjob()); // THEN auto collections = job->collections(); QStringList collectionNames; collectionNames.reserve(collections.size()); foreach (const auto &collection, collections) { collectionNames << collection.name(); } collectionNames.sort(); QCOMPARE(collectionNames, expectedNames); } void AkonadiStorageTestBase::shouldRetrieveAllCollectionAncestors() { // GIVEN auto storage = createStorage(); // WHEN auto job = storage->fetchCollections(Akonadi::Collection::root(), Akonadi::Storage::Recursive); AKVERIFYEXEC(job->kjob()); // THEN auto collections = job->collections(); foreach (const auto &collection, collections) { auto parent = collection.parentCollection(); while (parent != Akonadi::Collection::root()) { QVERIFY(parent.isValid()); QVERIFY(!parent.displayName().isEmpty()); parent = parent.parentCollection(); } } } void AkonadiStorageTestBase::shouldListFullItemsInACollection() { // GIVEN auto storage = createStorage(); const QStringList expectedRemoteIds = { "{1d33862f-f274-4c67-ab6c-362d56521ff4}", "{1d33862f-f274-4c67-ab6c-362d56521ff5}", "{1d33862f-f274-4c67-ab6c-362d56521ff6}", "{7824df00-2fd6-47a4-8319-52659dc82005}", "{7824df00-2fd6-47a4-8319-52659dc82006}", "{d0159c99-0d23-41fa-bb5f-tasktoremove}", }; // WHEN auto job = storage->fetchItems(calendar2()); AKVERIFYEXEC(job->kjob()); // THEN auto items = job->items(); QStringList itemRemoteIds; itemRemoteIds.reserve(items.size()); foreach (const auto &item, items) { itemRemoteIds << item.remoteId(); QVERIFY(item.loadedPayloadParts().contains(Akonadi::Item::FullPayload)); QVERIFY(!item.attributes().isEmpty()); QVERIFY(item.modificationTime().isValid()); QVERIFY(!item.flags().isEmpty()); Akonadi::Tag::List tags = item.tags(); QVERIFY(!item.tags().isEmpty()); foreach (const auto &tag, tags) { QVERIFY(tag.isValid()); QVERIFY(!tag.name().isEmpty()); QVERIFY(!tag.type().isEmpty()); } auto parent = item.parentCollection(); while (parent != Akonadi::Collection::root()) { QVERIFY(parent.isValid()); parent = parent.parentCollection(); } } itemRemoteIds.sort(); QCOMPARE(itemRemoteIds, expectedRemoteIds); } void AkonadiStorageTestBase::shouldListTags() { // GIVEN auto storage = createStorage(); const QStringList expectedGids = { "change-me", "delete-me", "errands-context", "online-context", "philosophy-tag", "physics-tag" }; // WHEN auto job = storage->fetchTags(); AKVERIFYEXEC(job->kjob()); // THEN auto tags = job->tags(); QStringList tagGids; tagGids.reserve(tags.size()); foreach (const auto &tag, tags) { tagGids << tag.gid(); QVERIFY(!tag.name().isEmpty()); QVERIFY(!tag.type().isEmpty()); } tagGids.sort(); QCOMPARE(tagGids, expectedGids); } void AkonadiStorageTestBase::shouldListItemsAssociatedWithTag() { // GIVEN auto storage = createStorage(); Akonadi::Tag tag = fetchTagByGID(QStringLiteral("errands-context")); const QStringList expectedRemoteIds = { "{1d33862f-f274-4c67-ab6c-362d56521ff4}", "{7824df00-2fd6-47a4-8319-52659dc82005}" }; // WHEN auto job = storage->fetchTagItems(tag); AKVERIFYEXEC(job->kjob()); // THEN auto items = job->items(); QStringList itemRemoteIds; itemRemoteIds.reserve(items.size()); foreach (const auto &item, items) { itemRemoteIds << item.remoteId(); QVERIFY(item.loadedPayloadParts().contains(Akonadi::Item::FullPayload)); QVERIFY(!item.attributes().isEmpty()); QVERIFY(item.modificationTime().isValid()); QVERIFY(!item.flags().isEmpty()); auto parent = item.parentCollection(); while (parent != Akonadi::Collection::root()) { QVERIFY(parent.isValid()); parent = parent.parentCollection(); } } itemRemoteIds.sort(); QCOMPARE(itemRemoteIds, expectedRemoteIds); } void AkonadiStorageTestBase::shouldNotifyCollectionAdded() { // GIVEN // A spied monitor auto monitor = createMonitor(); QSignalSpy spy(monitor.data(), &Akonadi::MonitorInterface::collectionAdded); MonitorSpy monitorSpy(monitor.data()); // A collection Akonadi::Collection collection; collection.setParentCollection(calendar2()); collection.setName(QStringLiteral("Foo!")); collection.setContentMimeTypes(QStringList() << QStringLiteral("application/x-vnd.akonadi.calendar.todo")); // WHEN auto storage = createStorage(); auto job = storage->createCollection(collection); AKVERIFYEXEC(job); monitorSpy.waitForStableState(); QTRY_VERIFY(!spy.isEmpty()); // THEN QCOMPARE(spy.size(), 1); auto notifiedCollection = spy.takeFirst().at(0).value(); QCOMPARE(notifiedCollection.name(), collection.name()); auto parent = notifiedCollection.parentCollection(); while (parent != Akonadi::Collection::root()) { QVERIFY(parent.isValid()); parent = parent.parentCollection(); } } void AkonadiStorageTestBase::shouldNotifyCollectionRemoved() { // GIVEN // A spied monitor auto monitor = createMonitor(); QSignalSpy spy(monitor.data(), &Akonadi::MonitorInterface::collectionRemoved); MonitorSpy monitorSpy(monitor.data()); // An existing item (if we trust the test data) Akonadi::Collection collection = fetchCollectionByRID(QStringLiteral("{1f78b360-a01b-4785-9187-75450190342c}")); QVERIFY(collection.isValid()); // WHEN auto storage = createStorage(); auto job = storage->removeCollection(collection); AKVERIFYEXEC(job); monitorSpy.waitForStableState(); QTRY_VERIFY(!spy.isEmpty()); // THEN QCOMPARE(spy.size(), 1); auto notifiedCollection= spy.takeFirst().at(0).value(); QCOMPARE(notifiedCollection.id(), collection.id()); } void AkonadiStorageTestBase::shouldNotifyCollectionChanged() { // GIVEN // A spied monitor auto monitor = createMonitor(); QSignalSpy spy(monitor.data(), &Akonadi::MonitorInterface::collectionChanged); MonitorSpy monitorSpy(monitor.data()); // A collection with an existing id (if we trust the test data) Akonadi::Collection collection = fetchCollectionByRID(QStringLiteral("{28ef9f03-4ebc-4e33-970f-f379775894f9}")); QVERIFY(collection.isValid()); collection.setName(QStringLiteral("Bar!")); // WHEN auto storage = createStorage(); auto job = storage->updateCollection(collection); AKVERIFYEXEC(job); monitorSpy.waitForStableState(); QTRY_VERIFY(!spy.isEmpty()); // THEN QCOMPARE(spy.size(), 1); auto notifiedCollection = spy.takeFirst().at(0).value(); QCOMPARE(notifiedCollection.id(), collection.id()); QCOMPARE(notifiedCollection.name(), collection.name()); auto parent = notifiedCollection.parentCollection(); while (parent != Akonadi::Collection::root()) { QVERIFY(parent.isValid()); parent = parent.parentCollection(); } } void AkonadiStorageTestBase::shouldNotifyItemAdded() { // GIVEN // A spied monitor auto monitor = createMonitor(); QSignalSpy spy(monitor.data(), &Akonadi::MonitorInterface::itemAdded); MonitorSpy monitorSpy(monitor.data()); // A todo... KCalCore::Todo::Ptr todo(new KCalCore::Todo); todo->setSummary(QStringLiteral("summary")); todo->setDescription(QStringLiteral("content")); todo->setCompleted(false); todo->setDtStart(QDateTime(QDate(2013, 11, 24))); todo->setDtDue(QDateTime(QDate(2014, 03, 01))); // ... as payload of an item... Akonadi::Item item; item.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); item.setPayload(todo); item.addAttribute(new Akonadi::EntityDisplayAttribute); // WHEN auto storage = createStorage(); auto job = storage->createItem(item, calendar2()); AKVERIFYEXEC(job); monitorSpy.waitForStableState(); QTRY_VERIFY(!spy.isEmpty()); // THEN QCOMPARE(spy.size(), 1); auto notifiedItem = spy.takeFirst().at(0).value(); QCOMPARE(*notifiedItem.payload(), *todo); QVERIFY(notifiedItem.hasAttribute()); auto parent = notifiedItem.parentCollection(); while (parent != Akonadi::Collection::root()) { QVERIFY(parent.isValid()); parent = parent.parentCollection(); } } void AkonadiStorageTestBase::shouldNotifyItemRemoved() { // GIVEN // A spied monitor auto monitor = createMonitor(); QSignalSpy spy(monitor.data(), &Akonadi::MonitorInterface::itemRemoved); MonitorSpy monitorSpy(monitor.data()); Akonadi::Item item = fetchItemByRID(QStringLiteral("{d0159c99-0d23-41fa-bb5f-tasktoremove}"), calendar2()); QVERIFY(item.isValid()); // WHEN auto storage = createStorage(); auto job = storage->removeItem(item); AKVERIFYEXEC(job); monitorSpy.waitForStableState(); QTRY_VERIFY(!spy.isEmpty()); // THEN QCOMPARE(spy.size(), 1); auto notifiedItem = spy.takeFirst().at(0).value(); QCOMPARE(notifiedItem.id(), item.id()); auto parent = notifiedItem.parentCollection(); while (parent != Akonadi::Collection::root()) { QVERIFY(parent.isValid()); parent = parent.parentCollection(); } } void AkonadiStorageTestBase::shouldNotifyItemChanged() { // GIVEN // A spied monitor auto monitor = createMonitor(); QSignalSpy spy(monitor.data(), &Akonadi::MonitorInterface::itemChanged); MonitorSpy monitorSpy(monitor.data()); // A todo... KCalCore::Todo::Ptr todo(new KCalCore::Todo); todo->setSummary(QStringLiteral("summary")); todo->setDescription(QStringLiteral("content")); todo->setCompleted(false); todo->setDtStart(QDateTime(QDate(2013, 11, 24))); todo->setDtDue(QDateTime(QDate(2014, 03, 01))); // ... as payload of an existing item (if we trust the test data)... Akonadi::Item item = fetchItemByRID(QStringLiteral("{1d33862f-f274-4c67-ab6c-362d56521ff6}"), calendar2()); QVERIFY(item.isValid()); item.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); item.setPayload(todo); item.addAttribute(new Akonadi::EntityDisplayAttribute); // WHEN auto storage = createStorage(); auto job = storage->updateItem(item); AKVERIFYEXEC(job); monitorSpy.waitForStableState(); QTRY_VERIFY(!spy.isEmpty()); // THEN QCOMPARE(spy.size(), 1); auto notifiedItem = spy.takeFirst().at(0).value(); QCOMPARE(notifiedItem.id(), item.id()); QCOMPARE(*notifiedItem.payload(), *todo); QVERIFY(notifiedItem.hasAttribute()); auto parent = notifiedItem.parentCollection(); while (parent != Akonadi::Collection::root()) { QVERIFY(parent.isValid()); parent = parent.parentCollection(); } } -void AkonadiStorageTestBase::shouldNotifyItemTagAdded() -{ - // GIVEN - - // A spied monitor - auto monitor = createMonitor(); - QSignalSpy spy(monitor.data(), &Akonadi::MonitorInterface::itemChanged); - MonitorSpy monitorSpy(monitor.data()); - - // An existing item (if we trust the test data)... - Akonadi::Item item = fetchItemByRID(QStringLiteral("{1d33862f-f274-4c67-ab6c-362d56521ff5}"), calendar2()); - QVERIFY(item.isValid()); - item.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); - - // An existing tag (if we trust the test data) - Akonadi::Tag tag(5); - - // WHEN - item.setTag(tag); - auto storage = createStorage(); - auto job = storage->updateItem(item); - AKVERIFYEXEC(job); - monitorSpy.waitForStableState(); - QTRY_VERIFY(!spy.isEmpty()); - - // THEN - QCOMPARE(spy.size(), 1); - auto notifiedItem = spy.takeFirst().at(0).value(); - QCOMPARE(notifiedItem.id(), item.id()); - QVERIFY(notifiedItem.hasPayload()); - - Akonadi::Tag::List notifiedTags = notifiedItem.tags(); - - QVERIFY(notifiedTags.contains(tag)); - foreach (const auto &tag, notifiedTags) { - QVERIFY(tag.isValid()); - QVERIFY(!tag.name().isEmpty()); - QVERIFY(!tag.type().isEmpty()); - } - - auto parent = notifiedItem.parentCollection(); - while (parent != Akonadi::Collection::root()) { - QVERIFY(parent.isValid()); - parent = parent.parentCollection(); - } -} - -void AkonadiStorageTestBase::shouldNotifyItemTagRemoved() // aka dissociate -{ - // GIVEN - auto storage = createStorage(); - Akonadi::Tag tag = fetchTagByGID(QStringLiteral("philosophy-tag")); - const QString expectedRemoteIds = {QStringLiteral("{7824df00-2fd6-47a4-8319-52659dc82006}")}; - auto job = storage->fetchTagItems(tag); - AKVERIFYEXEC(job->kjob()); - - auto item = job->items().at(0); - QCOMPARE(item.remoteId(), expectedRemoteIds); - - QVERIFY(item.loadedPayloadParts().contains(Akonadi::Item::FullPayload)); - QVERIFY(!item.attributes().isEmpty()); - QVERIFY(item.modificationTime().isValid()); - QVERIFY(!item.flags().isEmpty()); - QVERIFY(!item.tags().isEmpty()); - - // A spied monitor - auto monitor = createMonitor(); - QSignalSpy spy(monitor.data(), &Akonadi::MonitorInterface::itemChanged); - MonitorSpy monitorSpy(monitor.data()); - - // WHEN - item.clearTag(tag); - auto jobUpdate = storage->updateItem(item); - AKVERIFYEXEC(jobUpdate); - monitorSpy.waitForStableState(); - QTRY_VERIFY(!spy.isEmpty()); - - // THEN - QCOMPARE(spy.size(), 1); - auto notifiedItem = spy.takeFirst().at(0).value(); - QCOMPARE(notifiedItem.id(), item.id()); - QVERIFY(!notifiedItem.tags().contains(tag)); -} - -void AkonadiStorageTestBase::shouldNotifyTagAdded() -{ - // GIVEN - - // A spied monitor - auto monitor = createMonitor(); - QSignalSpy spy(monitor.data(), &Akonadi::MonitorInterface::tagAdded); - MonitorSpy monitorSpy(monitor.data()); - - // A tag - Akonadi::Tag tag; - tag.setGid("gid"); - tag.setName(QStringLiteral("name")); - tag.setType("type"); - - // WHEN - auto storage = createStorage(); - auto job = storage->createTag(tag); - AKVERIFYEXEC(job); - monitorSpy.waitForStableState(); - QTRY_VERIFY(!spy.isEmpty()); - - // THEN - QCOMPARE(spy.size(), 1); - auto notifiedTag = spy.takeFirst().at(0).value(); - QCOMPARE(notifiedTag.gid(), tag.gid()); - QCOMPARE(notifiedTag.name(), tag.name()); - QCOMPARE(notifiedTag.type(), tag.type()); -} - -void AkonadiStorageTestBase::shouldNotifyTagRemoved() -{ - // GIVEN - - // An existing tag (if we trust the test data) connected to an existing item tagged to it - auto storage = createStorage(); - Akonadi::Tag tag = fetchTagByGID(QStringLiteral("delete-me")); - // NOTE : this item was linked to the delete-me tag during test time - const QString expectedRemoteIds = {QStringLiteral("{1d33862f-f274-4c67-ab6c-362d56521ff5}")}; - auto job = storage->fetchTagItems(tag); - AKVERIFYEXEC(job->kjob()); - - QCOMPARE(job->items().size(), 1); - auto itemTagged = job->items().at(0); - QCOMPARE(itemTagged.remoteId(), expectedRemoteIds); - - // A spied monitor - auto monitor = createMonitor(); - QSignalSpy spy(monitor.data(), &Akonadi::MonitorInterface::tagRemoved); - QSignalSpy spyItemChanged(monitor.data(), &Akonadi::MonitorInterface::itemChanged); - MonitorSpy monitorSpy(monitor.data()); - - // WHEN - auto jobDelete = storage->removeTag(tag); - AKVERIFYEXEC(jobDelete); - monitorSpy.waitForStableState(); - QTRY_VERIFY(!spy.isEmpty()); - QTRY_VERIFY(!spyItemChanged.isEmpty()); - - // THEN - QCOMPARE(spy.size(), 1); - auto notifiedTag = spy.takeFirst().at(0).value(); - QCOMPARE(notifiedTag.id(), tag.id()); - - QCOMPARE(spyItemChanged.size(), 1); - auto notifiedItem = spyItemChanged.takeFirst().at(0).value(); - QCOMPARE(notifiedItem.id(), itemTagged.id()); -} - -void AkonadiStorageTestBase::shouldNotifyTagChanged() -{ - // GIVEN - - // A spied monitor - auto monitor = createMonitor(); - QSignalSpy spy(monitor.data(), &Akonadi::MonitorInterface::tagChanged); - MonitorSpy monitorSpy(monitor.data()); - - // An existing tag (if we trust the test data) - Akonadi::Tag tag(6); - tag.setName(QStringLiteral("Oh it changed!")); - - // WHEN - auto storage = createStorage(); - auto job = storage->updateTag(tag); - AKVERIFYEXEC(job); - monitorSpy.waitForStableState(); - QTRY_VERIFY(!spy.isEmpty()); - - // THEN - QCOMPARE(spy.size(), 1); - auto notifiedTag = spy.takeFirst().at(0).value(); - QCOMPARE(notifiedTag.id(), tag.id()); - QCOMPARE(notifiedTag.name(), tag.name()); -} - void AkonadiStorageTestBase::shouldReadDefaultCollectionFromSettings() { // GIVEN // A storage implementation auto storage = createStorage(); // WHEN Akonadi::StorageSettings::instance().setDefaultCollection(Akonadi::Collection(24)); // THEN QCOMPARE(storage->defaultCollection(), Akonadi::Collection(24)); } void AkonadiStorageTestBase::shouldUpdateItem() { // GIVEN // A storage implementation auto storage = createStorage(); // A spied monitor auto monitor = createMonitor(); QSignalSpy spy(monitor.data(), &Akonadi::MonitorInterface::itemChanged); MonitorSpy monitorSpy(monitor.data()); // A todo... KCalCore::Todo::Ptr todo(new KCalCore::Todo); todo->setSummary(QStringLiteral("new summary")); todo->setDescription(QStringLiteral("new content")); // ... as payload of an existing item (if we trust the test data)... Akonadi::Item item = fetchItemByRID(QStringLiteral("{1d33862f-f274-4c67-ab6c-362d56521ff4}"), calendar2()); item.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); item.setPayload(todo); // WHEN auto job = storage->updateItem(item); AKVERIFYEXEC(job); monitorSpy.waitForStableState(); QTRY_VERIFY(!spy.isEmpty()); // THEN QCOMPARE(spy.size(), 1); auto notifiedItem = spy.takeFirst().at(0).value(); QCOMPARE(notifiedItem.id(), item.id()); QCOMPARE(*notifiedItem.payload(), *todo); } void AkonadiStorageTestBase::shouldUseTransaction() { // GIVEN auto storage = createStorage(); Akonadi::Item item1 = fetchItemByRID(QStringLiteral("{0aa4dc30-a2c2-4e08-8241-033b3344debc}"), calendar1()); QVERIFY(item1.isValid()); Akonadi::Item item2 = fetchItemByRID(QStringLiteral("{5dc1aba7-eead-4254-ba7a-58e397de1179}"), calendar1()); QVERIFY(item2.isValid()); // create wrong item Akonadi::Item item3(10000); item3.setRemoteId(QStringLiteral("wrongId")); // A spied monitor auto monitor = createMonitor(); QSignalSpy spyUpdated(monitor.data(), &Akonadi::MonitorInterface::itemChanged); MonitorSpy monitorSpy(monitor.data()); // WHEN auto todo = item1.payload(); todo->setSummary(QStringLiteral("Buy tomatoes")); todo = item2.payload(); todo->setSummary(QStringLiteral("Buy chocolate")); auto transaction = storage->createTransaction(); storage->updateItem(item1, transaction); storage->updateItem(item3, transaction); // this job should fail storage->updateItem(item2, transaction); QVERIFY(!transaction->exec()); monitorSpy.waitForStableState(); // THEN QCOMPARE(spyUpdated.size(), 0); auto job = storage->fetchItem(item1); AKVERIFYEXEC(job->kjob()); QCOMPARE(job->items().size(), 1); item1 = job->items().at(0); job = storage->fetchItem(item2); AKVERIFYEXEC(job->kjob()); QCOMPARE(job->items().size(), 1); item2 = job->items().at(0); QCOMPARE(item1.payload()->summary(), QStringLiteral("Buy kiwis")); QCOMPARE(item2.payload()->summary(), QStringLiteral("Buy cheese")); } void AkonadiStorageTestBase::shouldCreateItem() { // GIVEN // A storage implementation auto storage = createStorage(); // A spied monitor auto monitor = createMonitor(); QSignalSpy spy(monitor.data(), &Akonadi::MonitorInterface::itemAdded); MonitorSpy monitorSpy(monitor.data()); // A todo... KCalCore::Todo::Ptr todo(new KCalCore::Todo); todo->setSummary(QStringLiteral("summary")); todo->setDescription(QStringLiteral("content")); todo->setCompleted(false); todo->setDtStart(QDateTime(QDate(2013, 11, 24))); todo->setDtDue(QDateTime(QDate(2014, 03, 01))); // ... as payload of a new item Akonadi::Item item; item.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); item.setPayload(todo); // WHEN auto job = storage->createItem(item, calendar2()); AKVERIFYEXEC(job); monitorSpy.waitForStableState(); QTRY_VERIFY(!spy.isEmpty()); // THEN QCOMPARE(spy.size(), 1); auto notifiedItem = spy.takeFirst().at(0).value(); QCOMPARE(notifiedItem.parentCollection(), calendar2()); QCOMPARE(*notifiedItem.payload(), *todo); } void AkonadiStorageTestBase::shouldRetrieveItem() { // GIVEN auto storage = createStorage(); Akonadi::Item findItem = fetchItemByRID(QStringLiteral("{7824df00-2fd6-47a4-8319-52659dc82005}"), calendar2()); QVERIFY(findItem.isValid()); // WHEN auto job = storage->fetchItem(findItem); AKVERIFYEXEC(job->kjob()); // THEN auto items = job->items(); QCOMPARE(items.size(), 1); const auto &item = items[0]; QCOMPARE(item.id(), findItem.id()); QVERIFY(item.loadedPayloadParts().contains(Akonadi::Item::FullPayload)); QVERIFY(!item.attributes().isEmpty()); QVERIFY(item.modificationTime().isValid()); QVERIFY(!item.flags().isEmpty()); auto parent = item.parentCollection(); while (parent != Akonadi::Collection::root()) { QVERIFY(parent.isValid()); parent = parent.parentCollection(); } } void AkonadiStorageTestBase::shouldMoveItem() { // GIVEN auto storage = createStorage(); Akonadi::Item item = fetchItemByRID(QStringLiteral("{7824df00-2fd6-47a4-8319-52659dc82005}"), calendar2()); QVERIFY(item.isValid()); // A spied monitor auto monitor = createMonitor(); QSignalSpy spyMoved(monitor.data(), &Akonadi::MonitorInterface::itemMoved); MonitorSpy monitorSpy(monitor.data()); auto job = storage->moveItem(item, calendar1()); AKVERIFYEXEC(job); monitorSpy.waitForStableState(); QTRY_VERIFY(!spyMoved.isEmpty()); QCOMPARE(spyMoved.size(), 1); auto movedItem = spyMoved.takeFirst().at(0).value(); QCOMPARE(movedItem.id(), item.id()); } void AkonadiStorageTestBase::shouldMoveItems() { // GIVEN auto storage = createStorage(); Akonadi::Item item = fetchItemByRID(QStringLiteral("{1d33862f-f274-4c67-ab6c-362d56521ff4}"), calendar2()); QVERIFY(item.isValid()); Akonadi::Item::List list; list << item; // A spied monitor auto monitor = createMonitor(); QSignalSpy spyMoved(monitor.data(), &Akonadi::MonitorInterface::itemMoved); MonitorSpy monitorSpy(monitor.data()); auto job = storage->moveItems(list, calendar1()); AKVERIFYEXEC(job); monitorSpy.waitForStableState(); QTRY_VERIFY(!spyMoved.isEmpty()); QCOMPARE(spyMoved.size(), 1); auto movedItem = spyMoved.takeFirst().at(0).value(); QCOMPARE(movedItem.id(), item.id()); } void AkonadiStorageTestBase::shouldDeleteItem() { //GIVEN auto storage = createStorage(); // A spied monitor auto monitor = createMonitor(); QSignalSpy spy(monitor.data(), &Akonadi::MonitorInterface::itemRemoved); MonitorSpy monitorSpy(monitor.data()); // An existing item (if we trust the test data) Akonadi::Item item = fetchItemByRID(QStringLiteral("{0aa4dc30-a2c2-4e08-8241-033b3344debc}"), calendar1()); QVERIFY(item.isValid()); //When auto job = storage->removeItem(item); AKVERIFYEXEC(job); monitorSpy.waitForStableState(); QTRY_VERIFY(!spy.isEmpty()); // THEN QCOMPARE(spy.size(), 1); auto notifiedItem = spy.takeFirst().at(0).value(); QCOMPARE(notifiedItem.id(), item.id()); } void AkonadiStorageTestBase::shouldDeleteItems() { //GIVEN auto storage = createStorage(); // A spied monitor auto monitor = createMonitor(); QSignalSpy spy(monitor.data(), &Akonadi::MonitorInterface::itemRemoved); MonitorSpy monitorSpy(monitor.data()); // An existing item (if we trust the test data) Akonadi::Item item = fetchItemByRID(QStringLiteral("{6c7bf5b9-4136-4203-9f45-54e32ea0eacb}"), calendar1()); QVERIFY(item.isValid()); Akonadi::Item item2 = fetchItemByRID(QStringLiteral("{83cf0b15-8d61-436b-97ae-4bd88fb2fef9}"), calendar1()); QVERIFY(item2.isValid()); Akonadi::Item::List list; list << item << item2; //When auto job = storage->removeItems(list); AKVERIFYEXEC(job); monitorSpy.waitForStableState(); QTRY_VERIFY(!spy.isEmpty()); // THEN QCOMPARE(spy.size(), 2); auto notifiedItem = spy.takeFirst().at(0).value(); QCOMPARE(notifiedItem.id(), item.id()); notifiedItem = spy.takeFirst().at(0).value(); QCOMPARE(notifiedItem.id(), item2.id()); } -void AkonadiStorageTestBase::shouldCreateTag() -{ - // GIVEN - - // A storage implementation - auto storage = createStorage(); - - // A spied monitor - auto monitor = createMonitor(); - QSignalSpy spy(monitor.data(), &Akonadi::MonitorInterface::tagAdded); - MonitorSpy monitorSpy(monitor.data()); - - // A tag - Akonadi::Tag tag; - QString name = QStringLiteral("Tag42"); - const QByteArray type = QByteArray("Zanshin-Context"); - const QByteArray gid = QByteArray(name.toLatin1()); - tag.setName(name); - tag.setType(QByteArray("Zanshin-Context")); - tag.setGid(gid); - - // WHEN - auto job = storage->createTag(tag); - AKVERIFYEXEC(job); - monitorSpy.waitForStableState(); - QTRY_VERIFY(!spy.isEmpty()); - - // THEN - QCOMPARE(spy.size(), 1); - auto notifiedTag = spy.takeFirst().at(0).value(); - QCOMPARE(notifiedTag.name(), name); - QCOMPARE(notifiedTag.type(), type); - QCOMPARE(notifiedTag.gid(), gid); -} - -void AkonadiStorageTestBase::shouldRemoveTag() -{ - - // GIVEN - auto storage = createStorage(); - - // A spied monitor - auto monitor = createMonitor(); - QSignalSpy spy(monitor.data(), &Akonadi::MonitorInterface::tagRemoved); - MonitorSpy monitorSpy(monitor.data()); - - // An existing tag - Akonadi::Tag tag = fetchTagByGID(QStringLiteral("errands-context")); - - // WHEN - auto job = storage->removeTag(tag); - AKVERIFYEXEC(job); - monitorSpy.waitForStableState(); - QTRY_VERIFY(!spy.isEmpty()); - - // THEN - QCOMPARE(spy.size(), 1); - auto notifiedTag = spy.takeFirst().at(0).value(); - QCOMPARE(notifiedTag.id(), tag.id()); -} - -void AkonadiStorageTestBase::shouldUpdateTag() -{ - // GIVEN - auto storage = createStorage(); - - // A spied monitor - auto monitor = createMonitor(); - QSignalSpy spy(monitor.data(), &Akonadi::MonitorInterface::tagChanged); - MonitorSpy monitorSpy(monitor.data()); - - // An existing tag - Akonadi::Tag tag = fetchTagByGID(QStringLiteral("change-me")); - - // WHEN - auto job = storage->updateTag(tag); - AKVERIFYEXEC(job); - monitorSpy.waitForStableState(); - QTRY_VERIFY(!spy.isEmpty()); - - // THEN - QCOMPARE(spy.size(), 1); - auto notifiedTag = spy.takeFirst().at(0).value(); - QCOMPARE(notifiedTag.id(), tag.id()); -} - void AkonadiStorageTestBase::shouldUpdateCollection() { // GIVEN // A storage implementation auto storage = createStorage(); // An existing collection Akonadi::Collection collection = calendar2(); // A spied monitor auto monitor = createMonitor(); QSignalSpy changeSpy(monitor.data(), &Akonadi::MonitorInterface::collectionChanged); QSignalSpy selectionSpy(monitor.data(), &Akonadi::MonitorInterface::collectionSelectionChanged); MonitorSpy monitorSpy(monitor.data()); // WHEN auto attr = new Akonadi::EntityDisplayAttribute; attr->setDisplayName(QStringLiteral("Foo")); collection.addAttribute(attr); auto job = storage->updateCollection(collection); AKVERIFYEXEC(job); monitorSpy.waitForStableState(); QTRY_VERIFY(!changeSpy.isEmpty()); // THEN QCOMPARE(changeSpy.size(), 1); QCOMPARE(selectionSpy.size(), 0); auto notifiedCollection = changeSpy.takeFirst().at(0).value(); QCOMPARE(notifiedCollection.id(), collection.id()); QVERIFY(notifiedCollection.hasAttribute()); QCOMPARE(notifiedCollection.attribute()->displayName(), attr->displayName()); } void AkonadiStorageTestBase::shouldNotifyCollectionTimestampChanges() { // GIVEN // A storage implementation auto storage = createStorage(); // An existing collection Akonadi::Collection collection = calendar2(); // A spied monitor auto monitor = createMonitor(); QSignalSpy changeSpy(monitor.data(), &Akonadi::MonitorInterface::collectionChanged); MonitorSpy monitorSpy(monitor.data()); // WHEN collection.attribute(Akonadi::Collection::AddIfMissing)->refreshTimestamp(); auto job = storage->updateCollection(collection); AKVERIFYEXEC(job); monitorSpy.waitForStableState(); QTRY_VERIFY(!changeSpy.isEmpty()); // THEN QCOMPARE(changeSpy.size(), 1); auto notifiedCollection = changeSpy.takeFirst().at(0).value(); QCOMPARE(notifiedCollection.id(), collection.id()); QVERIFY(notifiedCollection.hasAttribute()); } void AkonadiStorageTestBase::shouldNotifyCollectionSelectionChanges() { // GIVEN // A storage implementation auto storage = createStorage(); // An existing collection Akonadi::Collection collection = calendar2(); // A spied monitor auto monitor = createMonitor(); QSignalSpy changeSpy(monitor.data(), &Akonadi::MonitorInterface::collectionChanged); QSignalSpy selectionSpy(monitor.data(), &Akonadi::MonitorInterface::collectionSelectionChanged); MonitorSpy monitorSpy(monitor.data()); // WHEN auto attr = new Akonadi::ApplicationSelectedAttribute; attr->setSelected(false); collection.addAttribute(attr); auto job = storage->updateCollection(collection); AKVERIFYEXEC(job); monitorSpy.waitForStableState(); QTRY_VERIFY(!changeSpy.isEmpty()); // THEN QCOMPARE(changeSpy.size(), 1); QCOMPARE(selectionSpy.size(), 1); auto notifiedCollection = changeSpy.takeFirst().at(0).value(); QCOMPARE(notifiedCollection.id(), collection.id()); QVERIFY(notifiedCollection.hasAttribute()); QVERIFY(!notifiedCollection.attribute()->isSelected()); notifiedCollection = selectionSpy.takeFirst().at(0).value(); QCOMPARE(notifiedCollection.id(), collection.id()); QVERIFY(notifiedCollection.hasAttribute()); QVERIFY(!notifiedCollection.attribute()->isSelected()); } Akonadi::Item AkonadiStorageTestBase::fetchItemByRID(const QString &remoteId, const Akonadi::Collection &collection) { Akonadi::Item item; item.setRemoteId(remoteId); auto job = createStorage()->fetchItem(item); job->setCollection(collection); if (!job->kjob()->exec()) { qWarning() << job->kjob()->errorString(); return Akonadi::Item(); } if (job->items().count() != 1) { qWarning() << "Received unexpected amount of items: " << job->items().count(); return Akonadi::Item(); } return job->items().at(0); } Akonadi::Collection AkonadiStorageTestBase::fetchCollectionByRID(const QString &remoteId) { Akonadi::Collection collection; collection.setRemoteId(remoteId); auto job = createStorage()->fetchCollections(collection, Akonadi::StorageInterface::Base); job->setResource(QStringLiteral("akonadi_knut_resource_0")); if (!job->kjob()->exec()) { qWarning() << job->kjob()->errorString() << remoteId; return Akonadi::Collection(); } if (job->collections().count() != 1) { qWarning() << "Received unexpected amount of collections: " << job->collections().count(); return Akonadi::Collection(); } return job->collections().at(0); } Akonadi::Tag AkonadiStorageTestBase::fetchTagByGID(const QString &gid) { auto job = createStorage()->fetchTags(); if (!job->kjob()->exec()) { qWarning() << job->kjob()->errorString(); return Akonadi::Tag(); } auto tags = job->tags(); foreach (const auto &tag, tags) { if (tag.gid() == gid) return tag; } return Akonadi::Tag(); } Akonadi::Collection AkonadiStorageTestBase::calendar1() { return fetchCollectionByRID(QStringLiteral("{cdc229c7-a9b5-4d37-989d-a28e372be2a9}")); } Akonadi::Collection AkonadiStorageTestBase::calendar2() { return fetchCollectionByRID(QStringLiteral("{e682b8b5-b67c-4538-8689-6166f64177f0}")); } Akonadi::Collection AkonadiStorageTestBase::emails() { return fetchCollectionByRID(QStringLiteral("{14096930-7bfe-46ca-8fba-7c04d3b62ec8}")); } diff --git a/tests/testlib/akonadistoragetestbase.h b/tests/testlib/akonadistoragetestbase.h index 89e0159d..42c51b30 100644 --- a/tests/testlib/akonadistoragetestbase.h +++ b/tests/testlib/akonadistoragetestbase.h @@ -1,102 +1,94 @@ /* This file is part of Zanshin Copyright 2014-2015 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 TESTLIB_AKONADISTORAGETESTBASE_H #define TESTLIB_AKONADISTORAGETESTBASE_H #include "akonadi/akonadimonitorinterface.h" #include "akonadi/akonadistorageinterface.h" Q_DECLARE_METATYPE(Akonadi::StorageInterface::FetchDepth) namespace Testlib { class AkonadiStorageTestBase : public QObject { Q_OBJECT public: explicit AkonadiStorageTestBase(QObject *parent = nullptr); protected: virtual Akonadi::StorageInterface::Ptr createStorage() = 0; virtual Akonadi::MonitorInterface::Ptr createMonitor() = 0; private slots: void cleanupTestCase(); void dumpTree(); void shouldListCollections_data(); void shouldListCollections(); void shouldRetrieveAllCollectionAncestors(); void shouldListFullItemsInACollection(); void shouldListTags(); void shouldListItemsAssociatedWithTag(); void shouldNotifyCollectionAdded(); void shouldNotifyCollectionRemoved(); void shouldNotifyCollectionChanged(); void shouldNotifyItemAdded(); void shouldNotifyItemRemoved(); void shouldNotifyItemChanged(); - void shouldNotifyItemTagAdded(); - void shouldNotifyItemTagRemoved(); - void shouldNotifyTagAdded(); - void shouldNotifyTagRemoved(); - void shouldNotifyTagChanged(); void shouldReadDefaultCollectionFromSettings(); // This test must be run before shouldCreateItem because createItem // sometimes notifies an itemChanged with a delay. So this test might // receive this notification in addition to the itemChanged generated by updateItem. void shouldUpdateItem(); // This test must be run before shouldCreateItem because createItem // sometimes notifies an itemChanged with a delay. So this test might // receive this notification in addition to the itemChanged generated by updateItem. void shouldUseTransaction(); void shouldCreateItem(); void shouldRetrieveItem(); void shouldMoveItem(); void shouldMoveItems(); void shouldDeleteItem(); void shouldDeleteItems(); - void shouldCreateTag(); - void shouldRemoveTag(); - void shouldUpdateTag(); void shouldUpdateCollection(); void shouldNotifyCollectionTimestampChanges(); void shouldNotifyCollectionSelectionChanges(); private: Akonadi::Item fetchItemByRID(const QString &remoteId, const Akonadi::Collection &collection); Akonadi::Collection fetchCollectionByRID(const QString &remoteId); Akonadi::Tag fetchTagByGID(const QString &gid); Akonadi::Collection calendar1(); Akonadi::Collection calendar2(); Akonadi::Collection emails(); }; } #endif // TESTLIB_AKONADISTORAGETESTBASE_H diff --git a/tests/units/akonadi/akonadilivequeryintegratortest.cpp b/tests/units/akonadi/akonadilivequeryintegratortest.cpp index eea275c4..17e492cc 100644 --- a/tests/units/akonadi/akonadilivequeryintegratortest.cpp +++ b/tests/units/akonadi/akonadilivequeryintegratortest.cpp @@ -1,904 +1,884 @@ /* This file is part of Zanshin Copyright 2015 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 #include #include "akonadi/akonadicollectionfetchjobinterface.h" #include "akonadi/akonadiitemfetchjobinterface.h" #include "akonadi/akonaditagfetchjobinterface.h" #include "akonadi/akonadilivequeryintegrator.h" #include "akonadi/akonadiserializer.h" #include "akonadi/akonadistorage.h" #include "testlib/akonadifakedata.h" #include "testlib/gencollection.h" #include "testlib/gentodo.h" #include "testlib/testhelpers.h" #include "utils/jobhandler.h" using namespace Testlib; static QString titleFromItem(const Akonadi::Item &item) { if (item.hasPayload()) { const auto todo = item.payload(); return todo->summary(); } else { return QString(); } } class AkonadiLiveQueryIntegratorTest : public QObject { Q_OBJECT private: Akonadi::LiveQueryIntegrator::Ptr createIntegrator(AkonadiFakeData &data) { return Akonadi::LiveQueryIntegrator::Ptr( new Akonadi::LiveQueryIntegrator(createSerializer(), Akonadi::MonitorInterface::Ptr(data.createMonitor()) ) ); } Akonadi::StorageInterface::Ptr createStorage(AkonadiFakeData &data) { return Akonadi::StorageInterface::Ptr(data.createStorage()); } Akonadi::SerializerInterface::Ptr createSerializer() { return Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer); } auto fetchCollectionsFunction(Akonadi::StorageInterface::Ptr storage) { return [storage] (const Domain::LiveQueryInput::AddFunction &add) { auto job = storage->fetchCollections(Akonadi::Collection::root(), Akonadi::Storage::Recursive); Utils::JobHandler::install(job->kjob(), [add, job] { foreach (const auto &col, job->collections()) { add(col); } }); }; } auto fetchItemsInAllCollectionsFunction(Akonadi::StorageInterface::Ptr storage) { return [storage] (const Domain::LiveQueryInput::AddFunction &add) { auto job = storage->fetchCollections(Akonadi::Collection::root(), Akonadi::Storage::Recursive); Utils::JobHandler::install(job->kjob(), [add, job, storage] { foreach (const auto &col, job->collections()) { auto itemJob = storage->fetchItems(col); Utils::JobHandler::install(itemJob->kjob(), [add, itemJob] { foreach (const auto &item, itemJob->items()) add(item); }); } }); }; } auto fetchItemsInSelectedCollectionsFunction(Akonadi::StorageInterface::Ptr storage, Akonadi::SerializerInterface::Ptr serializer) { return [storage, serializer] (const Domain::LiveQueryInput::AddFunction &add) { auto job = storage->fetchCollections(Akonadi::Collection::root(), Akonadi::Storage::Recursive); Utils::JobHandler::install(job->kjob(), [add, job, storage, serializer] { foreach (const auto &col, job->collections()) { if (!serializer->isSelectedCollection(col)) continue; auto itemJob = storage->fetchItems(col); Utils::JobHandler::install(itemJob->kjob(), [add, itemJob] { foreach (const auto &item, itemJob->items()) add(item); }); } }); }; } private slots: void shouldBindContextQueries() { // GIVEN AkonadiFakeData data; // Three context todos, one not matching the predicate data.createItem(GenTodo().withUid("ctx-42").asContext().withTitle(QStringLiteral("42-in"))); data.createItem(GenTodo().withUid("ctx-43").asContext().withTitle(QStringLiteral("43-in"))); data.createItem(GenTodo().withUid("ctx-44").asContext().withTitle(QStringLiteral("44-ex"))); auto integrator = createIntegrator(data); auto storage = createStorage(data); auto query = Domain::LiveQueryOutput::Ptr(); auto fetch = [storage] (const Domain::LiveQueryInput::AddFunction &add) { auto job = storage->fetchTags(); Utils::JobHandler::install(job->kjob(), [add, job] { foreach (const auto &tag, job->tags()) { add(tag); } }); }; auto predicate = [] (const Akonadi::Tag &tag) { return tag.name().endsWith(QLatin1String("-in")); }; // Initial listing // WHEN integrator->bind("context1", query, fetch, predicate); auto result = query->result(); result->data(); integrator->bind("context2", query, fetch, predicate); result = query->result(); // Should not cause any problem or wrong data // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->name(), QStringLiteral("42-in")); QCOMPARE(result->data().at(1)->name(), QStringLiteral("43-in")); // Reacts to add // WHEN data.createItem(GenTodo().withId(45).withUid("ctx-45").asContext().withTitle(QStringLiteral("45-in"))); // THEN QCOMPARE(result->data().size(), 3); QCOMPARE(result->data().at(0)->name(), QStringLiteral("42-in")); QCOMPARE(result->data().at(1)->name(), QStringLiteral("43-in")); QCOMPARE(result->data().at(2)->name(), QStringLiteral("45-in")); // Reacts to remove // WHEN - data.removeTag(Akonadi::Tag(45)); + data.removeItem(Akonadi::Item(45)); // THEN QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->name(), QStringLiteral("42-in")); QCOMPARE(result->data().at(1)->name(), QStringLiteral("43-in")); // Reacts to change // WHEN data.modifyItem(GenTodo(data.item(42)).withTitle(QStringLiteral("42-bis-in"))); // THEN QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->name(), QStringLiteral("42-bis-in")); QCOMPARE(result->data().at(1)->name(), QStringLiteral("43-in")); // Reacts to change (which adds) // WHEN data.modifyItem(GenTodo(data.item(44)).withTitle(QStringLiteral("44-in"))); // THEN QCOMPARE(result->data().size(), 3); QCOMPARE(result->data().at(0)->name(), QStringLiteral("42-bis-in")); QCOMPARE(result->data().at(1)->name(), QStringLiteral("43-in")); QCOMPARE(result->data().at(2)->name(), QStringLiteral("44-in")); // Reacts to change (which removes) // WHEN data.modifyItem(GenTodo(data.item(44)).withTitle(QStringLiteral("44-ex"))); // THEN QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->name(), QStringLiteral("42-bis-in")); QCOMPARE(result->data().at(1)->name(), QStringLiteral("43-in")); // Don't keep a reference on any result result.clear(); // The bug we're trying to hit here is the following: // - when bind is called the first time a provider is created internally // - result is deleted at the end of the loop, no one holds the provider with // a strong reference anymore so it is deleted as well // - when bind is called the second time, there's a risk of a dangling // pointer if the recycling of providers is wrongly implemented which can lead // to a crash, if it is properly done no crash will occur for (int i = 0; i < 2; i++) { // WHEN * 2 integrator->bind("contextN", query, fetch, predicate); auto result = query->result(); // THEN * 2 QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QVERIFY(!result->data().isEmpty()); } } void shouldMoveContextBetweenQueries() { // GIVEN AkonadiFakeData data; // One context tag which shows in one query not the other data.createItem(GenTodo().withId(42).withUid("ctx-42").asContext().withTitle(QStringLiteral("42-in"))); auto integrator = createIntegrator(data); auto storage = createStorage(data); auto inQuery = Domain::LiveQueryOutput::Ptr(); auto exQuery = Domain::LiveQueryOutput::Ptr(); auto fetch = [storage] (const Domain::LiveQueryInput::AddFunction &add) { auto job = storage->fetchTags(); Utils::JobHandler::install(job->kjob(), [add, job] { foreach (const auto &tag, job->tags()) { add(tag); } }); }; auto inPredicate = [] (const Akonadi::Tag &tag) { return tag.name().endsWith(QLatin1String("-in")); }; auto exPredicate = [] (const Akonadi::Tag &tag) { return tag.name().endsWith(QLatin1String("-ex")); }; integrator->bind("context-in", inQuery, fetch, inPredicate); auto inResult = inQuery->result(); integrator->bind("context-ex", exQuery, fetch, exPredicate); auto exResult = exQuery->result(); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(inResult->data().size(), 1); QCOMPARE(exResult->data().size(), 0); // WHEN data.modifyItem(GenTodo(data.item(42)).withTitle(QStringLiteral("42-ex"))); // THEN QCOMPARE(inResult->data().size(), 0); QCOMPARE(exResult->data().size(), 1); } void shouldBindDataSourceQueries() { // GIVEN AkonadiFakeData data; // Three top level collections, one not matching the predicate data.createCollection(GenCollection().withId(42).withRootAsParent().withName(QStringLiteral("42-in")).withTaskContent()); data.createCollection(GenCollection().withId(43).withRootAsParent().withName(QStringLiteral("43-in")).withTaskContent()); data.createCollection(GenCollection().withId(44).withRootAsParent().withName(QStringLiteral("44-ex")).withTaskContent()); auto integrator = createIntegrator(data); auto storage = createStorage(data); auto query = Domain::LiveQueryOutput::Ptr(); auto fetch = fetchCollectionsFunction(storage); auto predicate = [] (const Akonadi::Collection &collection) { return collection.name().endsWith(QLatin1String("-in")); }; // Initial listing // WHEN integrator->bind("ds1", query, fetch, predicate); auto result = query->result(); result->data(); integrator->bind("ds2", query, fetch, predicate); result = query->result(); // Should not cause any problem or wrong data // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->name(), QStringLiteral("42-in")); QCOMPARE(result->data().at(1)->name(), QStringLiteral("43-in")); // Reacts to add // WHEN data.createCollection(GenCollection().withId(45).withRootAsParent().withName(QStringLiteral("45-in"))); // THEN QCOMPARE(result->data().size(), 3); QCOMPARE(result->data().at(0)->name(), QStringLiteral("42-in")); QCOMPARE(result->data().at(1)->name(), QStringLiteral("43-in")); QCOMPARE(result->data().at(2)->name(), QStringLiteral("45-in")); // Reacts to remove // WHEN data.removeCollection(Akonadi::Collection(45)); // THEN QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->name(), QStringLiteral("42-in")); QCOMPARE(result->data().at(1)->name(), QStringLiteral("43-in")); // Reacts to change // WHEN data.modifyCollection(GenCollection(data.collection(42)).withName(QStringLiteral("42-bis-in"))); // THEN QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->name(), QStringLiteral("42-bis-in")); QCOMPARE(result->data().at(1)->name(), QStringLiteral("43-in")); // Reacts to change (which adds) // WHEN data.modifyCollection(GenCollection(data.collection(44)).withName(QStringLiteral("44-in"))); // THEN QCOMPARE(result->data().size(), 3); QCOMPARE(result->data().at(0)->name(), QStringLiteral("42-bis-in")); QCOMPARE(result->data().at(1)->name(), QStringLiteral("43-in")); QCOMPARE(result->data().at(2)->name(), QStringLiteral("44-in")); // Reacts to change (which removes) // WHEN data.modifyCollection(GenCollection(data.collection(44)).withName(QStringLiteral("44-ex"))); // THEN QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->name(), QStringLiteral("42-bis-in")); QCOMPARE(result->data().at(1)->name(), QStringLiteral("43-in")); // Don't keep a reference on any result result.clear(); // The bug we're trying to hit here is the following: // - when bind is called the first time a provider is created internally // - result is deleted at the end of the loop, no one holds the provider with // a strong reference anymore so it is deleted as well // - when bind is called the second time, there's a risk of a dangling // pointer if the recycling of providers is wrongly implemented which can lead // to a crash, if it is properly done no crash will occur for (int i = 0; i < 2; i++) { // WHEN * 2 integrator->bind("dsN", query, fetch, predicate); auto result = query->result(); // THEN * 2 QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QVERIFY(!result->data().isEmpty()); } } void shouldMoveDataSourceBetweenQueries() { // GIVEN AkonadiFakeData data; // One top level collection which shows in one query not the other data.createCollection(GenCollection().withId(42).withRootAsParent().withName(QStringLiteral("42-in")).withTaskContent()); auto integrator = createIntegrator(data); auto storage = createStorage(data); auto inQuery = Domain::LiveQueryOutput::Ptr(); auto exQuery = Domain::LiveQueryOutput::Ptr(); auto fetch = fetchCollectionsFunction(storage); auto inPredicate = [] (const Akonadi::Collection &collection) { return collection.name().endsWith(QLatin1String("-in")); }; auto exPredicate = [] (const Akonadi::Collection &collection) { return collection.name().endsWith(QLatin1String("-ex")); }; integrator->bind("ds-in", inQuery, fetch, inPredicate); auto inResult = inQuery->result(); integrator->bind("ds-ex", exQuery, fetch, exPredicate); auto exResult = exQuery->result(); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(inResult->data().size(), 1); QCOMPARE(exResult->data().size(), 0); // WHEN data.modifyCollection(GenCollection(data.collection(42)).withName(QStringLiteral("42-ex"))); // THEN QCOMPARE(inResult->data().size(), 0); QCOMPARE(exResult->data().size(), 1); } void shouldBindProjectQueries() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withName(QStringLiteral("42")).withTaskContent()); // Three projects in the collection, one not matching the predicate data.createItem(GenTodo().withId(42).withParent(42).asProject().withTitle(QStringLiteral("42-in"))); data.createItem(GenTodo().withId(43).withParent(42).asProject().withTitle(QStringLiteral("43-in"))); data.createItem(GenTodo().withId(44).withParent(42).asProject().withTitle(QStringLiteral("44-ex"))); // Couple of tasks in the collection which should not appear or create trouble data.createItem(GenTodo().withId(40).withParent(42).withTitle(QStringLiteral("40"))); data.createItem(GenTodo().withId(41).withParent(42).withTitle(QStringLiteral("41-in"))); auto integrator = createIntegrator(data); auto storage = createStorage(data); auto query = Domain::LiveQueryOutput::Ptr(); auto fetch = fetchItemsInAllCollectionsFunction(storage); auto predicate = [] (const Akonadi::Item &item) { return titleFromItem(item).endsWith(QLatin1String("-in")); }; // Initial listing // WHEN integrator->bind("project1", query, fetch, predicate); auto result = query->result(); result->data(); integrator->bind("project2", query, fetch, predicate); result = query->result(); // Should not cause any problem or wrong data // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->name(), QStringLiteral("42-in")); QCOMPARE(result->data().at(1)->name(), QStringLiteral("43-in")); // Reacts to add // WHEN data.createItem(GenTodo().withId(45).withParent(42).asProject().withTitle(QStringLiteral("45-in"))); // THEN QCOMPARE(result->data().size(), 3); QCOMPARE(result->data().at(0)->name(), QStringLiteral("42-in")); QCOMPARE(result->data().at(1)->name(), QStringLiteral("43-in")); QCOMPARE(result->data().at(2)->name(), QStringLiteral("45-in")); // Reacts to remove // WHEN data.removeItem(Akonadi::Item(45)); // THEN QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->name(), QStringLiteral("42-in")); QCOMPARE(result->data().at(1)->name(), QStringLiteral("43-in")); // Reacts to change // WHEN data.modifyItem(GenTodo(data.item(42)).withTitle(QStringLiteral("42-bis-in"))); // THEN QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->name(), QStringLiteral("42-bis-in")); QCOMPARE(result->data().at(1)->name(), QStringLiteral("43-in")); // Reacts to change (which adds) // WHEN data.modifyItem(GenTodo(data.item(44)).withTitle(QStringLiteral("44-in"))); // THEN QCOMPARE(result->data().size(), 3); QCOMPARE(result->data().at(0)->name(), QStringLiteral("42-bis-in")); QCOMPARE(result->data().at(1)->name(), QStringLiteral("43-in")); QCOMPARE(result->data().at(2)->name(), QStringLiteral("44-in")); // Reacts to change (which removes) // WHEN data.modifyItem(GenTodo(data.item(44)).withTitle(QStringLiteral("44-ex"))); // THEN QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->name(), QStringLiteral("42-bis-in")); QCOMPARE(result->data().at(1)->name(), QStringLiteral("43-in")); // Don't keep a reference on any result result.clear(); // The bug we're trying to hit here is the following: // - when bind is called the first time a provider is created internally // - result is deleted at the end of the loop, no one holds the provider with // a strong reference anymore so it is deleted as well // - when bind is called the second time, there's a risk of a dangling // pointer if the recycling of providers is wrongly implemented which can lead // to a crash, if it is properly done no crash will occur for (int i = 0; i < 2; i++) { // WHEN * 2 integrator->bind("projectN", query, fetch, predicate); auto result = query->result(); // THEN * 2 QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QVERIFY(!result->data().isEmpty()); } } void shouldMoveProjectsBetweenQueries() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withName(QStringLiteral("42")).withTaskContent()); // One project which shows in one query and not the other data.createItem(GenTodo().withId(42).withParent(42).asProject().withTitle(QStringLiteral("42-in"))); // Couple of tasks in the collection which should not appear or create trouble data.createItem(GenTodo().withId(39).withParent(42).withTitle(QStringLiteral("39"))); data.createItem(GenTodo().withId(40).withParent(42).withTitle(QStringLiteral("40-ex"))); data.createItem(GenTodo().withId(41).withParent(42).withTitle(QStringLiteral("41-in"))); auto integrator = createIntegrator(data); auto storage = createStorage(data); auto inQuery = Domain::LiveQueryOutput::Ptr(); auto exQuery = Domain::LiveQueryOutput::Ptr(); auto fetch = fetchItemsInAllCollectionsFunction(storage); auto inPredicate = [] (const Akonadi::Item &item) { return titleFromItem(item).endsWith(QLatin1String("-in")); }; auto exPredicate = [] (const Akonadi::Item &item) { return titleFromItem(item).endsWith(QLatin1String("-ex")); }; integrator->bind("project-in", inQuery, fetch, inPredicate); auto inResult = inQuery->result(); integrator->bind("project-ex", exQuery, fetch, exPredicate); auto exResult = exQuery->result(); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(inResult->data().size(), 1); QCOMPARE(exResult->data().size(), 0); // WHEN data.modifyItem(GenTodo(data.item(42)).withTitle(QStringLiteral("42-ex"))); // THEN QCOMPARE(inResult->data().size(), 0); QCOMPARE(exResult->data().size(), 1); } void shouldReactToCollectionSelectionChangesForProjectQueries() { // GIVEN AkonadiFakeData data; // Two top level collections data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); data.createCollection(GenCollection().withId(43).withRootAsParent().withTaskContent()); // One project in each collection data.createItem(GenTodo().withId(42).withParent(42).asProject().withTitle(QStringLiteral("42"))); data.createItem(GenTodo().withId(43).withParent(43).asProject().withTitle(QStringLiteral("43"))); // Couple of tasks in the collections which should not appear or create trouble data.createItem(GenTodo().withId(40).withParent(42).withTitle(QStringLiteral("40"))); data.createItem(GenTodo().withId(41).withParent(43).withTitle(QStringLiteral("41"))); auto integrator = createIntegrator(data); auto storage = createStorage(data); auto serializer = createSerializer(); auto query = Domain::LiveQueryOutput::Ptr(); auto fetch = fetchItemsInSelectedCollectionsFunction(storage, serializer); auto predicate = [] (const Akonadi::Item &) { return true; }; integrator->bind("project query", query, fetch, predicate); auto result = query->result(); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 2); // WHEN data.modifyCollection(GenCollection(data.collection(43)).selected(false)); TestHelpers::waitForEmptyJobQueue(); // THEN QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->name(), QStringLiteral("42")); } void shouldBindTaskQueries() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withName(QStringLiteral("42")).withTaskContent()); // Three tasks in the collection, one not matching the predicate data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42-in"))); data.createItem(GenTodo().withId(43).withParent(42).withTitle(QStringLiteral("43-in"))); data.createItem(GenTodo().withId(44).withParent(42).withTitle(QStringLiteral("44-ex"))); // Couple of projects in the collection which should not appear or create trouble data.createItem(GenTodo().withId(38).withParent(42).asProject().withTitle(QStringLiteral("38"))); data.createItem(GenTodo().withId(39).withParent(42).asProject().withTitle(QStringLiteral("39-in"))); auto integrator = createIntegrator(data); auto storage = createStorage(data); auto query = Domain::LiveQueryOutput::Ptr(); auto fetch = fetchItemsInAllCollectionsFunction(storage); auto predicate = [] (const Akonadi::Item &item) { return titleFromItem(item).endsWith(QLatin1String("-in")); }; // Initial listing // WHEN integrator->bind("task1", query, fetch, predicate); auto result = query->result(); result->data(); integrator->bind("task2", query, fetch, predicate); result = query->result(); // Should not cause any problem or wrong data // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42-in")); QCOMPARE(result->data().at(1)->title(), QStringLiteral("43-in")); // Reacts to add // WHEN data.createItem(GenTodo().withId(45).withParent(42).withTitle(QStringLiteral("45-in"))); // THEN QCOMPARE(result->data().size(), 3); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42-in")); QCOMPARE(result->data().at(1)->title(), QStringLiteral("43-in")); QCOMPARE(result->data().at(2)->title(), QStringLiteral("45-in")); // Reacts to remove // WHEN data.removeItem(Akonadi::Item(45)); // THEN QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42-in")); QCOMPARE(result->data().at(1)->title(), QStringLiteral("43-in")); // Reacts to change // WHEN data.modifyItem(GenTodo(data.item(42)).withTitle(QStringLiteral("42-bis-in"))); // THEN QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42-bis-in")); QCOMPARE(result->data().at(1)->title(), QStringLiteral("43-in")); // Reacts to change (which adds) // WHEN data.modifyItem(GenTodo(data.item(44)).withTitle(QStringLiteral("44-in"))); // THEN QCOMPARE(result->data().size(), 3); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42-bis-in")); QCOMPARE(result->data().at(1)->title(), QStringLiteral("43-in")); QCOMPARE(result->data().at(2)->title(), QStringLiteral("44-in")); // Reacts to change (which removes) // WHEN data.modifyItem(GenTodo(data.item(44)).withTitle(QStringLiteral("44-ex"))); // THEN QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42-bis-in")); QCOMPARE(result->data().at(1)->title(), QStringLiteral("43-in")); // Don't keep a reference on any result result.clear(); // The bug we're trying to hit here is the following: // - when bind is called the first time a provider is created internally // - result is deleted at the end of the loop, no one holds the provider with // a strong reference anymore so it is deleted as well // - when bind is called the second time, there's a risk of a dangling // pointer if the recycling of providers is wrongly implemented which can lead // to a crash, if it is properly done no crash will occur for (int i = 0; i < 2; i++) { // WHEN * 2 integrator->bind("taskN", query, fetch, predicate); auto result = query->result(); // THEN * 2 QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QVERIFY(!result->data().isEmpty()); } } void shouldMoveTasksBetweenQueries() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withName(QStringLiteral("42")).withTaskContent()); // One task which shows in one query and not the other data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42-in"))); auto integrator = createIntegrator(data); auto storage = createStorage(data); auto inQuery = Domain::LiveQueryOutput::Ptr(); auto exQuery = Domain::LiveQueryOutput::Ptr(); auto fetch = fetchItemsInAllCollectionsFunction(storage); auto inPredicate = [] (const Akonadi::Item &item) { return titleFromItem(item).endsWith(QLatin1String("-in")); }; auto exPredicate = [] (const Akonadi::Item &item) { return titleFromItem(item).endsWith(QLatin1String("-ex")); }; integrator->bind("task-in", inQuery, fetch, inPredicate); auto inResult = inQuery->result(); integrator->bind("task-ex", exQuery, fetch, exPredicate); auto exResult = exQuery->result(); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(inResult->data().size(), 1); QCOMPARE(exResult->data().size(), 0); // WHEN data.modifyItem(GenTodo(data.item(42)).withTitle(QStringLiteral("42-ex"))); // THEN QCOMPARE(inResult->data().size(), 0); QCOMPARE(exResult->data().size(), 1); } void shouldReactToCollectionSelectionChangesForTaskQueries() { // GIVEN AkonadiFakeData data; // Two top level collections data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); data.createCollection(GenCollection().withId(43).withRootAsParent().withTaskContent()); // One task in each collection data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42"))); data.createItem(GenTodo().withId(43).withParent(43).withTitle(QStringLiteral("43"))); // Couple of projects in the collections which should not appear or create trouble data.createItem(GenTodo().withId(40).withParent(42).asProject().withTitle(QStringLiteral("40"))); data.createItem(GenTodo().withId(41).withParent(43).asProject().withTitle(QStringLiteral("41"))); auto integrator = createIntegrator(data); auto storage = createStorage(data); auto serializer = createSerializer(); auto query = Domain::LiveQueryOutput::Ptr(); auto fetch = fetchItemsInSelectedCollectionsFunction(storage, serializer); auto predicate = [] (const Akonadi::Item &) { return true; }; integrator->bind("task query", query, fetch, predicate); auto result = query->result(); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 2); // WHEN data.modifyCollection(GenCollection(data.collection(43)).selected(false)); TestHelpers::waitForEmptyJobQueue(); // THEN QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); } void shouldCallCollectionRemoveHandlers() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withName(QStringLiteral("42"))); auto integrator = createIntegrator(data); qint64 removedId = -1; integrator->addRemoveHandler([&removedId] (const Akonadi::Collection &collection) { removedId = collection.id(); }); // WHEN data.removeCollection(Akonadi::Collection(42)); // THEN QCOMPARE(removedId, qint64(42)); } void shouldCallItemRemoveHandlers() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withName(QStringLiteral("42"))); // One item in the collection data.createItem(GenTodo().withId(42).withParent(42)); auto integrator = createIntegrator(data); qint64 removedId = -1; integrator->addRemoveHandler([&removedId] (const Akonadi::Item &item) { removedId = item.id(); }); // WHEN data.removeItem(Akonadi::Item(42)); // THEN QCOMPARE(removedId, qint64(42)); } - void shouldCallTagRemoveHandlers() - { - // GIVEN - AkonadiFakeData data; - - // One tag - data.createItem(GenTodo().withId(42).withUid("ctx-42").withTitle(QStringLiteral("42"))); - - auto integrator = createIntegrator(data); - qint64 removedId = -1; - integrator->addRemoveHandler([&removedId] (const Akonadi::Tag &tag) { - removedId = tag.id(); - }); - - // WHEN - data.removeTag(Akonadi::Tag(42)); - - // THEN - QCOMPARE(removedId, qint64(42)); - } }; ZANSHIN_TEST_MAIN(AkonadiLiveQueryIntegratorTest) #include "akonadilivequeryintegratortest.moc" diff --git a/tests/units/testlib/akonadifakedatatest.cpp b/tests/units/testlib/akonadifakedatatest.cpp index c6211d09..4ff2da96 100644 --- a/tests/units/testlib/akonadifakedatatest.cpp +++ b/tests/units/testlib/akonadifakedatatest.cpp @@ -1,622 +1,650 @@ /* This file is part of Zanshin Copyright 2015 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 "testlib/akonadifakedata.h" #include "akonadi/akonadimonitorinterface.h" +#include "akonadi/akonadiserializer.h" #include +#include + namespace QTest { template inline bool zCompareHelper(bool isOk, const T &left, const T &right, const char *actual, const char *expected, const char *file, int line) { return compare_helper(isOk, isOk ? "COMPARE()" : "Compared values are not the same", toString(left), toString(right), actual, expected, file, line); } // More aggressive compare to make sure we just don't get collections with ids out template <> inline bool qCompare(const Akonadi::Collection &left, const Akonadi::Collection &right, const char *actual, const char *expected, const char *file, int line) { return zCompareHelper((left == right) && (left.displayName() == right.displayName()), left, right, actual, expected, file, line); } -// More aggressive compare to make sure we just don't get tags with ids out -template <> -inline bool qCompare(const Akonadi::Tag &left, const Akonadi::Tag &right, - const char *actual, const char *expected, - const char *file, int line) -{ - return zCompareHelper((left == right) && (left.name() == right.name()), - left, right, actual, expected, file, line); -} - // More aggressive compare to make sure we just don't get items with ids out template <> inline bool qCompare(const Akonadi::Item &left, const Akonadi::Item &right, const char *actual, const char *expected, const char *file, int line) { return zCompareHelper((left == right) && (left.payloadData() == right.payloadData()), left, right, actual, expected, file, line); } } class AkonadiFakeDataTest : public QObject { Q_OBJECT public: explicit AkonadiFakeDataTest(QObject *parent = nullptr) : QObject(parent) { qRegisterMetaType(); - qRegisterMetaType(); qRegisterMetaType(); } private slots: void shouldBeInitiallyEmpty() { // GIVEN auto data = Testlib::AkonadiFakeData(); // THEN QVERIFY(data.collections().isEmpty()); - QVERIFY(data.tags().isEmpty()); + QVERIFY(data.contexts().isEmpty()); QVERIFY(data.items().isEmpty()); } void shouldCreateCollections() { // GIVEN auto data = Testlib::AkonadiFakeData(); QScopedPointer monitor(data.createMonitor()); QSignalSpy spy(monitor.data(), &Akonadi::MonitorInterface::collectionAdded); auto c1 = Akonadi::Collection(42); c1.setName(QStringLiteral("42")); auto c2 = Akonadi::Collection(43); c2.setName(QStringLiteral("43")); const auto colSet = QSet() << c1 << c2; // WHEN data.createCollection(c1); data.createCollection(c2); // THEN QCOMPARE(data.collections().toList().toSet(), colSet); QCOMPARE(data.collection(c1.id()), c1); QCOMPARE(data.collection(c2.id()), c2); QCOMPARE(spy.size(), 2); QCOMPARE(spy.takeFirst().at(0).value(), c1); QCOMPARE(spy.takeFirst().at(0).value(), c2); } void shouldModifyCollections() { // GIVEN auto data = Testlib::AkonadiFakeData(); QScopedPointer monitor(data.createMonitor()); QSignalSpy spy(monitor.data(), &Akonadi::MonitorInterface::collectionChanged); auto c1 = Akonadi::Collection(42); c1.setName(QStringLiteral("42")); data.createCollection(c1); auto c2 = Akonadi::Collection(c1.id()); c2.setName(QStringLiteral("42-bis")); // WHEN data.modifyCollection(c2); // THEN QCOMPARE(data.collections().size(), 1); QCOMPARE(data.collection(c1.id()), c2); QCOMPARE(spy.size(), 1); QCOMPARE(spy.takeFirst().at(0).value(), c2); } - void shouldNotLooseParentCollectionOnModifyCollection() + void shouldNotLoseParentCollectionOnModifyCollection() { // GIVEN auto data = Testlib::AkonadiFakeData(); auto root = Akonadi::Collection(42); root.setName(QStringLiteral("root")); data.createCollection(root); auto c1 = Akonadi::Collection(43); c1.setName(QStringLiteral("43")); c1.setParentCollection(Akonadi::Collection(root.id())); data.createCollection(c1); auto c2 = Akonadi::Collection(c1.id()); c2.setName(QStringLiteral("43-bis")); // WHEN data.modifyCollection(c2); // THEN QCOMPARE(data.collections().size(), 2); QCOMPARE(data.collection(c1.id()), c2); QCOMPARE(data.collection(c1.id()).parentCollection().id(), root.id()); } void shouldListChildCollections() { // GIVEN auto data = Testlib::AkonadiFakeData(); auto c1 = Akonadi::Collection(42); c1.setName(QStringLiteral("42")); auto c2 = Akonadi::Collection(43); c2.setName(QStringLiteral("43")); c2.setParentCollection(Akonadi::Collection(42)); const auto colSet = QSet() << c2; // WHEN data.createCollection(c1); data.createCollection(c2); // THEN QVERIFY(data.childCollections(c2.id()).isEmpty()); QCOMPARE(data.childCollections(c1.id()).toList().toSet(), colSet); } void shouldReparentCollectionsOnModify() { // GIVEN auto data = Testlib::AkonadiFakeData(); auto c1 = Akonadi::Collection(42); c1.setName(QStringLiteral("42")); data.createCollection(c1); auto c2 = Akonadi::Collection(43); c2.setName(QStringLiteral("43")); data.createCollection(c2); auto c3 = Akonadi::Collection(44); c3.setParentCollection(Akonadi::Collection(42)); data.createCollection(c3); // WHEN c3.setParentCollection(Akonadi::Collection(43)); data.modifyCollection(c3); // THEN QVERIFY(data.childCollections(c1.id()).isEmpty()); QCOMPARE(data.childCollections(c2.id()).size(), 1); QCOMPARE(data.childCollections(c2.id()).at(0), c3); } void shouldRemoveCollections() { // GIVEN auto data = Testlib::AkonadiFakeData(); QScopedPointer monitor(data.createMonitor()); QSignalSpy spy(monitor.data(), &Akonadi::MonitorInterface::collectionRemoved); + Akonadi::Serializer serializer; auto c1 = Akonadi::Collection(42); c1.setName(QStringLiteral("42")); data.createCollection(c1); auto c2 = Akonadi::Collection(43); c2.setName(QStringLiteral("43")); c2.setParentCollection(Akonadi::Collection(42)); data.createCollection(c2); auto c3 = Akonadi::Collection(44); c3.setName(QStringLiteral("44")); c3.setParentCollection(Akonadi::Collection(43)); data.createCollection(c3); - auto i1 = Akonadi::Item(42); - i1.setPayloadFromData("42"); + auto task1 = Domain::Task::Ptr::create(); + auto i1 = serializer.createItemFromTask(task1); + i1.setId(42); i1.setParentCollection(Akonadi::Collection(43)); data.createItem(i1); - auto i2 = Akonadi::Item(43); - i2.setPayloadFromData("43"); + auto task2 = Domain::Task::Ptr::create(); + auto i2 = serializer.createItemFromTask(task2); + i2.setId(43); i2.setParentCollection(Akonadi::Collection(44)); data.createItem(i2); // WHEN data.removeCollection(c2); // THEN QCOMPARE(data.collections().size(), 1); QCOMPARE(data.collections().at(0), c1); QVERIFY(!data.collection(c2.id()).isValid()); QVERIFY(!data.collection(c3.id()).isValid()); QVERIFY(data.childCollections(c1.id()).isEmpty()); QVERIFY(data.childCollections(c2.id()).isEmpty()); QVERIFY(data.childCollections(c3.id()).isEmpty()); QVERIFY(data.items().isEmpty()); QVERIFY(!data.item(i1.id()).isValid()); QVERIFY(!data.item(i2.id()).isValid()); QVERIFY(data.childItems(c2.id()).isEmpty()); QVERIFY(data.childItems(c3.id()).isEmpty()); QCOMPARE(spy.size(), 2); QCOMPARE(spy.takeFirst().at(0).value(), c3); QCOMPARE(spy.takeFirst().at(0).value(), c2); } - void shouldCreateTags() + void shouldCreateContexts() { // GIVEN auto data = Testlib::AkonadiFakeData(); QScopedPointer monitor(data.createMonitor()); - QSignalSpy spy(monitor.data(), &Akonadi::MonitorInterface::tagAdded); + QSignalSpy spy(monitor.data(), &Akonadi::MonitorInterface::itemAdded); + Akonadi::Serializer serializer; + + auto context1 = Domain::Context::Ptr::create(); + context1->setName(QStringLiteral("42")); + context1->setProperty("todoUid", "ctx-42"); + auto t1 = serializer.createItemFromContext(context1); - auto t1 = Akonadi::Tag(42); - t1.setName(QStringLiteral("42")); - auto t2 = Akonadi::Tag(43); - t2.setName(QStringLiteral("43")); + auto context2 = Domain::Context::Ptr::create(); + context2->setName(QStringLiteral("43")); + context2->setProperty("todoUid", "ctx-43"); + auto t2 = serializer.createItemFromContext(context2); // WHEN - data.createTag(t1); - data.createTag(t2); + data.createContext(t1); + data.createContext(t2); // THEN - QCOMPARE(data.tags().size(), 2); - QVERIFY(data.tags().contains(t1)); - QVERIFY(data.tags().contains(t2)); - QCOMPARE(data.tag(t1.id()), t1); - QCOMPARE(data.tag(t2.id()), t2); + QCOMPARE(data.contexts().size(), 2); + QVERIFY(data.contexts().contains(t1)); + QVERIFY(data.contexts().contains(t2)); + QCOMPARE(data.contextItem("ctx-42"), t1); + QCOMPARE(data.contextItem("ctx-43"), t2); QCOMPARE(spy.size(), 2); - QCOMPARE(spy.takeFirst().at(0).value(), t1); - QCOMPARE(spy.takeFirst().at(0).value(), t2); + QCOMPARE(spy.takeFirst().at(0).value(), t1); + QCOMPARE(spy.takeFirst().at(0).value(), t2); } - void shouldModifyTags() + void shouldModifyContexts() { // GIVEN auto data = Testlib::AkonadiFakeData(); QScopedPointer monitor(data.createMonitor()); - QSignalSpy spy(monitor.data(), &Akonadi::MonitorInterface::tagChanged); + QSignalSpy spy(monitor.data(), &Akonadi::MonitorInterface::itemChanged); + Akonadi::Serializer serializer; - auto t1 = Akonadi::Tag(42); - t1.setName(QStringLiteral("42")); - data.createTag(t1); + auto context1 = Domain::Context::Ptr::create(); + context1->setName(QStringLiteral("42")); + context1->setProperty("todoUid", "ctx-42"); + auto t1 = serializer.createItemFromContext(context1); + data.createContext(t1); - auto t2 = Akonadi::Tag(t1.id()); - t2.setName(QStringLiteral("42-bis")); + auto context2 = Domain::Context::Ptr::create(); + context2->setName(QStringLiteral("42-bis")); + context2->setProperty("todoUid", "ctx-42"); + auto t2 = serializer.createItemFromContext(context2); // WHEN - data.modifyTag(t2); + data.modifyContext(t2); // THEN - QCOMPARE(data.tags().size(), 1); - QCOMPARE(data.tag(t1.id()), t2); + QCOMPARE(data.contexts().size(), 1); + QCOMPARE(data.contextItem("ctx-42"), t2); QCOMPARE(spy.size(), 1); - QCOMPARE(spy.takeFirst().at(0).value(), t2); + QCOMPARE(spy.takeFirst().at(0).value(), t2); } - void shouldRemoveTags() + void shouldRemoveContexts() { // GIVEN auto data = Testlib::AkonadiFakeData(); QScopedPointer monitor(data.createMonitor()); - QSignalSpy tagSpy(monitor.data(), &Akonadi::MonitorInterface::tagRemoved); + QSignalSpy contextSpy(monitor.data(), &Akonadi::MonitorInterface::itemRemoved); QSignalSpy itemSpy(monitor.data(), &Akonadi::MonitorInterface::itemChanged); + Akonadi::Serializer serializer; auto c1 = Akonadi::Collection(42); data.createCollection(c1); - auto t1 = Akonadi::Tag(42); - t1.setName(QStringLiteral("42")); - data.createTag(t1); + auto context1 = Domain::Context::Ptr::create(); + context1->setName(QStringLiteral("1")); + context1->setProperty("todoUid", "ctx-1"); + auto t1 = serializer.createItemFromContext(context1); + data.createContext(t1); - auto t2 = Akonadi::Tag(43); - t2.setName(QStringLiteral("43")); - data.createTag(t2); + auto context2 = Domain::Context::Ptr::create(); + context2->setName(QStringLiteral("2")); + context2->setProperty("todoUid", "ctx-2"); + auto t2 = serializer.createItemFromContext(context2); + data.createContext(t2); - auto i1 = Akonadi::Item(42); - i1.setPayloadFromData("42"); + auto task1 = Domain::Task::Ptr::create(); + auto i1 = serializer.createItemFromTask(task1); i1.setParentCollection(c1); - i1.setTag(Akonadi::Tag(t1.id())); + i1.setId(42); + serializer.addContextToTask(context1, i1); data.createItem(i1); + QVERIFY(serializer.isContextChild(context1, i1)); - auto i2 = Akonadi::Item(43); - i2.setPayloadFromData("43"); + auto task2 = Domain::Task::Ptr::create(); + auto i2 = serializer.createItemFromTask(task2); i2.setParentCollection(c1); - i2.setTag(Akonadi::Tag(t2.id())); + i2.setId(43); + serializer.addContextToTask(context2, i2); data.createItem(i2); + QVERIFY(serializer.isContextChild(context2, i2)); const auto itemSet = QSet() << i1 << i2; // WHEN - data.removeTag(t2); + data.removeContext(t2); // THEN - QCOMPARE(data.tags().size(), 1); - QCOMPARE(data.tags().at(0), t1); + QCOMPARE(data.contexts().size(), 1); + QCOMPARE(data.contexts().at(0), t1); - QVERIFY(!data.tag(t2.id()).isValid()); + QVERIFY(!data.contextItem("ctx-2").isValid()); - QCOMPARE(data.tagItems(t1.id()).size(), 1); - QCOMPARE(data.tagItems(t1.id()).at(0), i1); - QVERIFY(data.tagItems(t2.id()).isEmpty()); + QCOMPARE(data.contextItems("ctx-1").size(), 1); + QCOMPARE(data.contextItems("ctx-1").at(0), i1); + QVERIFY(data.contextItems("ctx-2").isEmpty()); QCOMPARE(data.items().toList().toSet(), itemSet); QVERIFY(data.item(i1.id()).isValid()); - QVERIFY(data.item(i2.id()).isValid()); - QVERIFY(!data.item(i2.id()).tags().contains(t2)); + const auto item2 = data.item(i2.id()); + QVERIFY(item2.isValid()); + QVERIFY(!serializer.isContextChild(context2, item2)); - QCOMPARE(tagSpy.size(), 1); - QCOMPARE(tagSpy.takeFirst().at(0).value(), t2); + QCOMPARE(contextSpy.size(), 1); + QCOMPARE(contextSpy.takeFirst().at(0).value(), t2); QCOMPARE(itemSpy.size(), 1); - QCOMPARE(itemSpy.first().at(0).value(), i2); - QVERIFY(!itemSpy.first().at(0).value().tags().contains(t2)); + const auto emittedItem2 = itemSpy.first().at(0).value(); + QCOMPARE(emittedItem2, i2); + QVERIFY(!serializer.isContextChild(context2, emittedItem2)); } void shouldCreateItems() { // GIVEN auto data = Testlib::AkonadiFakeData(); QScopedPointer monitor(data.createMonitor()); QSignalSpy spy(monitor.data(), &Akonadi::MonitorInterface::itemAdded); auto i1 = Akonadi::Item(42); i1.setPayloadFromData("42"); auto i2 = Akonadi::Item(43); i2.setPayloadFromData("43"); const auto itemSet = QSet() << i1 << i2; // WHEN data.createItem(i1); data.createItem(i2); // THEN QCOMPARE(data.items().toList().toSet(), itemSet); QCOMPARE(data.item(i1.id()), i1); QCOMPARE(data.item(i2.id()), i2); QCOMPARE(spy.size(), 2); QCOMPARE(spy.takeFirst().at(0).value(), i1); QCOMPARE(spy.takeFirst().at(0).value(), i2); } void shouldModifyItems() { // GIVEN auto data = Testlib::AkonadiFakeData(); QScopedPointer monitor(data.createMonitor()); QSignalSpy spy(monitor.data(), &Akonadi::MonitorInterface::itemChanged); QSignalSpy moveSpy(monitor.data(), &Akonadi::MonitorInterface::itemMoved); auto c1 = Akonadi::Collection(42); c1.setName(QStringLiteral("42")); data.createCollection(c1); auto i1 = Akonadi::Item(42); i1.setPayloadFromData("42"); i1.setParentCollection(Akonadi::Collection(42)); data.createItem(i1); auto i2 = Akonadi::Item(i1.id()); i2.setPayloadFromData("42-bis"); i2.setParentCollection(Akonadi::Collection(42)); // WHEN data.modifyItem(i2); // THEN QCOMPARE(data.items().size(), 1); QCOMPARE(data.item(i1.id()), i2); QCOMPARE(spy.size(), 1); QCOMPARE(spy.takeFirst().at(0).value(), i2); QCOMPARE(moveSpy.size(), 0); } - void shouldNotLooseParentCollectionOnModifyItem() + void shouldNotLoseParentCollectionOnModifyItem() { // GIVEN auto data = Testlib::AkonadiFakeData(); auto c1 = Akonadi::Collection(42); c1.setName(QStringLiteral("42")); data.createCollection(c1); auto i1 = Akonadi::Item(42); i1.setPayloadFromData("42"); i1.setParentCollection(Akonadi::Collection(42)); data.createItem(i1); auto i2 = Akonadi::Item(i1.id()); i2.setPayloadFromData("42-bis"); // WHEN data.modifyItem(i2); // THEN QCOMPARE(data.items().size(), 1); QCOMPARE(data.item(i1.id()), i2); QCOMPARE(data.item(i1.id()).parentCollection().id(), c1.id()); } void shouldListChildItems() { // GIVEN auto data = Testlib::AkonadiFakeData(); auto c1 = Akonadi::Collection(42); c1.setName(QStringLiteral("42")); data.createCollection(c1); auto i1 = Akonadi::Item(42); i1.setPayloadFromData("42"); i1.setParentCollection(Akonadi::Collection(42)); // WHEN data.createItem(i1); // THEN QCOMPARE(data.childItems(c1.id()).size(), 1); QCOMPARE(data.childItems(c1.id()).at(0), i1); } void shouldReparentItemsOnModify() { // GIVEN auto data = Testlib::AkonadiFakeData(); QScopedPointer monitor(data.createMonitor()); QSignalSpy spy(monitor.data(), &Akonadi::MonitorInterface::itemMoved); auto c1 = Akonadi::Collection(42); c1.setName(QStringLiteral("42")); data.createCollection(c1); auto c2 = Akonadi::Collection(43); c2.setName(QStringLiteral("43")); data.createCollection(c2); auto i1 = Akonadi::Item(42); i1.setPayloadFromData("42"); i1.setParentCollection(Akonadi::Collection(42)); data.createItem(i1); // WHEN i1.setPayloadFromData("42-bis"); i1.setParentCollection(Akonadi::Collection(43)); data.modifyItem(i1); // THEN QVERIFY(data.childItems(c1.id()).isEmpty()); QCOMPARE(data.childItems(c2.id()).size(), 1); QCOMPARE(data.childItems(c2.id()).at(0), i1); QCOMPARE(spy.size(), 1); QCOMPARE(spy.takeFirst().at(0).value(), i1); } void shouldListTagItems() { // GIVEN auto data = Testlib::AkonadiFakeData(); - auto t1 = Akonadi::Tag(42); - t1.setName(QStringLiteral("42")); - data.createTag(t1); + Akonadi::Serializer serializer; - auto i1 = Akonadi::Item(42); - i1.setPayloadFromData("42"); - i1.setTag(Akonadi::Tag(42)); + auto context1 = Domain::Context::Ptr::create(); + context1->setName(QStringLiteral("42")); + context1->setProperty("todoUid", "ctx-42"); + auto t1 = serializer.createItemFromContext(context1); + data.createContext(t1); + + auto task1 = Domain::Task::Ptr::create(); + auto i1 = serializer.createItemFromTask(task1); + i1.setId(1); + serializer.addContextToTask(context1, i1); // WHEN data.createItem(i1); // THEN - QCOMPARE(data.tagItems(t1.id()).size(), 1); - QCOMPARE(data.tagItems(t1.id()).at(0), i1); + QCOMPARE(data.contextItems("ctx-42").size(), 1); + QCOMPARE(data.contextItems("ctx-42").at(0), i1); } void shouldRetagItemsOnModify() { // GIVEN auto data = Testlib::AkonadiFakeData(); QScopedPointer monitor(data.createMonitor()); QSignalSpy spy(monitor.data(), &Akonadi::MonitorInterface::itemChanged); - - auto t1 = Akonadi::Tag(42); - t1.setName(QStringLiteral("42")); - data.createTag(t1); - - auto t2 = Akonadi::Tag(43); - t2.setName(QStringLiteral("43")); - data.createTag(t2); - - auto i1 = Akonadi::Item(42); - i1.setPayloadFromData("42"); - i1.setTag(Akonadi::Tag(42)); + Akonadi::Serializer serializer; + + auto context1 = Domain::Context::Ptr::create(); + context1->setName(QStringLiteral("42")); + context1->setProperty("todoUid", "ctx-42"); + auto t1 = serializer.createItemFromContext(context1); + data.createContext(t1); + + auto context2 = Domain::Context::Ptr::create(); + context2->setName(QStringLiteral("43")); + context2->setProperty("todoUid", "ctx-43"); + auto t2 = serializer.createItemFromContext(context2); + data.createContext(t2); + + auto task1 = Domain::Task::Ptr::create(); + auto i1 = serializer.createItemFromTask(task1); + i1.setId(1); + serializer.addContextToTask(context1, i1); data.createItem(i1); // WHEN - i1.setPayloadFromData("42-bis"); - i1.clearTag(Akonadi::Tag(42)); - i1.setTag(Akonadi::Tag(43)); - data.modifyItem(i1); + auto i2 = serializer.createItemFromTask(task1); + i2.setId(1); + serializer.addContextToTask(context2, i2); + data.modifyItem(i2); // THEN - QVERIFY(data.tagItems(t1.id()).isEmpty()); - QCOMPARE(data.tagItems(t2.id()).size(), 1); - QCOMPARE(data.tagItems(t2.id()).at(0), i1); + QVERIFY(data.contextItems("ctx-42").isEmpty()); + QCOMPARE(data.contextItems("ctx-43").size(), 1); + QCOMPARE(data.contextItems("ctx-43").at(0), i2); QCOMPARE(spy.size(), 1); - QCOMPARE(spy.takeFirst().at(0).value(), i1); + QCOMPARE(spy.takeFirst().at(0).value(), i2); } void shouldRemoveItems() { // GIVEN auto data = Testlib::AkonadiFakeData(); QScopedPointer monitor(data.createMonitor()); QSignalSpy spy(monitor.data(), &Akonadi::MonitorInterface::itemRemoved); auto c1 = Akonadi::Collection(42); c1.setName(QStringLiteral("42")); data.createCollection(c1); auto i1 = Akonadi::Item(42); i1.setPayloadFromData("42"); i1.setParentCollection(Akonadi::Collection(42)); data.createItem(i1); // WHEN data.removeItem(i1); // THEN QVERIFY(data.items().isEmpty()); QVERIFY(!data.item(i1.id()).isValid()); QVERIFY(data.childItems(c1.id()).isEmpty()); QCOMPARE(spy.size(), 1); QCOMPARE(spy.takeFirst().at(0).value(), i1); } }; ZANSHIN_TEST_MAIN(AkonadiFakeDataTest) #include "akonadifakedatatest.moc" diff --git a/tests/units/testlib/akonadifakedataxmlloadertest.cpp b/tests/units/testlib/akonadifakedataxmlloadertest.cpp index dac2f7c5..e23bd60c 100644 --- a/tests/units/testlib/akonadifakedataxmlloadertest.cpp +++ b/tests/units/testlib/akonadifakedataxmlloadertest.cpp @@ -1,84 +1,84 @@ /* This file is part of Zanshin Copyright 2015 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 "testlib/akonadifakedata.h" #include "testlib/akonadifakedataxmlloader.h" #include class AkonadiFakeDataXmlLoaderTest : public QObject { Q_OBJECT private slots: void shouldLoadFromXmlFile() { // GIVEN auto data = Testlib::AkonadiFakeData(); // Put a first collection with an idea to make sure it isn't lost on loading auto searchCollection = Akonadi::Collection(1); searchCollection.setParentCollection(Akonadi::Collection::root()); searchCollection.setName(QStringLiteral("Search")); data.createCollection(searchCollection); auto loader = Testlib::AkonadiFakeDataXmlLoader(&data); // WHEN loader.load(SOURCE_DIR "/../akonadi/testenv/data/testdata.xml"); // THEN QVERIFY(data.collections().size() > 1); QVERIFY(data.items().size() > 1); - QVERIFY(data.tags().size() > 1); + QVERIFY(data.contexts().size() > 1); // Do a bit of sanity checking, no need to do much more than that as // the xml loading will be extensively used in the AkonadiFakeStorageTest // and AkonadiFakeData has quite a few asserts. QCOMPARE(data.childCollections(Akonadi::Collection::root().id()).size(), 2); const auto res = data.childCollections(Akonadi::Collection::root().id()).at(1); QCOMPARE(res.id(), qint64(2)); const auto children = data.childCollections(res.id()); QCOMPARE(children.size(), 5); QCOMPARE(children.at(0).name(), QStringLiteral("Calendar1")); QCOMPARE(children.at(0).id(), qint64(3)); QCOMPARE(children.at(0).remoteId(), QStringLiteral("{cdc229c7-a9b5-4d37-989d-a28e372be2a9}")); QCOMPARE(children.at(1).name(), QStringLiteral("Emails")); QCOMPARE(children.at(1).id(), qint64(4)); QCOMPARE(children.at(1).remoteId(), QStringLiteral("{14096930-7bfe-46ca-8fba-7c04d3b62ec8}")); QCOMPARE(children.at(2).name(), QStringLiteral("Contacts")); QCOMPARE(children.at(2).id(), qint64(5)); QCOMPARE(children.at(2).remoteId(), QStringLiteral("{36bf43ac-0988-4435-b602-d6c29e606630}")); QCOMPARE(children.at(3).name(), QStringLiteral("Destroy me!")); QCOMPARE(children.at(3).id(), qint64(6)); QCOMPARE(children.at(3).remoteId(), QStringLiteral("{1f78b360-a01b-4785-9187-75450190342c}")); QCOMPARE(children.at(4).name(), QStringLiteral("Change me!")); QCOMPARE(children.at(4).id(), qint64(7)); QCOMPARE(children.at(4).remoteId(), QStringLiteral("{28ef9f03-4ebc-4e33-970f-f379775894f9}")); } }; ZANSHIN_TEST_MAIN(AkonadiFakeDataXmlLoaderTest) #include "akonadifakedataxmlloadertest.moc"