diff --git a/src/zanshin/migrator/zanshincontextitemsmigrator.cpp b/src/zanshin/migrator/zanshincontextitemsmigrator.cpp index a9f9904d..a95a0687 100644 --- a/src/zanshin/migrator/zanshincontextitemsmigrator.cpp +++ b/src/zanshin/migrator/zanshincontextitemsmigrator.cpp @@ -1,147 +1,150 @@ /* This file is part of Zanshin Copyright 2019 David Faure 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 "zanshincontextitemsmigrator.h" #include #include #include #include #include #include #include static const char s_contextTagType[] = "Zanshin-Context"; ZanshinContextItemsMigrator::ZanshinContextItemsMigrator() { } ZanshinContextItemsMigrator::FetchResult ZanshinContextItemsMigrator::fetchAllItems(WhichItems which) { FetchResult result; auto collectionsJob = m_storage.fetchCollections(Akonadi::Collection::root(), Akonadi::Storage::Recursive); collectionsJob->kjob()->exec(); auto collections = collectionsJob->collections(); foreach (const auto &collection, collections) { auto job = m_storage.fetchItemsWithTags(collection); job->kjob()->exec(); bool hasTaskToConvert = false; auto items = job->items(); Akonadi::Item::List selectedItems; foreach (const Akonadi::Item &item, items) { if (item.hasPayload()) { auto todo = item.payload(); if (which == WhichItems::TasksToConvert && !todo->customProperty("Zanshin", "ContextList").isEmpty()) { // This folder was already migrated, skip it hasTaskToConvert = false; qDebug() << "Detected an already converted task" << todo->uid(); break; } const bool isContext = !todo->customProperty("Zanshin", "Context").isEmpty(); if ((isContext && which == WhichItems::OnlyContexts) || (!isContext && which == WhichItems::TasksToConvert) || (!isContext && which == WhichItems::AllTasks)) { selectedItems.push_back(item); + hasTaskToConvert = true; } - hasTaskToConvert = true; } } if (hasTaskToConvert) { result.pickedCollection = collection; result.items += selectedItems; } } if (!result.pickedCollection.isValid()) result.pickedCollection = collections.first(); return result; } Akonadi::Tag::List ZanshinContextItemsMigrator::fetchAllTags() { Akonadi::Tag::List tags; Akonadi::TagFetchJob job; job.exec(); const auto allTags = job.tags(); std::copy_if(allTags.constBegin(), allTags.constEnd(), std::back_inserter(tags), [](const Akonadi::Tag &tag) { return tag.type() == s_contextTagType; }); return tags; } void ZanshinContextItemsMigrator::createContexts(const Akonadi::Tag::List &contextTags, const Akonadi::Collection &collection) { int count = 0; for (const auto &tag : contextTags) { auto context = Domain::Context::Ptr::create(); context->setName(tag.name()); - context->setProperty("todoUid", tag.gid()); Akonadi::Item item = m_serializer.createItemFromContext(context); item.setParentCollection(collection); auto job = new Akonadi::ItemCreateJob(item, collection); if (job->exec()) { ++count; + m_tagUids.insert(tag.id(), job->item().payload()->uid()); } else { qWarning() << "Failure to create context:" << job->errorString(); } } qDebug() << "Created" << count << "contexts in collection" << collection.name(); } void ZanshinContextItemsMigrator::associateContexts(Akonadi::Item::List& items) { int count = 0; for (auto &item : items) { const auto allTags = item.tags(); for (const auto &tag : allTags) { if (tag.type() == s_contextTagType) { auto context = Domain::Context::Ptr::create(); context->setName(tag.name()); - context->setProperty("todoUid", tag.gid()); + const auto tagUid = m_tagUids.value(tag.id()); + if (tagUid.isEmpty()) + qWarning() << "Item" << item.id() << "uses unknown tag" << tag.id() << tag.name(); + context->setProperty("todoUid", tagUid); m_serializer.addContextToTask(context, item); auto job = new Akonadi::ItemModifyJob(item); if (job->exec()) { item = job->item(); ++count; } else { qWarning() << "Failure to associate context" << tag.name() << "to task:" << job->errorString(); } } } } qDebug() << "Associated contexts to" << count << "items"; } bool ZanshinContextItemsMigrator::migrateTags() { auto result = fetchAllItems(WhichItems::TasksToConvert); auto tags = fetchAllTags(); createContexts(tags, result.pickedCollection); associateContexts(result.items); return true; } diff --git a/src/zanshin/migrator/zanshincontextitemsmigrator.h b/src/zanshin/migrator/zanshincontextitemsmigrator.h index 44bb4342..e3ed0f27 100644 --- a/src/zanshin/migrator/zanshincontextitemsmigrator.h +++ b/src/zanshin/migrator/zanshincontextitemsmigrator.h @@ -1,58 +1,59 @@ /* This file is part of Zanshin Copyright 2019 David Faure 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 ZANSHINCONTEXTITEMSMIGRATOR_H #define ZANSHINCONTEXTITEMSMIGRATOR_H #include #include #include enum class WhichItems { TasksToConvert, OnlyContexts, AllTasks }; class ZanshinContextItemsMigrator { public: ZanshinContextItemsMigrator(); struct FetchResult { Akonadi::Item::List items; Akonadi::Collection pickedCollection; }; FetchResult fetchAllItems(WhichItems which); Akonadi::Tag::List fetchAllTags(); void createContexts(const Akonadi::Tag::List &contextTags, const Akonadi::Collection &collection); void associateContexts(Akonadi::Item::List &items); bool migrateTags(); private: Akonadi::Storage m_storage; Akonadi::Serializer m_serializer; + QHash m_tagUids; }; #endif // ZANSHINCONTEXTITEMSMIGRATOR_H diff --git a/tests/units/migrator/zanshincontextitemsmigrationtest.cpp b/tests/units/migrator/zanshincontextitemsmigrationtest.cpp index 8c2ec223..3e22321d 100644 --- a/tests/units/migrator/zanshincontextitemsmigrationtest.cpp +++ b/tests/units/migrator/zanshincontextitemsmigrationtest.cpp @@ -1,230 +1,229 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens Copyright 2014-2019 David Faure 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 "zanshincontextitemsmigrator.h" #include #include #include #include #include #include #include #include #include "akonadi/akonadicollectionfetchjobinterface.h" #include "akonadi/akonadiitemfetchjobinterface.h" #include "akonadi/akonadistorage.h" #include "akonadi/akonadiserializer.h" #include #include #include #include class ZanshinContextItemsMigrationTest : public QObject { Q_OBJECT public: explicit ZanshinContextItemsMigrationTest(QObject *parent = nullptr) : QObject(parent) { qRegisterMetaType(); qRegisterMetaType(); } private slots: void initTestCase() { QVERIFY(TestLib::TestSafety::checkTestIsIsolated()); auto storage = Akonadi::StorageInterface::Ptr(new Akonadi::Storage); TestLib::AkonadiDebug::dumpTree(storage); //QProcess proc; //proc.execute("akonadiconsole"); //qApp->exec(); } void cleanupTestCase() { // Give a chance for jobs still waiting for an event loop // run to be deleted through deleteLater() QTest::qWait(10); } void shouldFetchAllItems() { - // GIVEN - ZanshinContextItemsMigrator migrator; - // WHEN - auto result = migrator.fetchAllItems(WhichItems::TasksToConvert); + auto result = m_migrator.fetchAllItems(WhichItems::TasksToConvert); // THEN // the migrator gathered all items and the related tags - m_expectedUids.insert(QStringLiteral("child-of-project"), {"errands-context", "online-context"}); + m_expectedUids.insert(QStringLiteral("child-of-project"), {"Errands", "Online"}); m_expectedUids.insert(QStringLiteral("new-project-with-property"), {}); - m_expectedUids.insert(QStringLiteral("old-project-with-comment"), {"errands-context"}); + m_expectedUids.insert(QStringLiteral("old-project-with-comment"), {"Errands"}); m_expectedUids.insert(QStringLiteral("project-with-comment-and-property"), {}); m_expectedUids.insert(QStringLiteral("project-with-children"), {}); - m_expectedUids.insert(QStringLiteral("standalone-task"), {"errands-context"}); + m_expectedUids.insert(QStringLiteral("standalone-task"), {"Errands"}); checkExpectedTags(result.items, m_expectedUids); // and picked a collection QCOMPARE(result.pickedCollection.name(), "Calendar2"); } void shouldFetchAllTags() { - // GIVEN - ZanshinContextItemsMigrator migrator; - // WHEN - Akonadi::Tag::List tags = migrator.fetchAllTags(); + Akonadi::Tag::List tags = m_migrator.fetchAllTags(); // THEN - QCOMPARE(tagNames(tags), QStringList() << "errands-context" << "online-context" << "unused-context"); + QCOMPARE(tagNames(tags), QStringList() << "Errands" << "Online" << "Unused"); } void shouldCreateContexts() { // GIVEN - ZanshinContextItemsMigrator migrator; - auto result = migrator.fetchAllItems(WhichItems::TasksToConvert); // in fact we just need the collection... - Akonadi::Tag::List tags = migrator.fetchAllTags(); + const auto result = m_migrator.fetchAllItems(WhichItems::TasksToConvert); // in fact we just need the collection... + const Akonadi::Tag::List tags = m_migrator.fetchAllTags(); // WHEN - migrator.createContexts(tags, result.pickedCollection); + m_migrator.createContexts(tags, result.pickedCollection); // THEN - auto newItemList = migrator.fetchAllItems(WhichItems::OnlyContexts).items; + auto newItemList = m_migrator.fetchAllItems(WhichItems::OnlyContexts).items; Akonadi::Item::List contextItems; QVector contextTodos; for (auto it = newItemList.constBegin(); it != newItemList.constEnd(); ++it) { const auto item = *it; if (m_serializer.isContext(item)) { contextItems.append(item); - contextTodos.append(item.payload()); + auto todo = item.payload(); + contextTodos.append(todo); + m_contextTodos.insert(todo->uid(), todo->summary()); } } QCOMPARE(contextItems.size(), 3); QCOMPARE(contextTodos.size(), 3); - QCOMPARE(contextTodos.at(0)->uid(), "errands-context"); - QCOMPARE(contextTodos.at(1)->uid(), "online-context"); - QCOMPARE(contextTodos.at(2)->uid(), "unused-context"); + QCOMPARE(contextTodos.at(0)->summary(), "Errands"); + QCOMPARE(contextTodos.at(1)->summary(), "Online"); + QCOMPARE(contextTodos.at(2)->summary(), "Unused"); } void shouldAssociateContexts() { // GIVEN - ZanshinContextItemsMigrator migrator; - auto result = migrator.fetchAllItems(WhichItems::TasksToConvert); + auto result = m_migrator.fetchAllItems(WhichItems::TasksToConvert); // WHEN - migrator.associateContexts(result.items); + m_migrator.associateContexts(result.items); // THEN - auto newResult = migrator.fetchAllItems(WhichItems::AllTasks); + auto newResult = m_migrator.fetchAllItems(WhichItems::AllTasks); checkExpectedContexts(newResult.items, m_expectedUids); } private: void checkExpectedTags(const Akonadi::Item::List &items, const QMap &expectedItems) { const QMap itemHash = fillHash(items); const QStringList uids = itemHash.keys(); if (items.count() != expectedItems.count()) // QCOMPARE for QStringList isn't verbose enough qWarning() << "Got" << uids << "expected" << expectedItems.keys(); QCOMPARE(uids, QStringList(expectedItems.keys())); for (auto it = expectedItems.constBegin(); it != expectedItems.constEnd(); ++it) { //qDebug() << it.key(); const Akonadi::Item item = itemHash.value(it.key()); const auto allTags = item.tags(); Akonadi::Tag::List tags; std::copy_if(allTags.constBegin(), allTags.constEnd(), std::back_inserter(tags), [](const Akonadi::Tag &tag) { return tag.type() == "Zanshin-Context"; }); const auto itemTags = tagNames(tags); if (itemTags != it.value()) // QCOMPARE for QStringList isn't verbose enough qWarning() << it.key() << "got" << itemTags << "expected" << it.value(); QCOMPARE(itemTags, it.value()); QVERIFY(!m_serializer.isContext(item)); } } static QMap fillHash(const Akonadi::Item::List &items) { QMap itemHash; for (const Akonadi::Item &item : items) { auto todo = item.payload(); itemHash.insert(todo->uid(), item); } return itemHash; } void checkExpectedContexts(const Akonadi::Item::List &items, const QMap &expectedItems) { - const QMap itemHash = fillHash(items); + const QMap itemHash = fillHash(items); const QStringList uids = itemHash.keys(); if (uids.count() != expectedItems.count()) // QCOMPARE for QStringList isn't verbose enough qWarning() << "Got" << uids << "expected" << expectedItems.keys(); QCOMPARE(uids, QStringList(expectedItems.keys())); for (auto it = expectedItems.constBegin(); it != expectedItems.constEnd(); ++it) { const Akonadi::Item item = itemHash.value(it.key()); //qDebug() << item.id() << it.key(); auto todo = item.payload(); QVERIFY(todo); - const auto contexts = todo->customProperty("Zanshin", "ContextList").split(',', QString::SkipEmptyParts); - if (contexts != it.value()) // QCOMPARE for QStringList isn't verbose enough - qWarning() << it.key() << "got" << contexts << "expected" << it.value(); - QCOMPARE(contexts, it.value()); + const auto contextUids = todo->customProperty("Zanshin", "ContextList").split(',', QString::SkipEmptyParts); + QStringList contextNames; + std::transform(contextUids.cbegin(), contextUids.cend(), std::back_inserter(contextNames), [this](const QString &uid) { return m_contextTodos.value(uid); }); + contextNames.sort(); + if (contextNames != it.value()) // QCOMPARE for QStringList isn't verbose enough + qWarning() << it.key() << "got" << contextNames << "expected" << it.value(); + QCOMPARE(contextNames, it.value()); QVERIFY(!m_serializer.isContext(item)); } } static QStringList tagNames(const Akonadi::Tag::List &tags) { QStringList itemTags; - std::transform(tags.constBegin(), tags.constEnd(), std::back_inserter(itemTags), [](const Akonadi::Tag &tag) { return tag.gid(); }); + std::transform(tags.constBegin(), tags.constEnd(), std::back_inserter(itemTags), [](const Akonadi::Tag &tag) { return tag.name(); }); itemTags.sort(); return itemTags; } QMap contexts*/> m_expectedUids; Akonadi::Serializer m_serializer; + QHash m_contextTodos; + ZanshinContextItemsMigrator m_migrator; }; ZANSHIN_TEST_MAIN(ZanshinContextItemsMigrationTest) #include "zanshincontextitemsmigrationtest.moc"