diff --git a/autotests/server/CMakeLists.txt b/autotests/server/CMakeLists.txt index b644b7882..e25c96982 100644 --- a/autotests/server/CMakeLists.txt +++ b/autotests/server/CMakeLists.txt @@ -1,104 +1,104 @@ ########### next target ############### # QTEST_MAIN is using QApplication when QT_GUI_LIB is defined remove_definitions(-DQT_GUI_LIB) set(EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR}) include_directories(${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_BINARY_DIR}/src/server ${Akonadi_SOURCE_DIR}/src/server) akonadi_run_xsltproc( XSL ${Akonadi_SOURCE_DIR}/src/server/storage/schema.xsl XML ${CMAKE_CURRENT_SOURCE_DIR}/dbtest_data/unittest_schema.xml BASENAME unittestschema CLASSNAME UnitTestSchema ) akonadi_run_xsltproc( XSL ${CMAKE_CURRENT_SOURCE_DIR}/dbpopulator.xsl XML ${CMAKE_CURRENT_SOURCE_DIR}/dbtest_data/dbdata.xml BASENAME dbpopulator ) set_property(SOURCE ${CMAKE_CURRENT_BINARY_DIR}/dbpopulator.cpp PROPERTY SKIP_AUTOMOC TRUE) set(common_SRCS unittestschema.cpp fakeconnection.cpp fakedatastore.cpp fakeclient.cpp fakeakonadiserver.cpp fakesearchmanager.cpp fakeitemretrievalmanager.cpp dbinitializer.cpp inspectablenotificationcollector.cpp ${CMAKE_CURRENT_BINARY_DIR}/dbpopulator.cpp ) add_library(akonadi_unittest_common STATIC ${common_SRCS}) target_link_libraries(akonadi_unittest_common KF5AkonadiPrivate libakonadiserver Qt5::Core Qt5::DBus Qt5::Test Qt5::Sql Qt5::Network ) macro(add_server_test _source) set(_test ${_source} ../../src/server/akonadiserver_debug.cpp ../../src/server/akonadiserver_search_debug.cpp) get_filename_component(_name ${_source} NAME_WE) qt5_add_resources(_test dbtest_data/dbtest_data.qrc) add_executable(${_name} ${_test}) add_test(NAME AkonadiServer-${_name} COMMAND ${_name}) if (ENABLE_ASAN) set_tests_properties(AkonadiServer-${_name} PROPERTIES ENVIRONMENT ASAN_OPTIONS=symbolize=1 ) endif() set_tests_properties(AkonadiServer-${_name} PROPERTIES ENVIRONMENT "QT_HASH_SEED=0;QT_NO_CPU_FEATURE=sse4.2" ) target_link_libraries(${_name} akonadi_shared akonadi_unittest_common libakonadiserver KF5AkonadiPrivate Qt5::Core Qt5::DBus Qt5::Test Qt5::Sql Qt5::Network ${CMAKE_SHARED_LINKER_FLAGS_ASAN} ) endmacro() add_server_test(dbtypetest.cpp) add_server_test(dbintrospectortest.cpp) add_server_test(querybuildertest.cpp) add_server_test(dbinitializertest.cpp) add_server_test(dbupdatertest.cpp) add_server_test(handlertest.cpp) add_server_test(dbconfigtest.cpp) add_server_test(parthelpertest.cpp) add_server_test(itemretrievertest.cpp) add_server_test(notificationmanagertest.cpp) add_server_test(parttypehelpertest.cpp) add_server_test(collectionstatisticstest.cpp) if (SQLITE_FOUND) # tests using the fake server need the QSQLITE3 plugin add_server_test(partstreamertest.cpp) -add_server_test(akappendhandlertest.cpp) -add_server_test(linkhandlertest.cpp) -add_server_test(listhandlertest.cpp) -add_server_test(modifyhandlertest.cpp) -add_server_test(movehandlertest.cpp) -add_server_test(createhandlertest.cpp) +add_server_test(itemcreatehandlertest.cpp) +add_server_test(itemlinkhandlertest.cpp) +add_server_test(itemmovehandlertest.cpp) +add_server_test(collectioncreatehandlertest.cpp) +add_server_test(collectionfetchhandlertest.cpp) +add_server_test(collectionmodifyhandlertest.cpp) add_server_test(collectionreferencetest.cpp) add_server_test(searchtest.cpp akonadiprivate) add_server_test(relationhandlertest.cpp akonadiprivate) add_server_test(taghandlertest.cpp akonadiprivate) add_server_test(fetchhandlertest.cpp akonadiprivate) endif() diff --git a/autotests/server/createhandlertest.cpp b/autotests/server/collectioncreatehandlertest.cpp similarity index 97% rename from autotests/server/createhandlertest.cpp rename to autotests/server/collectioncreatehandlertest.cpp index 77265827d..98e94aadc 100644 --- a/autotests/server/createhandlertest.cpp +++ b/autotests/server/collectioncreatehandlertest.cpp @@ -1,194 +1,193 @@ /* Copyright (c) 2014 Christian Mollekopf This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include -#include #include #include "fakeakonadiserver.h" #include "dbinitializer.h" #include "aktest.h" #include "entities.h" #include #include using namespace Akonadi; using namespace Akonadi::Server; -class CreateHandlerTest : public QObject +class CollectionCreateHandlerTest : public QObject { Q_OBJECT public: - CreateHandlerTest() + CollectionCreateHandlerTest() { try { FakeAkonadiServer::instance()->init(); } catch (const FakeAkonadiServerException &e) { qWarning() << "Server exception: " << e.what(); qFatal("Fake Akonadi Server failed to start up, aborting test"); } } - ~CreateHandlerTest() + ~CollectionCreateHandlerTest() { FakeAkonadiServer::instance()->quit(); } private Q_SLOTS: void testCreate_data() { DbInitializer dbInitializer; QTest::addColumn("scenarios"); QTest::addColumn("notification"); auto notificationTemplate = Protocol::CollectionChangeNotificationPtr::create(); notificationTemplate->setOperation(Protocol::CollectionChangeNotification::Add); notificationTemplate->setParentCollection(3); notificationTemplate->setResource("akonadi_fake_resource_0"); notificationTemplate->setSessionId(FakeAkonadiServer::instanceName().toLatin1()); { auto cmd = Protocol::CreateCollectionCommandPtr::create(); cmd->setName(QStringLiteral("New Name")); cmd->setParent(Scope(3)); cmd->setAttributes({ { "MYRANDOMATTRIBUTE", "" } }); auto resp = Protocol::FetchCollectionsResponsePtr::create(8); resp->setName(QStringLiteral("New Name")); resp->setParentId(3); resp->setAttributes({ { "MYRANDOMATTRIBUTE", "" } }); resp->setResource(QStringLiteral("akonadi_fake_resource_0")); resp->cachePolicy().setLocalParts({ QLatin1String("ALL") }); resp->setMimeTypes({ QLatin1String("application/octet-stream"), QLatin1String("inode/directory") }); TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, cmd) << TestScenario::create(5, TestScenario::ServerCmd, resp) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateCollectionResponsePtr::create()); Protocol::FetchCollectionsResponse collection(*resp); auto notification = Protocol::CollectionChangeNotificationPtr::create(*notificationTemplate); notification->setCollection(std::move(collection)); QTest::newRow("create collection") << scenarios << notification; } { auto cmd = Protocol::CreateCollectionCommandPtr::create(); cmd->setName(QStringLiteral("Name 2")); cmd->setParent(Scope(3)); cmd->setEnabled(false); cmd->setDisplayPref(Tristate::True); cmd->setSyncPref(Tristate::True); cmd->setIndexPref(Tristate::True); auto resp = Protocol::FetchCollectionsResponsePtr::create(9); resp->setName(QStringLiteral("Name 2")); resp->setParentId(3); resp->setEnabled(false); resp->setDisplayPref(Tristate::True); resp->setSyncPref(Tristate::True); resp->setIndexPref(Tristate::True); resp->setResource(QStringLiteral("akonadi_fake_resource_0")); resp->cachePolicy().setLocalParts({ QLatin1String("ALL") }); resp->setMimeTypes({ QLatin1String("application/octet-stream"), QLatin1String("inode/directory") }); TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, cmd) << TestScenario::create(5, TestScenario::ServerCmd, resp) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateCollectionResponsePtr::create()); Protocol::FetchCollectionsResponse collection(*resp); auto notification = Protocol::CollectionChangeNotificationPtr::create(*notificationTemplate); notification->setCollection(std::move(collection)); QTest::newRow("create collection with local override") << scenarios << notification; } { auto cmd = Protocol::CreateCollectionCommandPtr::create(); cmd->setName(QStringLiteral("TopLevel")); cmd->setParent(Scope(0)); cmd->setMimeTypes({ QLatin1String("inode/directory") }); auto resp = Protocol::FetchCollectionsResponsePtr::create(10); resp->setName(QStringLiteral("TopLevel")); resp->setParentId(0); resp->setEnabled(true); resp->setMimeTypes({ QLatin1String("inode/directory") }); resp->cachePolicy().setLocalParts({ QLatin1String("ALL") }); resp->setResource(QStringLiteral("akonadi_fake_resource_0")); TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario("akonadi_fake_resource_0") << TestScenario::create(5, TestScenario::ClientCmd, cmd) << TestScenario::create(5, TestScenario::ServerCmd, resp) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateCollectionResponsePtr::create()); Protocol::FetchCollectionsResponse collection(*resp); auto notification = Protocol::CollectionChangeNotificationPtr::create(*notificationTemplate); notification->setSessionId("akonadi_fake_resource_0"); notification->setParentCollection(0); notification->setCollection(std::move(collection)); QTest::newRow("create top-level collection") << scenarios << notification; } } void testCreate() { QFETCH(TestScenario::List, scenarios); QFETCH(Protocol::CollectionChangeNotificationPtr, notification); FakeAkonadiServer::instance()->setScenarios(scenarios); FakeAkonadiServer::instance()->runTest(); auto notificationSpy = FakeAkonadiServer::instance()->notificationSpy(); if (notification->operation() != Protocol::CollectionChangeNotification::InvalidOp) { QCOMPARE(notificationSpy->count(), 1); const auto notifications = notificationSpy->takeFirst().first().value(); QCOMPARE(notifications.count(), 1); const auto actualNtf = notifications.first().staticCast(); if (*actualNtf != *notification) { qDebug() << "Actual: " << Protocol::debugString(actualNtf); qDebug() << "Expected:" << Protocol::debugString(notification); } QCOMPARE(*notifications.first().staticCast(), *notification); } else { QVERIFY(notificationSpy->isEmpty() || notificationSpy->takeFirst().first().value().isEmpty()); } } }; -AKTEST_FAKESERVER_MAIN(CreateHandlerTest) +AKTEST_FAKESERVER_MAIN(CollectionCreateHandlerTest) -#include "createhandlertest.moc" +#include "collectioncreatehandlertest.moc" diff --git a/autotests/server/listhandlertest.cpp b/autotests/server/collectionfetchhandlertest.cpp similarity index 99% rename from autotests/server/listhandlertest.cpp rename to autotests/server/collectionfetchhandlertest.cpp index 94bf41786..7567b83ca 100644 --- a/autotests/server/listhandlertest.cpp +++ b/autotests/server/collectionfetchhandlertest.cpp @@ -1,591 +1,588 @@ /* Copyright (c) 2014 Christian Mollekopf This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include -#include - - #include #include "fakeakonadiserver.h" #include "aktest.h" #include "entities.h" #include "dbinitializer.h" #include #include using namespace Akonadi; using namespace Akonadi::Server; -class ListHandlerTest : public QObject +class CollectionFetchHandlerTest : public QObject { Q_OBJECT public: - ListHandlerTest() + CollectionFetchHandlerTest() : QObject() { try { FakeAkonadiServer::instance()->setPopulateDb(false); FakeAkonadiServer::instance()->init(); } catch (const FakeAkonadiServerException &e) { qWarning() << "Server exception: " << e.what(); qFatal("Fake Akonadi Server failed to start up, aborting test"); } { MimeType mt(QStringLiteral("mimetype1")); mt.insert(); } { MimeType mt(QStringLiteral("mimetype2")); mt.insert(); } { MimeType mt(QStringLiteral("mimetype3")); mt.insert(); } { MimeType mt(QStringLiteral("mimetype4")); mt.insert(); } } - ~ListHandlerTest() + ~CollectionFetchHandlerTest() { FakeAkonadiServer::instance()->quit(); } Protocol::FetchCollectionsCommandPtr createCommand(const Scope &scope, Protocol::FetchCollectionsCommand::Depth depth = Akonadi::Protocol::FetchCollectionsCommand::BaseCollection, Protocol::Ancestor::Depth ancDepth = Protocol::Ancestor::NoAncestor, const QStringList &mimeTypes = QStringList(), const QString &resource = QString()) { auto cmd = Protocol::FetchCollectionsCommandPtr::create(scope); cmd->setDepth(depth); cmd->setAncestorsDepth(ancDepth); cmd->setMimeTypes(mimeTypes); cmd->setResource(resource); return cmd; } QScopedPointer initializer; private Q_SLOTS: void testList_data() { initializer.reset(new DbInitializer); Resource res = initializer->createResource("testresource"); Collection col1 = initializer->createCollection("col1"); Collection col2 = initializer->createCollection("col2", col1); Collection col3 = initializer->createCollection("col3", col2); Collection col4 = initializer->createCollection("col4"); QTest::addColumn("scenarios"); { TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, createCommand(Scope(), Protocol::FetchCollectionsCommand::AllCollections)) << TestScenario::create(5, TestScenario::ServerCmd, initializer->listResponse(initializer->collection("Search"))) << TestScenario::create(5, TestScenario::ServerCmd, initializer->listResponse(col1)) << TestScenario::create(5, TestScenario::ServerCmd, initializer->listResponse(col2)) << TestScenario::create(5, TestScenario::ServerCmd, initializer->listResponse(col3)) << TestScenario::create(5, TestScenario::ServerCmd, initializer->listResponse(col4)) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::FetchCollectionsResponsePtr::create()); QTest::newRow("recursive list") << scenarios; } { TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, createCommand(col1.id())) << TestScenario::create(5, TestScenario::ServerCmd, initializer->listResponse(col1)) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::FetchCollectionsResponsePtr::create()); QTest::newRow("base list") << scenarios; } { TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, createCommand(col1.id(), Protocol::FetchCollectionsCommand::ParentCollection)) << TestScenario::create(5, TestScenario::ServerCmd, initializer->listResponse(col2)) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::FetchCollectionsResponsePtr::create()); QTest::newRow("first level list") << scenarios; } { TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, createCommand(col1.id(), Protocol::FetchCollectionsCommand::AllCollections)) << TestScenario::create(5, TestScenario::ServerCmd, initializer->listResponse(col2)) << TestScenario::create(5, TestScenario::ServerCmd, initializer->listResponse(col3)) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::FetchCollectionsResponsePtr::create()); QTest::newRow("recursive list that filters collection") << scenarios; } { TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, createCommand(col2.id(), Protocol::FetchCollectionsCommand::BaseCollection, Protocol::Ancestor::AllAncestors)) << TestScenario::create(5, TestScenario::ServerCmd, initializer->listResponse(col2, true)) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::FetchCollectionsResponsePtr::create()); QTest::newRow("base ancestors") << scenarios; } { TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, createCommand(col2.id(), Protocol::FetchCollectionsCommand::BaseCollection, Protocol::Ancestor::AllAncestors)) << TestScenario::create(5, TestScenario::ServerCmd, initializer->listResponse(col2, true)) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::FetchCollectionsResponsePtr::create()); QTest::newRow("first level ancestors") << scenarios; } { TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, createCommand(col1.id(), Protocol::FetchCollectionsCommand::AllCollections, Protocol::Ancestor::AllAncestors)) << TestScenario::create(5, TestScenario::ServerCmd, initializer->listResponse(col2, true)) << TestScenario::create(5, TestScenario::ServerCmd, initializer->listResponse(col3, true)) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::FetchCollectionsResponsePtr::create()); QTest::newRow("recursive ancestors") << scenarios; } { TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, createCommand(Scope(), Protocol::FetchCollectionsCommand::ParentCollection)) << TestScenario::create(5, TestScenario::ServerCmd, initializer->listResponse(initializer->collection("Search"))) << TestScenario::create(5, TestScenario::ServerCmd, initializer->listResponse(col1)) << TestScenario::create(5, TestScenario::ServerCmd, initializer->listResponse(col4)) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::FetchCollectionsResponsePtr::create()); QTest::newRow("first level root list") << scenarios; } } void testList() { QFETCH(TestScenario::List, scenarios); FakeAkonadiServer::instance()->setScenarios(scenarios); FakeAkonadiServer::instance()->runTest(); } void testListFiltered_data() { initializer.reset(new DbInitializer); MimeType mtCalendar(QStringLiteral("text/calendar")); mtCalendar.insert(); Resource res = initializer->createResource("testresource"); Collection col1 = initializer->createCollection("col1"); col1.update(); Collection col2 = initializer->createCollection("col2", col1); col2.addMimeType(mtCalendar); col2.update(); QTest::addColumn("scenarios"); { TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, createCommand(Scope(), Protocol::FetchCollectionsCommand::AllCollections, Protocol::Ancestor::NoAncestor, { QLatin1String("text/calendar") })) << TestScenario::create(5, TestScenario::ServerCmd, initializer->listResponse(col1, false, false)) << TestScenario::create(5, TestScenario::ServerCmd, initializer->listResponse(col2)) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::FetchCollectionsResponsePtr::create()); QTest::newRow("recursive list to display including local override") << scenarios; } } void testListFiltered() { QFETCH(TestScenario::List, scenarios); FakeAkonadiServer::instance()->setScenarios(scenarios); FakeAkonadiServer::instance()->runTest(); } void testListFilterByResource() { initializer.reset(new DbInitializer); Resource res2; res2.setName(QStringLiteral("testresource2")); QVERIFY(res2.insert()); Resource res = initializer->createResource("testresource"); Collection col1 = initializer->createCollection("col1"); Collection col2; col2.setName(QStringLiteral("col2")); col2.setRemoteId(QStringLiteral("col2")); col2.setResource(res2); QVERIFY(col2.insert()); TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, createCommand(Scope(), Protocol::FetchCollectionsCommand::AllCollections, Protocol::Ancestor::NoAncestor, {}, QStringLiteral("testresource"))) << TestScenario::create(5, TestScenario::ServerCmd, initializer->listResponse(col1)) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::FetchCollectionsResponsePtr::create()); FakeAkonadiServer::instance()->setScenarios(scenarios); FakeAkonadiServer::instance()->runTest(); col2.remove(); res2.remove(); } void testListEnabled_data() { initializer.reset(new DbInitializer); Resource res = initializer->createResource("testresource"); Collection col1 = initializer->createCollection("col1"); Collection col2 = initializer->createCollection("col2", col1); col2.setEnabled(false); col2.setSyncPref(Collection::True); col2.setDisplayPref(Collection::True); col2.setIndexPref(Collection::True); col2.update(); Collection col3 = initializer->createCollection("col3", col2); col3.setEnabled(true); col3.setSyncPref(Collection::False); col3.setDisplayPref(Collection::False); col3.setIndexPref(Collection::False); col3.update(); QTest::addColumn("scenarios"); { TestScenario::List scenarios; auto cmd = createCommand(col3.id()); cmd->setDisplayPref(true); scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, cmd) << TestScenario::create(5, TestScenario::ServerCmd, initializer->listResponse(col3)) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::FetchCollectionsResponsePtr::create()); //Listing a disabled collection should still work for base listing QTest::newRow("list base of disabled collection") << scenarios; } { TestScenario::List scenarios; auto cmd = createCommand(Scope(), Protocol::FetchCollectionsCommand::AllCollections); cmd->setDisplayPref(true); scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, cmd) << TestScenario::create(5, TestScenario::ServerCmd, initializer->listResponse(initializer->collection("Search"))) << TestScenario::create(5, TestScenario::ServerCmd, initializer->listResponse(col1)) << TestScenario::create(5, TestScenario::ServerCmd, initializer->listResponse(col2)) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::FetchCollectionsResponsePtr::create()); QTest::newRow("recursive list to display including local override") << scenarios; } { TestScenario::List scenarios; auto cmd = createCommand(Scope(), Protocol::FetchCollectionsCommand::AllCollections); cmd->setSyncPref(true); scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, cmd) << TestScenario::create(5, TestScenario::ServerCmd, initializer->listResponse(initializer->collection("Search"))) << TestScenario::create(5, TestScenario::ServerCmd, initializer->listResponse(col1)) << TestScenario::create(5, TestScenario::ServerCmd, initializer->listResponse(col2)) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::FetchCollectionsResponsePtr::create()); QTest::newRow("recursive list to sync including local override") << scenarios; } { TestScenario::List scenarios; auto cmd = createCommand(Scope(), Protocol::FetchCollectionsCommand::AllCollections); cmd->setIndexPref(true); scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, cmd) << TestScenario::create(5, TestScenario::ServerCmd, initializer->listResponse(initializer->collection("Search"))) << TestScenario::create(5, TestScenario::ServerCmd, initializer->listResponse(col1)) << TestScenario::create(5, TestScenario::ServerCmd, initializer->listResponse(col2)) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::FetchCollectionsResponsePtr::create()); QTest::newRow("recursive list to index including local override") << scenarios; } { TestScenario::List scenarios; auto cmd = createCommand(Scope(), Protocol::FetchCollectionsCommand::AllCollections); cmd->setEnabled(true); scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, cmd) << TestScenario::create(5, TestScenario::ServerCmd, initializer->listResponse(initializer->collection("Search"))) << TestScenario::create(5, TestScenario::ServerCmd, initializer->listResponse(col1)) << TestScenario::create(5, TestScenario::ServerCmd, initializer->listResponse(col2)) << TestScenario::create(5, TestScenario::ServerCmd, initializer->listResponse(col3)) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::FetchCollectionsResponsePtr::create()); QTest::newRow("recursive list of enabled") << scenarios; } } void testListEnabled() { QFETCH(TestScenario::List, scenarios); FakeAkonadiServer::instance()->setScenarios(scenarios); FakeAkonadiServer::instance()->runTest(); } void testListAttribute_data() { initializer.reset(new DbInitializer); Resource res = initializer->createResource("testresource"); Collection col1 = initializer->createCollection("col1"); Collection col2 = initializer->createCollection("col2"); CollectionAttribute attr1; attr1.setType("type"); attr1.setValue("value"); attr1.setCollection(col1); attr1.insert(); CollectionAttribute attr2; attr2.setType("type"); attr2.setValue(QStringLiteral("Umlautäöü").toUtf8()); attr2.setCollection(col2); attr2.insert(); QTest::addColumn("scenarios"); { TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, createCommand(col1.id())) << TestScenario::create(5, TestScenario::ServerCmd, initializer->listResponse(col1, false, true)) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::FetchCollectionsResponsePtr::create()); QTest::newRow("list attribute") << scenarios; } { TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, createCommand(col2.id())) << TestScenario::create(5, TestScenario::ServerCmd, initializer->listResponse(col2, false, true)) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::FetchCollectionsResponsePtr::create()); QTest::newRow("list attribute") << scenarios; } } void testListAttribute() { QFETCH(TestScenario::List, scenarios); FakeAkonadiServer::instance()->setScenarios(scenarios); FakeAkonadiServer::instance()->runTest(); } void testListAncestorAttributes_data() { initializer.reset(new DbInitializer); Resource res = initializer->createResource("testresource"); Collection col1 = initializer->createCollection("col1"); CollectionAttribute attr1; attr1.setType("type"); attr1.setValue("value"); attr1.setCollection(col1); attr1.insert(); Collection col2 = initializer->createCollection("col2", col1); QTest::addColumn("scenarios"); { TestScenario::List scenarios; auto cmd = createCommand(col2.id(), Protocol::FetchCollectionsCommand::BaseCollection, Protocol::Ancestor::AllAncestors); cmd->setAncestorsAttributes({ "type" }); scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, cmd) << TestScenario::create(5, TestScenario::ServerCmd, initializer->listResponse(col2, true, true, { QLatin1String("type") })) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::FetchCollectionsResponsePtr::create()); QTest::newRow("list ancestor attribute with fetch scope") << scenarios; } } void testListAncestorAttributes() { QFETCH(TestScenario::List, scenarios); FakeAkonadiServer::instance()->setScenarios(scenarios); FakeAkonadiServer::instance()->runTest(); } void testIncludeAncestors_data() { //The collection we are querying contains a load of disabled collections (typical scenario with many shared folders) //The collection we are NOT querying contains a reasonable amount of enabled collections (to test the performance impact of the manually filtering by tree) initializer.reset(new DbInitializer); Resource res = initializer->createResource("testresource"); MimeType mtDirectory = MimeType::retrieveByName(QStringLiteral("mimetype1")); Collection col1 = initializer->createCollection("col1"); col1.addMimeType(mtDirectory); col1.update(); Collection col2 = initializer->createCollection("col2", col1); Collection col3 = initializer->createCollection("col3", col2); Collection col4 = initializer->createCollection("col4", col3); col4.addMimeType(mtDirectory); col4.update(); QTest::addColumn("scenarios"); { TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, createCommand(Scope(), Protocol::FetchCollectionsCommand::AllCollections, Protocol::Ancestor::NoAncestor, { QLatin1String("mimetype1") })) << TestScenario::create(5, TestScenario::ServerCmd, initializer->listResponse(col1)) << TestScenario::create(5, TestScenario::ServerCmd, initializer->listResponse(col2)) << TestScenario::create(5, TestScenario::ServerCmd, initializer->listResponse(col3)) << TestScenario::create(5, TestScenario::ServerCmd, initializer->listResponse(col4)) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::FetchCollectionsResponsePtr::create()); QTest::newRow("ensure filtered grandparent is included") << scenarios; } { TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, createCommand(col1.id(), Protocol::FetchCollectionsCommand::AllCollections, Protocol::Ancestor::NoAncestor, { QLatin1String("mimetype1") })) << TestScenario::create(5, TestScenario::ServerCmd, initializer->listResponse(col2)) << TestScenario::create(5, TestScenario::ServerCmd, initializer->listResponse(col3)) << TestScenario::create(5, TestScenario::ServerCmd, initializer->listResponse(col4)) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::FetchCollectionsResponsePtr::create()); //This also ensures col1 is excluded although it matches the mimetype filter QTest::newRow("ensure filtered grandparent is included with specified parent") << scenarios; } } void testIncludeAncestors() { QFETCH(TestScenario::List, scenarios); FakeAkonadiServer::instance()->setScenarios(scenarios); FakeAkonadiServer::instance()->runTest(); } //No point in running the benchmark every time #if 0 void testListEnabledBenchmark_data() { //The collection we are quering contains a load of disabled collections (typical scenario with many shared folders) //The collection we are NOT querying contains a reasonable amount of enabled collections (to test the performance impact of the manually filtering by tree) initializer.reset(new DbInitializer); Resource res = initializer->createResource("testresource"); Collection toplevel = initializer->createCollection("toplevel"); Collection col1 = initializer->createCollection("col1", toplevel); Collection col2 = initializer->createCollection("col2", col1); Collection col3 = initializer->createCollection("col3", col2); Collection col4 = initializer->createCollection("col4", toplevel); Collection col5 = initializer->createCollection("col5", col4); col5.setEnabled(false); col5.update(); Collection col6 = initializer->createCollection("col6", col5); col5.setEnabled(false); col5.update(); MimeType mt1 = MimeType::retrieveByName(QLatin1String("mimetype1")); MimeType mt2 = MimeType::retrieveByName(QLatin1String("mimetype2")); MimeType mt3 = MimeType::retrieveByName(QLatin1String("mimetype3")); MimeType mt4 = MimeType::retrieveByName(QLatin1String("mimetype4")); QTime t; t.start(); for (int i = 0; i < 100000; i++) { QByteArray name = QString::fromLatin1("col%1").arg(i+4).toLatin1(); Collection col = initializer->createCollection(name.data(), col3); col.setEnabled(false); col.addMimeType(mt1); col.addMimeType(mt2); col.addMimeType(mt3); col.addMimeType(mt4); col.update(); } for (int i = 0; i < 1000; i++) { QByteArray name = QString::fromLatin1("col%1").arg(i+100004).toLatin1(); Collection col = initializer->createCollection(name.data(), col5); col.addMimeType(mt1); col.addMimeType(mt2); col.update(); } qDebug() << "Created 100000 collections in" << t.elapsed() << "msecs"; QTest::addColumn("scenarios"); // { // QList scenario; // scenario << FakeAkonadiServer::defaultScenario() // << "C: 2 LIST " + QByteArray::number(toplevel.id()) + " INF (ENABLED ) ()" // << "S: IGNORE 1006" // << "S: 2 OK List completed"; // QTest::newRow("recursive list of enabled") << scenario; // } // { // QList scenario; // scenario << FakeAkonadiServer::defaultScenario() // << "C: 2 LIST " + QByteArray::number(toplevel.id()) + " INF (MIMETYPE (mimetype1) RESOURCE \"testresource\") ()" // // << "C: 2 LIST " + QByteArray::number(0) + " INF (RESOURCE \"testresource\") ()" // << "S: IGNORE 101005" // << "S: 2 OK List completed"; // QTest::newRow("recursive list filtered by mimetype") << scenario; // } { TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, createCommand(toplevel.id(), Protocol::FetchCollectionsCommand::AllCollections, Protocol::Ancestor::AllAncestors, { QLatin1String("mimetype1") }, QLatin1String("testresource"))) << TestScenario::ignore(101005) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::FetchCollectionsResponse()); QTest::newRow("recursive list filtered by mimetype with ancestors") << scenarios; } } void testListEnabledBenchmark() { QFETCH(TestScenario::List, scenarios); // StorageDebugger::instance()->enableSQLDebugging(true); // StorageDebugger::instance()->writeToFile(QLatin1String("sqllog.txt")); QBENCHMARK { FakeAkonadiServer::instance()->setScenarios(scenarios); FakeAkonadiServer::instance()->runTest(); } } #endif }; -AKTEST_FAKESERVER_MAIN(ListHandlerTest) +AKTEST_FAKESERVER_MAIN(CollectionFetchHandlerTest) -#include "listhandlertest.moc" +#include "collectionfetchhandlertest.moc" diff --git a/autotests/server/modifyhandlertest.cpp b/autotests/server/collectionmodifyhandlertest.cpp similarity index 97% rename from autotests/server/modifyhandlertest.cpp rename to autotests/server/collectionmodifyhandlertest.cpp index ef6fb77d5..f45384b53 100644 --- a/autotests/server/modifyhandlertest.cpp +++ b/autotests/server/collectionmodifyhandlertest.cpp @@ -1,206 +1,205 @@ /* Copyright (c) 2014 Christian Mollekopf This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include -#include #include #include "fakeakonadiserver.h" #include "aktest.h" #include "entities.h" #include #include #include using namespace Akonadi; using namespace Akonadi::Server; -class ModifyHandlerTest : public QObject +class CollectionModifyHandlerTest : public QObject { Q_OBJECT public: - ModifyHandlerTest() + CollectionModifyHandlerTest() { try { FakeAkonadiServer::instance()->init(); } catch (const FakeAkonadiServerException &e) { qWarning() << "Server exception: " << e.what(); qFatal("Fake Akonadi Server failed to start up, aborting test"); } } - ~ModifyHandlerTest() + ~CollectionModifyHandlerTest() { FakeAkonadiServer::instance()->quit(); } private Q_SLOTS: void testModify_data() { QTest::addColumn("scenarios"); QTest::addColumn("expectedNotifications"); QTest::addColumn("newValue"); auto notificationTemplate = Protocol::CollectionChangeNotificationPtr::create(); notificationTemplate->setOperation(Protocol::CollectionChangeNotification::Modify); notificationTemplate->setParentCollection(4); notificationTemplate->setResource("akonadi_fake_resource_0"); notificationTemplate->setSessionId(FakeAkonadiServer::instanceName().toLatin1()); auto collectionTemplate = Protocol::FetchCollectionsResponsePtr::create(); collectionTemplate->setId(5); collectionTemplate->setRemoteId(QStringLiteral("ColD")); collectionTemplate->setRemoteRevision(QStringLiteral("")); collectionTemplate->setName(QStringLiteral("New Name")); collectionTemplate->setParentId(4); collectionTemplate->setResource(QStringLiteral("akonadi_fake_resource_0")); { auto cmd = Protocol::ModifyCollectionCommandPtr::create(5); cmd->setName(QStringLiteral("New Name")); TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, cmd) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::ModifyCollectionResponsePtr::create()); auto notification = Protocol::CollectionChangeNotificationPtr::create(*notificationTemplate); notification->setChangedParts(QSet() << "NAME"); notification->setCollection(*collectionTemplate); QTest::newRow("modify collection") << scenarios << Protocol::ChangeNotificationList{ notification } << QVariant::fromValue(QStringLiteral("New Name")); } { auto cmd = Protocol::ModifyCollectionCommandPtr::create(5); cmd->setEnabled(false); TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, cmd) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::ModifyCollectionResponsePtr::create()); Protocol::FetchCollectionsResponse collection(*collectionTemplate); collection.setEnabled(false); auto notification = Protocol::CollectionChangeNotificationPtr::create(*notificationTemplate); notification->setChangedParts(QSet() << "ENABLED"); notification->setCollection(collection); auto unsubscribeNotification = Protocol::CollectionChangeNotificationPtr::create(*notificationTemplate); unsubscribeNotification->setOperation(Protocol::CollectionChangeNotification::Unsubscribe); unsubscribeNotification->setCollection(std::move(collection)); QTest::newRow("disable collection") << scenarios << Protocol::ChangeNotificationList{ notification, unsubscribeNotification} << QVariant::fromValue(false); } { auto cmd = Protocol::ModifyCollectionCommandPtr::create(5); cmd->setEnabled(true); TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, cmd) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::ModifyCollectionResponsePtr::create()); auto notification = Protocol::CollectionChangeNotificationPtr::create(*notificationTemplate); notification->setChangedParts(QSet() << "ENABLED"); notification->setCollection(*collectionTemplate); auto subscribeNotification = Protocol::CollectionChangeNotificationPtr::create(*notificationTemplate); subscribeNotification->setOperation(Protocol::CollectionChangeNotification::Subscribe); subscribeNotification->setCollection(*collectionTemplate); QTest::newRow("enable collection") << scenarios << Protocol::ChangeNotificationList{ notification, subscribeNotification } << QVariant::fromValue(true); } { auto cmd = Protocol::ModifyCollectionCommandPtr::create(5); cmd->setEnabled(false); cmd->setSyncPref(Tristate::True); cmd->setDisplayPref(Tristate::True); cmd->setIndexPref(Tristate::True); TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, cmd) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::ModifyCollectionResponsePtr::create()); Protocol::FetchCollectionsResponse collection(*collectionTemplate); collection.setEnabled(false); collection.setSyncPref(Tristate::True); collection.setDisplayPref(Tristate::True); collection.setIndexPref(Tristate::True); auto notification = Protocol::CollectionChangeNotificationPtr::create(*notificationTemplate); notification->setChangedParts(QSet() << "ENABLED" << "SYNC" << "DISPLAY" << "INDEX"); notification->setCollection(collection); auto unsubscribeNotification = Protocol::CollectionChangeNotificationPtr::create(*notificationTemplate); unsubscribeNotification->setOperation(Protocol::CollectionChangeNotification::Unsubscribe); unsubscribeNotification->setCollection(std::move(collection)); QTest::newRow("local override enable") << scenarios << Protocol::ChangeNotificationList{ notification, unsubscribeNotification } << QVariant::fromValue(true); } } void testModify() { QFETCH(TestScenario::List, scenarios); QFETCH(Protocol::ChangeNotificationList, expectedNotifications); QFETCH(QVariant, newValue); FakeAkonadiServer::instance()->setScenarios(scenarios); FakeAkonadiServer::instance()->runTest(); auto notificationSpy = FakeAkonadiServer::instance()->notificationSpy(); if (expectedNotifications.isEmpty()) { QVERIFY(notificationSpy->isEmpty() || notificationSpy->takeFirst().first().value().isEmpty()); return; } QCOMPARE(notificationSpy->count(), 1); //Only one notify call QCOMPARE(notificationSpy->first().count(), 1); const auto receivedNotifications = notificationSpy->first().first().value(); QCOMPARE(receivedNotifications.size(), expectedNotifications.count()); for (int i = 0; i < expectedNotifications.size(); i++) { const auto recvNtf = receivedNotifications.at(i).staticCast(); const auto expNtf = expectedNotifications.at(i).staticCast(); if (*recvNtf != *expNtf) { qDebug() << "Actual: " << Protocol::debugString(recvNtf); qDebug() << "Expected:" << Protocol::debugString(expNtf); } QCOMPARE(*recvNtf, *expNtf); const auto notification = receivedNotifications.at(i).staticCast(); if (notification->changedParts().contains("NAME")) { Collection col = Collection::retrieveById(notification->collection().id()); QCOMPARE(col.name(), newValue.toString()); } if (!notification->changedParts().intersects({ "ENABLED", "SYNC", "DISPLAY", "INDEX" })) { Collection col = Collection::retrieveById(notification->collection().id()); const bool sync = col.syncPref() == Collection::Undefined ? col.enabled() : col.syncPref() == Collection::True; QCOMPARE(sync, newValue.toBool()); const bool display = col.displayPref() == Collection::Undefined ? col.enabled() : col.displayPref() == Collection::True; QCOMPARE(display, newValue.toBool()); const bool index = col.indexPref() == Collection::Undefined ? col.enabled() : col.indexPref() == Collection::True; QCOMPARE(index, newValue.toBool()); } } } }; -AKTEST_FAKESERVER_MAIN(ModifyHandlerTest) +AKTEST_FAKESERVER_MAIN(CollectionModifyHandlerTest) -#include "modifyhandlertest.moc" +#include "collectionmodifyhandlertest.moc" diff --git a/autotests/server/collectionreferencetest.cpp b/autotests/server/collectionreferencetest.cpp index 3d7d95acb..c22f61ccd 100644 --- a/autotests/server/collectionreferencetest.cpp +++ b/autotests/server/collectionreferencetest.cpp @@ -1,270 +1,269 @@ /* Copyright (c) 2014 Christian Mollekopf This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include -#include #include #include "fakeakonadiserver.h" #include "fakedatastore.h" #include #include "entities.h" #include "collectionreferencemanager.h" #include "dbinitializer.h" #include #include using namespace Akonadi; using namespace Akonadi::Server; Q_DECLARE_METATYPE(Collection::List) class CollectionReferenceTest : public QObject { Q_OBJECT DbInitializer initializer; public: CollectionReferenceTest() { try { FakeAkonadiServer::instance()->setPopulateDb(false); FakeAkonadiServer::instance()->init(); } catch (const FakeAkonadiServerException &e) { qWarning() << "Server exception: " << e.what(); qFatal("Fake Akonadi Server failed to start up, aborting test"); } initializer.createResource("testresource"); initializer.createCollection("col1"); Collection col2 = initializer.createCollection("col2"); col2.setEnabled(false); col2.update(); } ~CollectionReferenceTest() { FakeAkonadiServer::instance()->quit(); } private Q_SLOTS: void testModify_data() { QTest::addColumn("scenarios"); QTest::addColumn("expectedNotifications"); auto notificationTemplate = Protocol::CollectionChangeNotificationPtr::create(); notificationTemplate->setOperation(Protocol::CollectionChangeNotification::Modify); notificationTemplate->setParentCollection(0); notificationTemplate->setResource("testresource"); notificationTemplate->setSessionId(FakeAkonadiServer::instanceName().toLatin1()); Protocol::FetchCollectionsResponse collection; collection.setId(initializer.collection("col2").id()); collection.setRemoteId(QStringLiteral("col2")); collection.setRemoteRevision(QStringLiteral("")); notificationTemplate->setCollection(std::move(collection)); { auto cmd = Protocol::FetchCollectionsCommandPtr::create(); cmd->setDepth(Protocol::FetchCollectionsCommand::AllCollections); cmd->setResource(QStringLiteral("testresource")); cmd->setEnabled(true); TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, cmd) << TestScenario::create(5, TestScenario::ServerCmd, initializer.listResponse(initializer.collection("col1"))) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::FetchCollectionsResponsePtr::create()); QTest::newRow("list before referenced first level") << scenarios << Protocol::ChangeNotificationList(); } { auto cmd = Protocol::ModifyCollectionCommandPtr::create(initializer.collection("col2").id()); cmd->setReferenced(true); TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, cmd) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::ModifyCollectionResponsePtr::create()); auto notification = Protocol::CollectionChangeNotificationPtr::create(*notificationTemplate); notification->setChangedParts(QSet() << "REFERENCED"); QTest::newRow("reference") << scenarios << (Protocol::ChangeNotificationList() << notification); } { auto cmd = Protocol::ModifyCollectionCommandPtr::create(initializer.collection("col2").id()); cmd->setReferenced(true); auto listCmd = Protocol::FetchCollectionsCommandPtr::create(initializer.collection("col2").id()); listCmd->setDepth(Protocol::FetchCollectionsCommand::BaseCollection); listCmd->setEnabled(true); Collection col2 = initializer.collection("col2"); col2.setReferenced(true); TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, cmd) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::ModifyCollectionResponsePtr::create()) << TestScenario::create(6, TestScenario::ClientCmd, listCmd) << TestScenario::create(6, TestScenario::ServerCmd, initializer.listResponse(col2)) << TestScenario::create(6, TestScenario::ServerCmd, Protocol::FetchCollectionsResponsePtr::create()); auto notification = Protocol::CollectionChangeNotificationPtr::create(*notificationTemplate); notification->setChangedParts(QSet() << "REFERENCED"); QTest::newRow("list referenced base") << scenarios << (Protocol::ChangeNotificationList() << notification); } { auto cmd = Protocol::ModifyCollectionCommandPtr::create(initializer.collection("col2").id()); cmd->setReferenced(true); auto listCmd = Protocol::FetchCollectionsCommandPtr::create(); listCmd->setResource(QStringLiteral("testresource")); listCmd->setEnabled(true); listCmd->setDepth(Protocol::FetchCollectionsCommand::ParentCollection); Collection col2 = initializer.collection("col2"); col2.setReferenced(true); TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, cmd) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::ModifyCollectionResponsePtr::create()) << TestScenario::create(6, TestScenario::ClientCmd, listCmd) << TestScenario::create(6, TestScenario::ServerCmd, initializer.listResponse(initializer.collection("col1"))) << TestScenario::create(6, TestScenario::ServerCmd, initializer.listResponse(col2)) << TestScenario::create(6, TestScenario::ServerCmd, Protocol::FetchCollectionsResponsePtr::create()); auto notification = Protocol::CollectionChangeNotificationPtr::create(*notificationTemplate); notification->setChangedParts(QSet() << "REFERENCED"); QTest::newRow("list referenced first level") << scenarios << (Protocol::ChangeNotificationList() << notification); } { auto cmd1 = Protocol::ModifyCollectionCommandPtr::create(initializer.collection("col2").id()); cmd1->setReferenced(true); auto cmd2 = Protocol::ModifyCollectionCommandPtr::create(initializer.collection("col2").id()); cmd2->setReferenced(false); TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, cmd1) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::ModifyCollectionResponsePtr::create()) << TestScenario::create(6, TestScenario::ClientCmd, cmd2) << TestScenario::create(6, TestScenario::ServerCmd, Protocol::ModifyCollectionResponsePtr::create()); auto notification = Protocol::CollectionChangeNotificationPtr::create(*notificationTemplate); notification->setChangedParts(QSet() << "REFERENCED"); QTest::newRow("dereference") << scenarios << (Protocol::ChangeNotificationList() << notification << notification); } } void testModify() { QFETCH(TestScenario::List, scenarios); QFETCH(Protocol::ChangeNotificationList, expectedNotifications); // Clean all references from previous run CollectionReferenceManager::cleanup(); FakeAkonadiServer::instance()->setScenarios(scenarios); FakeAkonadiServer::instance()->runTest(); auto notificationSpy = FakeAkonadiServer::instance()->notificationSpy(); if (expectedNotifications.isEmpty()) { QTRY_VERIFY(notificationSpy->isEmpty() || notificationSpy->takeFirst().first().value().isEmpty()); } else { Protocol::ChangeNotificationList receivedNotifications; for (int q = 0; q < notificationSpy->size(); q++) { //Only one notify call QCOMPARE(notificationSpy->first().count(), 1); const Protocol::ChangeNotificationList n = notificationSpy->first().first().value(); for (int i = 0; i < n.size(); i++) { receivedNotifications.append(n.at(i)); } } QCOMPARE(receivedNotifications.size(), expectedNotifications.count()); for (int i = 0; i < expectedNotifications.size(); i++) { QCOMPARE(*receivedNotifications.at(i), *expectedNotifications.at(i)); } } } void testReferenceCollection() { Collection col = initializer.createCollection("testReferenceCollection"); CollectionReferenceManager::instance()->referenceCollection("testReferenceCollectionSession", col, true); QVERIFY(CollectionReferenceManager::instance()->isReferenced(col.id())); QVERIFY(CollectionReferenceManager::instance()->isReferenced(col.id(), "testReferenceCollectionSession")); CollectionReferenceManager::instance()->referenceCollection("foobar", col, false); QVERIFY(CollectionReferenceManager::instance()->isReferenced(col.id())); QVERIFY(CollectionReferenceManager::instance()->isReferenced(col.id(), "testReferenceCollectionSession")); CollectionReferenceManager::instance()->referenceCollection("testReferenceCollectionSession", col, false); QVERIFY(!CollectionReferenceManager::instance()->isReferenced(col.id())); QVERIFY(!CollectionReferenceManager::instance()->isReferenced(col.id(), "testReferenceCollectionSession")); QVERIFY(col.remove()); } void testSessionClosed() { Collection col = initializer.createCollection("testSessionCollection"); col.setReferenced(true); QVERIFY(col.update()); CollectionReferenceManager::instance()->referenceCollection("testSessionClosedSession", col, true); CollectionReferenceManager::instance()->referenceCollection("testSessionClosedSession2", col, true); //Remove first session CollectionReferenceManager::instance()->removeSession("testSessionClosedSession2"); QVERIFY(Collection::retrieveById(col.id()).referenced()); QVERIFY(!CollectionReferenceManager::instance()->isReferenced(col.id(), "testSessionClosedSession2")); QVERIFY(CollectionReferenceManager::instance()->isReferenced(col.id(), "testSessionClosedSession")); CollectionReferenceManager::instance()->removeSession("testSessionClosedSession"); QVERIFY(!Collection::retrieveById(col.id()).referenced()); QVERIFY(!CollectionReferenceManager::instance()->isReferenced(col.id(), "testSessionClosedSession")); QVERIFY(col.remove()); } void testCleanup() { Collection col = initializer.createCollection("testCleanupCollection"); col.setReferenced(true); QVERIFY(col.update()); CollectionReferenceManager::cleanup(); QVERIFY(!Collection::retrieveById(col.id()).referenced()); QVERIFY(col.remove()); } }; AKTEST_FAKESERVER_MAIN(CollectionReferenceTest) #include "collectionreferencetest.moc" diff --git a/autotests/server/handlertest.cpp b/autotests/server/handlertest.cpp index b1a8c8c87..9337f1a9a 100644 --- a/autotests/server/handlertest.cpp +++ b/autotests/server/handlertest.cpp @@ -1,203 +1,204 @@ /* Copyright (c) 2011 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include "handler.h" -#include "handler/create.h" -#include "handler/list.h" -#include "handler/searchpersistent.h" -#include "handler/search.h" -#include "handler/fetch.h" -#include "handler/store.h" -#include "handler/status.h" -#include "handler/delete.h" -#include "handler/modify.h" -#include "handler/transaction.h" -#include "handler/akappend.h" -#include "handler/copy.h" -#include "handler/colcopy.h" -#include "handler/link.h" -#include "handler/resourceselect.h" -#include "handler/remove.h" -#include "handler/move.h" -#include "handler/colmove.h" -#include "handler/login.h" -#include "handler/logout.h" +#include "handler/collectioncreatehandler.h" +#include "handler/collectionfetchhandler.h" +#include "handler/collectionstatsfetchhandler.h" +#include "handler/collectiondeletehandler.h" +#include "handler/collectionmodifyhandler.h" +#include "handler/collectioncopyhandler.h" +#include "handler/collectiondeletehandler.h" +#include "handler/collectionmovehandler.h" +#include "handler/searchcreatehandler.h" +#include "handler/searchhandler.h" +#include "handler/itemfetchhandler.h" +#include "handler/itemdeletehandler.h" +#include "handler/itemmodifyhandler.h" +#include "handler/itemcreatehandler.h" +#include "handler/itemcopyhandler.h" +#include "handler/itemlinkhandler.h" +#include "handler/itemmovehandler.h" +#include "handler/resourceselecthandler.h" +#include "handler/transactionhandler.h" +#include "handler/loginhandler.h" +#include "handler/logouthandler.h" using namespace Akonadi; using namespace Akonadi::Server; #define MAKE_CMD_ROW( command, class ) QTest::newRow(#command) << command << QByteArray(typeid(Akonadi::Server::class).name()); class HandlerTest : public QObject { Q_OBJECT private: void setupTestData() { QTest::addColumn("command"); QTest::addColumn("className"); } void addAuthCommands() { - MAKE_CMD_ROW(Protocol::Command::CreateCollection, Create) - MAKE_CMD_ROW(Protocol::Command::FetchCollections, List) - MAKE_CMD_ROW(Protocol::Command::StoreSearch, SearchPersistent) - MAKE_CMD_ROW(Protocol::Command::Search, Search) - MAKE_CMD_ROW(Protocol::Command::FetchItems, Fetch) - MAKE_CMD_ROW(Protocol::Command::ModifyItems, Store) - MAKE_CMD_ROW(Protocol::Command::FetchCollectionStats, Status) - MAKE_CMD_ROW(Protocol::Command::DeleteCollection, Delete) - MAKE_CMD_ROW(Protocol::Command::ModifyCollection, Modify) + MAKE_CMD_ROW(Protocol::Command::CreateCollection, CollectionCreateHandler) + MAKE_CMD_ROW(Protocol::Command::FetchCollections, CollectionFetchHandler) + MAKE_CMD_ROW(Protocol::Command::StoreSearch, SearchCreateHandler) + MAKE_CMD_ROW(Protocol::Command::Search, SearchHandler) + MAKE_CMD_ROW(Protocol::Command::FetchItems, ItemFetchHandler) + MAKE_CMD_ROW(Protocol::Command::ModifyItems, ItemModifyHandler) + MAKE_CMD_ROW(Protocol::Command::FetchCollectionStats, CollectionStatsFetchHandler) + MAKE_CMD_ROW(Protocol::Command::DeleteCollection, CollectionDeleteHandler) + MAKE_CMD_ROW(Protocol::Command::ModifyCollection, CollectionModifyHandler) MAKE_CMD_ROW(Protocol::Command::Transaction, TransactionHandler) - MAKE_CMD_ROW(Protocol::Command::CreateItem, AkAppend) - MAKE_CMD_ROW(Protocol::Command::CopyItems, Copy) - MAKE_CMD_ROW(Protocol::Command::CopyCollection, ColCopy) - MAKE_CMD_ROW(Protocol::Command::LinkItems, Link) - MAKE_CMD_ROW(Protocol::Command::SelectResource, ResourceSelect) - MAKE_CMD_ROW(Protocol::Command::DeleteItems, Remove) - MAKE_CMD_ROW(Protocol::Command::MoveItems, Move) - MAKE_CMD_ROW(Protocol::Command::MoveCollection, ColMove) + MAKE_CMD_ROW(Protocol::Command::CreateItem, ItemCreateHandler) + MAKE_CMD_ROW(Protocol::Command::CopyItems, ItemCopyHandler) + MAKE_CMD_ROW(Protocol::Command::CopyCollection, CollectionCopyHandler) + MAKE_CMD_ROW(Protocol::Command::LinkItems, ItemLinkHandler) + MAKE_CMD_ROW(Protocol::Command::SelectResource, ResourceSelectHandler) + MAKE_CMD_ROW(Protocol::Command::DeleteItems, ItemDeleteHandler) + MAKE_CMD_ROW(Protocol::Command::MoveItems, ItemMoveHandler) + MAKE_CMD_ROW(Protocol::Command::MoveCollection, CollectionMoveHandler) } void addNonAuthCommands() { - MAKE_CMD_ROW(Protocol::Command::Login, Login) + MAKE_CMD_ROW(Protocol::Command::Login, LoginHandler) } void addAlwaysCommands() { - MAKE_CMD_ROW(Protocol::Command::Logout, Logout) + MAKE_CMD_ROW(Protocol::Command::Logout, LogoutHandler) } void addInvalidCommands() { //MAKE_CMD_ROW(Protocol::Command::Invalid, UnknownCommandHandler) } private Q_SLOTS: void testFindAuthenticatedCommand_data() { setupTestData(); addAuthCommands(); } void testFindAuthenticatedCommand() { QFETCH(Protocol::Command::Type, command); QFETCH(QByteArray, className); QScopedPointer handler(Handler::findHandlerForCommandAuthenticated(command)); QVERIFY(!handler.isNull()); QCOMPARE(QByteArray(typeid(*handler.data()).name()), className); } void testFindAuthenticatedCommandNegative_data() { setupTestData(); addNonAuthCommands(); addAlwaysCommands(); addInvalidCommands(); } void testFindAuthenticatedCommandNegative() { QFETCH(Protocol::Command::Type, command); QFETCH(QByteArray, className); QScopedPointer handler(Handler::findHandlerForCommandAuthenticated(command)); QVERIFY(handler.isNull()); } void testFindNonAutenticatedCommand_data() { setupTestData(); addNonAuthCommands(); } void testFindNonAutenticatedCommand() { QFETCH(Protocol::Command::Type, command); QFETCH(QByteArray, className); QScopedPointer handler(Handler::findHandlerForCommandNonAuthenticated(command)); QVERIFY(!handler.isNull()); QCOMPARE(QByteArray(typeid(*handler.data()).name()), className); } void testFindNonAutenticatedCommandNegative_data() { setupTestData(); addAuthCommands(); addAlwaysCommands(); addInvalidCommands(); } void testFindNonAutenticatedCommandNegative() { QFETCH(Protocol::Command::Type, command); QFETCH(QByteArray, className); QScopedPointer handler(Handler::findHandlerForCommandNonAuthenticated(command)); QVERIFY(handler.isNull()); } void testFindAlwaysCommand_data() { setupTestData(); addAlwaysCommands(); } void testFindAlwaysCommand() { QFETCH(Protocol::Command::Type, command); QFETCH(QByteArray, className); QScopedPointer handler(Handler::findHandlerForCommandAlwaysAllowed(command)); QVERIFY(!handler.isNull()); QCOMPARE(QByteArray(typeid(*handler.data()).name()), className); } void testFindAlwaysCommandNegative_data() { setupTestData(); addAuthCommands(); addNonAuthCommands(); addInvalidCommands(); } void testFindAlwaysCommandNegative() { QFETCH(Protocol::Command::Type, command); QFETCH(QByteArray, className); QScopedPointer handler(Handler::findHandlerForCommandAlwaysAllowed(command)); QVERIFY(handler.isNull()); } }; AKTEST_MAIN(HandlerTest) #include "handlertest.moc" diff --git a/autotests/server/akappendhandlertest.cpp b/autotests/server/itemcreatehandlertest.cpp similarity index 99% rename from autotests/server/akappendhandlertest.cpp rename to autotests/server/itemcreatehandlertest.cpp index 04d998813..0104c44e9 100644 --- a/autotests/server/akappendhandlertest.cpp +++ b/autotests/server/itemcreatehandlertest.cpp @@ -1,907 +1,906 @@ /* Copyright (c) 2014 Daniel Vrátil This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include -#include #include #include #include #include "fakeakonadiserver.h" #include "fakeentities.h" #include #include using namespace Akonadi; using namespace Akonadi::Server; Q_DECLARE_METATYPE(PimItem) Q_DECLARE_METATYPE(QVector) Q_DECLARE_METATYPE(QVector) Q_DECLARE_METATYPE(QVector) -class AkAppendHandlerTest : public QObject +class ItemCreateHandlerTest : public QObject { Q_OBJECT public: - AkAppendHandlerTest() + ItemCreateHandlerTest() { // Effectively disable external payload parts, we have a dedicated unit-test // for that const QString serverConfigFile = StandardDirs::serverConfigFile(StandardDirs::ReadWrite); QSettings settings(serverConfigFile, QSettings::IniFormat); settings.setValue(QStringLiteral("General/SizeThreshold"), std::numeric_limits::max()); try { FakeAkonadiServer::instance()->init(); } catch (const FakeAkonadiServerException &e) { qWarning() << e.what(); qFatal("Fake Akonadi Server failed to start up, aborting test"); } } - ~AkAppendHandlerTest() + ~ItemCreateHandlerTest() { FakeAkonadiServer::instance()->quit(); } void updatePimItem(PimItem &pimItem, const QString &remoteId, const qint64 size) { pimItem.setRemoteId(remoteId); pimItem.setGid(remoteId); pimItem.setSize(size); } void updateNotifcationEntity(Protocol::ItemChangeNotificationPtr &ntf, const PimItem &pimItem) { Protocol::FetchItemsResponse item; item.setId(pimItem.id()); item.setRemoteId(pimItem.remoteId()); item.setRemoteRevision(pimItem.remoteRevision()); item.setMimeType(pimItem.mimeType().name()); ntf->setItems({std::move(item)}); } struct PartHelper { PartHelper(const QString &type_, const QByteArray &data_, int size_, Part::Storage storage_ = Part::Internal, int version_ = 0) : type(type_) , data(data_) , size(size_) , storage(storage_) , version(version_) { } QString type; QByteArray data; int size; Part::Storage storage; int version; }; void updateParts(QVector &parts, const std::vector &updatedParts) { parts.clear(); Q_FOREACH (const PartHelper &helper, updatedParts) { FakePart part; const QStringList types = helper.type.split(QLatin1Char(':')); Q_ASSERT(types.count() == 2); part.setPartType(PartType(types[1], types[0])); part.setData(helper.data); part.setDatasize(helper.size); part.setStorage(helper.storage); part.setVersion(helper.version); parts << part; } } void updateFlags(QVector &flags, const QStringList &updatedFlags) { flags.clear(); for (const QString &flagName : updatedFlags) { Flag flag; flag.setName(flagName); flags << flag; } } struct TagHelper { TagHelper(const QString &tagType_, const QString &gid_, const QString &remoteId_ = QString()) : tagType(tagType_) , gid(gid_) , remoteId(remoteId_) { } QString tagType; QString gid; QString remoteId; }; void updateTags(QVector &tags, const std::vector &updatedTags) { tags.clear(); Q_FOREACH (const TagHelper &helper, updatedTags) { FakeTag tag; TagType tagType; tagType.setName(helper.tagType); tag.setTagType(tagType); tag.setGid(helper.gid); tag.setRemoteId(helper.remoteId); tags << tag; } } Protocol::CreateItemCommandPtr createCommand(const PimItem &pimItem, const QDateTime &dt, const QSet &parts, qint64 overrideSize = -1) { const qint64 size = overrideSize > -1 ? overrideSize : pimItem.size(); auto cmd = Protocol::CreateItemCommandPtr::create(); cmd->setCollection(Scope(pimItem.collectionId())); cmd->setItemSize(size); cmd->setRemoteId(pimItem.remoteId()); cmd->setRemoteRevision(pimItem.remoteRevision()); cmd->setMimeType(pimItem.mimeType().name()); cmd->setGid(pimItem.gid()); cmd->setDateTime(dt); cmd->setParts(parts); return cmd; } Protocol::FetchItemsResponsePtr createResponse(qint64 expectedId, const PimItem &pimItem, const QDateTime &datetime, const QVector &parts, qint64 overrideSize = -1) { const qint64 size = overrideSize > -1 ? overrideSize : pimItem.size(); auto resp = Protocol::FetchItemsResponsePtr::create(expectedId); resp->setParentId(pimItem.collectionId()); resp->setSize(size); resp->setRemoteId(pimItem.remoteId()); resp->setRemoteRevision(pimItem.remoteRevision()); resp->setMimeType(pimItem.mimeType().name()); resp->setGid(pimItem.gid()); resp->setMTime(datetime); resp->setParts(parts); resp->setAncestors({ Protocol::Ancestor(4, QLatin1String("ColC")) }); return resp; } TestScenario errorResponse(const QString &errorMsg) { auto response = Protocol::CreateItemResponsePtr::create(); response->setError(1, errorMsg); return TestScenario::create(5, TestScenario::ServerCmd, response); } private Q_SLOTS: - void testAkAppend_data() + void testItemCreate_data() { using Notifications = QVector; QTest::addColumn("scenarios"); QTest::addColumn("notifications"); QTest::addColumn("pimItem"); QTest::addColumn >("parts"); QTest::addColumn >("flags"); QTest::addColumn >("tags"); QTest::addColumn("uidnext"); QTest::addColumn("datetime"); QTest::addColumn("expectFail"); TestScenario::List scenarios; auto notification = Protocol::ItemChangeNotificationPtr::create(); qint64 uidnext = 0; QDateTime datetime(QDate(2014, 05, 12), QTime(14, 46, 00), Qt::UTC); PimItem pimItem; QVector parts; QVector flags; QVector tags; pimItem.setCollectionId(4); pimItem.setSize(10); pimItem.setRemoteId(QStringLiteral("TEST-1")); pimItem.setRemoteRevision(QStringLiteral("1")); pimItem.setGid(QStringLiteral("TEST-1")); pimItem.setMimeType(MimeType::retrieveByName(QStringLiteral("application/octet-stream"))); pimItem.setDatetime(datetime); updateParts(parts, { { QLatin1String("PLD:DATA"), "0123456789", 10 } }); notification->setOperation(Protocol::ItemChangeNotification::Add); notification->setParentCollection(4); notification->setResource("akonadi_fake_resource_0"); Protocol::FetchItemsResponse item; item.setId(-1); item.setRemoteId(QStringLiteral("TEST-1")); item.setRemoteRevision(QStringLiteral("1")); item.setMimeType(QStringLiteral("application/octet-stream")); notification->setItems({std::move(item)}); notification->setSessionId(FakeAkonadiServer::instanceName().toLatin1()); uidnext = 13; scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd,createCommand(pimItem, datetime, { "PLD:DATA" })) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::MetaData)) << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:DATA", Protocol::PartMetaData("PLD:DATA", 10))) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::Data)) << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:DATA", "0123456789")) << TestScenario::create(5, TestScenario::ServerCmd, createResponse(uidnext, pimItem, datetime, { Protocol::StreamPayloadResponse("PLD:DATA", Protocol::PartMetaData("PLD:DATA", 10), "0123456789") })) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create()); QTest::newRow("single-part") << scenarios << Notifications{ notification } << pimItem << parts << flags << tags << uidnext << datetime << false; notification = Protocol::ItemChangeNotificationPtr::create(*notification); updatePimItem(pimItem, QStringLiteral("TEST-2"), 20); updateParts(parts, { { QLatin1String("PLD:DATA"), "Random Data", 11 }, { QLatin1String("PLD:PLDTEST"), "Test Data", 9 } }); updateNotifcationEntity(notification, pimItem); ++uidnext; scenarios.clear(); scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, createCommand(pimItem, datetime, { "PLD:DATA", "PLD:PLDTEST" } )) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::MetaData)) << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:DATA", Protocol::PartMetaData("PLD:DATA", 11, 0))) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::Data)) << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:DATA", "Random Data")) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:PLDTEST", Protocol::StreamPayloadCommand::MetaData)) << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:PLDTEST", Protocol::PartMetaData("PLD:PLDTEST", 9, 0))) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:PLDTEST", Protocol::StreamPayloadCommand::Data)) << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:PLDTEST", "Test Data")) << TestScenario::create(5, TestScenario::ServerCmd, createResponse(uidnext, pimItem, datetime, { Protocol::StreamPayloadResponse("PLD:DATA", Protocol::PartMetaData("PLD:DATA", 11), "Random Data"), Protocol::StreamPayloadResponse("PLD:PLDTEST", Protocol::PartMetaData("PLD:PLDTEST", 9), "Test Data") })) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create()); QTest::newRow("multi-part") << scenarios << Notifications{ notification } << pimItem << parts << flags << tags << uidnext << datetime << false; TestScenario inScenario, outScenario; { auto cmd = Protocol::CreateItemCommandPtr::create(); cmd->setCollection(Scope(100)); inScenario = TestScenario::create(5, TestScenario::ClientCmd, cmd); } scenarios.clear(); scenarios << FakeAkonadiServer::loginScenario() << inScenario << errorResponse(QStringLiteral("Invalid parent collection")); QTest::newRow("invalid collection") << scenarios << Notifications{} << PimItem() << QVector() << QVector() << QVector() << -1ll << QDateTime() << true; { auto cmd = Protocol::CreateItemCommandPtr::create(); cmd->setCollection(Scope(6)); inScenario = TestScenario::create(5, TestScenario::ClientCmd, cmd); } scenarios.clear(); scenarios << FakeAkonadiServer::loginScenario() << inScenario << errorResponse(QStringLiteral("Cannot append item into virtual collection")); QTest::newRow("virtual collection") << scenarios << Notifications{} << PimItem() << QVector() << QVector() << QVector() << -1ll << QDateTime() << true; updatePimItem(pimItem, QStringLiteral("TEST-3"), 5); updateParts(parts, { { QLatin1String("PLD:DATA"), "12345", 5 } }); updateNotifcationEntity(notification, pimItem); ++uidnext; scenarios.clear(); scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, createCommand(pimItem, datetime, { "PLD:DATA" }, 1)) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::MetaData)) << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:DATA", Protocol::PartMetaData("PLD:DATA", 5))) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::Data)) << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:DATA", "12345")) << TestScenario::create(5, TestScenario::ServerCmd, createResponse(uidnext, pimItem, datetime, { Protocol::StreamPayloadResponse("PLD:DATA", Protocol::PartMetaData("PLD:DATA", 5), "12345") })) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create()); QTest::newRow("mismatch item sizes (smaller)") << scenarios << Notifications{ notification } << pimItem << parts << flags << tags << uidnext << datetime << false; notification = Protocol::ItemChangeNotificationPtr::create(*notification); updatePimItem(pimItem, QStringLiteral("TEST-4"), 10); updateNotifcationEntity(notification, pimItem); ++uidnext; scenarios.clear(); scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, createCommand(pimItem, datetime, { "PLD:DATA" }, 10)) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::MetaData)) << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:DATA", Protocol::PartMetaData("PLD:DATA", 5))) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::Data)) << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:DATA", "12345")) << TestScenario::create(5, TestScenario::ServerCmd, createResponse(uidnext, pimItem, datetime, { Protocol::StreamPayloadResponse("PLD:DATA", Protocol::PartMetaData("PLD:DATA", 5), "12345") }, 10)) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create()); QTest::newRow("mismatch item sizes (bigger)") << scenarios << Notifications{ notification } << pimItem << parts << flags << tags << uidnext << datetime << false; scenarios.clear(); scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, createCommand(pimItem, datetime, { "PLD:DATA" })) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::MetaData)) << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:DATA", Protocol::PartMetaData("PLD:DATA", 5))) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::Data)) << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:DATA", "123")) << errorResponse(QStringLiteral("Payload size mismatch")); QTest::newRow("incomplete part data") << scenarios << Notifications{} << PimItem() << QVector() << QVector() << QVector() << -1ll << QDateTime() << true; scenarios.clear(); scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, createCommand(pimItem, datetime, { "PLD:DATA" })) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::MetaData)) << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:DATA", Protocol::PartMetaData("PLD:DATA", 4))) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::Data)) << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:DATA", "1234567890")) << errorResponse(QStringLiteral("Payload size mismatch")); QTest::newRow("part data larger than advertised") << scenarios << Notifications{} << PimItem() << QVector() << QVector() << QVector() << -1ll << QDateTime() << true; notification = Protocol::ItemChangeNotificationPtr::create(*notification); updatePimItem(pimItem, QStringLiteral("TEST-5"), 0); updateParts(parts, { { QLatin1String("PLD:DATA"), QByteArray(), 0 } }); updateNotifcationEntity(notification, pimItem); ++uidnext; scenarios.clear(); scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, createCommand(pimItem, datetime, { "PLD:DATA" })) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::MetaData)) << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:DATA", Protocol::PartMetaData("PLD:DATA", 0))) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::Data)) << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:DATA", QByteArray())) << TestScenario::create(5, TestScenario::ServerCmd, createResponse(uidnext, pimItem ,datetime, { Protocol::StreamPayloadResponse("PLD:DATA", Protocol::PartMetaData("PLD:DATA", 0), QByteArray()) } )) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create()); QTest::newRow("empty payload part") << scenarios << Notifications{ notification } << pimItem << parts << flags << tags << uidnext << datetime << false; notification = Protocol::ItemChangeNotificationPtr::create(*notification); updatePimItem(pimItem, QStringLiteral("TEST-8"), 1); updateParts(parts, { { QLatin1String("PLD:DATA"), QByteArray("\0", 1), 1 } }); updateNotifcationEntity(notification, pimItem); ++uidnext; scenarios.clear(); scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, createCommand(pimItem, datetime, { "PLD:DATA" })) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::MetaData)) << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:DATA", Protocol::PartMetaData("PLD:DATA", 1))) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::Data)) << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:DATA", QByteArray("\0", 1))) << TestScenario::create(5, TestScenario::ServerCmd, createResponse(uidnext, pimItem, datetime, { Protocol::StreamPayloadResponse("PLD:DATA", Protocol::PartMetaData("PLD:DATA", 1), QByteArray("\0", 1)) })) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create()); QTest::newRow("part data will null character") << scenarios << Notifications{ notification } << pimItem << parts << flags << tags << uidnext << datetime << false; const QString utf8String = QStringLiteral("äöüß@€µøđ¢©®"); notification = Protocol::ItemChangeNotificationPtr::create(*notification); updatePimItem(pimItem, QStringLiteral("TEST-9"), utf8String.toUtf8().size()); updateParts(parts, { { QLatin1String("PLD:DATA"), utf8String.toUtf8(), utf8String.toUtf8().size() } }); updateNotifcationEntity(notification, pimItem); ++uidnext; scenarios.clear(); scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, createCommand(pimItem, datetime, { "PLD:DATA" })) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::MetaData)) << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:DATA", Protocol::PartMetaData("PLD:DATA", parts.first().datasize()))) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::Data)) << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:DATA", utf8String.toUtf8())) << TestScenario::create(5, TestScenario::ServerCmd, createResponse(uidnext, pimItem, datetime, { Protocol::StreamPayloadResponse("PLD:DATA", Protocol::PartMetaData("PLD:DATA", utf8String.toUtf8().size()), utf8String.toUtf8()) })) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create()); QTest::newRow("utf8 part data") << scenarios << Notifications{ notification } << pimItem << parts << flags << tags << uidnext << datetime << false; const QByteArray hugeData = QByteArray("a").repeated(1 << 20); notification = Protocol::ItemChangeNotificationPtr::create(*notification); updatePimItem(pimItem, QStringLiteral("TEST-10"), 1 << 20); updateParts(parts, { { QLatin1String("PLD:DATA"), hugeData, 1 << 20 } }); updateNotifcationEntity(notification, pimItem); ++uidnext; scenarios.clear(); scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, createCommand(pimItem, datetime, { "PLD:DATA" })) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::MetaData)) << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:DATA", Protocol::PartMetaData("PLD:DATA", parts.first().datasize()))) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::Data)) << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:DATA", hugeData)) << TestScenario::create(5, TestScenario::ServerCmd, createResponse(uidnext, pimItem ,datetime, { Protocol::StreamPayloadResponse("PLD:DATA", Protocol::PartMetaData("PLD:DATA", parts.first().datasize()), hugeData) })) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create()); QTest::newRow("huge part data") << scenarios << Notifications{ notification } << pimItem << parts << flags << tags << uidnext << datetime << false; const QByteArray dataWithNewLines = "Bernard, Bernard, Bernard, Bernard, look, look Bernard!\nWHAT!!!!!!!\nI'm a prostitute robot from the future!"; notification = Protocol::ItemChangeNotificationPtr::create(*notification); updatePimItem(pimItem, QStringLiteral("TEST-11"), dataWithNewLines.size()); updateParts(parts, { { QLatin1String("PLD:DATA"), dataWithNewLines, dataWithNewLines.size() } }); updateNotifcationEntity(notification, pimItem); ++uidnext; scenarios.clear(); scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, createCommand(pimItem, datetime, { "PLD:DATA" })) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::MetaData)) << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:DATA", Protocol::PartMetaData("PLD:DATA", parts.first().datasize()))) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::Data)) << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:DATA", dataWithNewLines)) << TestScenario::create(5, TestScenario::ServerCmd, createResponse(uidnext, pimItem, datetime, { Protocol::StreamPayloadResponse("PLD:DATA", Protocol::PartMetaData("PLD:DATA", dataWithNewLines.size()), dataWithNewLines) })) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create()); QTest::newRow("data with newlines") << scenarios << Notifications{ notification } << pimItem << parts << flags << tags << uidnext << datetime << false; const QByteArray lotsOfNewlines = QByteArray("\n").repeated(1 << 20); notification = Protocol::ItemChangeNotificationPtr::create(*notification); updatePimItem(pimItem, QStringLiteral("TEST-12"), lotsOfNewlines.size()); updateParts(parts, { { QLatin1String("PLD:DATA"), lotsOfNewlines, lotsOfNewlines.size() } }); updateNotifcationEntity(notification, pimItem); ++uidnext; scenarios.clear(); scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, createCommand(pimItem, datetime, { "PLD:DATA" })) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::MetaData)) << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:DATA", Protocol::PartMetaData("PLD:DATA", parts.first().datasize()))) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::Data)) << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:DATA", lotsOfNewlines)) << TestScenario::create(5, TestScenario::ServerCmd, createResponse(uidnext, pimItem, datetime, { Protocol::StreamPayloadResponse("PLD:DATA", Protocol::PartMetaData("PLD:DATA", parts.first().datasize()), lotsOfNewlines) })) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create()); QTest::newRow("data with lots of newlines") << scenarios << Notifications{ notification } << pimItem << parts << flags << tags << uidnext << datetime << false; notification = Protocol::ItemChangeNotificationPtr::create(*notification); updatePimItem(pimItem, QStringLiteral("TEST-13"), 20); updateParts(parts, { { QLatin1String("PLD:NEWPARTTYPE1"), "0123456789", 10 }, { QLatin1String("PLD:NEWPARTTYPE2"), "9876543210", 10 } }); updateNotifcationEntity(notification, pimItem); ++uidnext; scenarios.clear(); scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, createCommand(pimItem, datetime, { "PLD:NEWPARTTYPE1", "PLD:NEWPARTTYPE2" })) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:NEWPARTTYPE2", Protocol::StreamPayloadCommand::MetaData)) << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:NEWPARTTYPE2", Protocol::PartMetaData("PLD:NEWPARTTYPE2", 10))) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:NEWPARTTYPE2", Protocol::StreamPayloadCommand::Data)) << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:NEWPARTTYPE2", "9876543210")) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:NEWPARTTYPE1", Protocol::StreamPayloadCommand::MetaData)) << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:NEWPARTTYPE1", Protocol::PartMetaData("PLD:NEWPARTTYPE1", 10))) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:NEWPARTTYPE1", Protocol::StreamPayloadCommand::Data)) << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:NEWPARTTYPE1", "0123456789")) << TestScenario::create(5, TestScenario::ServerCmd, createResponse(uidnext, pimItem, datetime, { Protocol::StreamPayloadResponse("PLD:NEWPARTTYPE2", Protocol::PartMetaData("PLD:NEWPARTTYPE2", 10), "9876543210"), Protocol::StreamPayloadResponse("PLD:NEWPARTTYPE1", Protocol::PartMetaData("PLD:NEWPARTTYPE1", 10), "0123456789") })) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create()); QTest::newRow("non-existent part types") << scenarios << Notifications{ notification } << pimItem << parts << flags << tags << uidnext << datetime << false; notification = Protocol::ItemChangeNotificationPtr::create(*notification); updatePimItem(pimItem, QStringLiteral("TEST-14"), 0); updateParts(parts, {}); updateFlags(flags, QStringList() << QStringLiteral("\\SEEN") << QStringLiteral("\\RANDOM")); updateNotifcationEntity(notification, pimItem); ++uidnext; { auto cmd = createCommand(pimItem, datetime, {}); cmd->setFlags({ "\\SEEN", "\\RANDOM" }); inScenario = TestScenario::create(5, TestScenario::ClientCmd, cmd); auto rsp = createResponse(uidnext, pimItem, datetime, {}); rsp->setFlags({ "\\SEEN", "\\RANDOM" }); outScenario = TestScenario::create(5, TestScenario::ServerCmd, rsp); } scenarios.clear(); scenarios << FakeAkonadiServer::loginScenario() << inScenario << outScenario << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create()); QTest::newRow("item with flags") << scenarios << Notifications{ notification } << pimItem << parts << flags << tags << uidnext << datetime << false; notification = Protocol::ItemChangeNotificationPtr::create(*notification); updatePimItem(pimItem, QStringLiteral("TEST-15"), 0); updateFlags(flags, {}); updateTags(tags, { { QLatin1String("PLAIN"), QLatin1String("TAG-1") }, { QLatin1String("PLAIN"), QLatin1String("TAG-2") } }); updateNotifcationEntity(notification, pimItem); ++uidnext; { auto cmd = createCommand(pimItem, datetime, {}); cmd->setTags(Scope(Scope::Gid, { QLatin1String("TAG-1"), QLatin1String("TAG-2") })); inScenario = TestScenario::create(5, TestScenario::ClientCmd, cmd); auto rsp = createResponse(uidnext, pimItem, datetime, {}); rsp->setTags({ Protocol::FetchTagsResponse(2, "TAG-1", "PLAIN"), Protocol::FetchTagsResponse(3, "TAG-2", "PLAIN") }); outScenario = TestScenario::create(5, TestScenario::ServerCmd, rsp); } scenarios.clear(); scenarios << FakeAkonadiServer::loginScenario() << inScenario << outScenario << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create()); QTest::newRow("item with non-existent tags (GID)") << scenarios << Notifications{ notification } << pimItem << parts << flags << tags << uidnext << datetime << false; notification = Protocol::ItemChangeNotificationPtr::create(*notification); updatePimItem(pimItem, QStringLiteral("TEST-16"), 0); updateTags(tags, { { QLatin1String("PLAIN"), QLatin1String("TAG-3") }, { QLatin1String("PLAIN"), QLatin1String("TAG-4") } }); updateNotifcationEntity(notification, pimItem); ++uidnext; { auto cmd = createCommand(pimItem, datetime, {}); cmd->setTags(Scope(Scope::Rid, { QLatin1String("TAG-3"), QLatin1String("TAG-4") })); inScenario = TestScenario::create(5, TestScenario::ClientCmd, cmd); auto rsp = createResponse(uidnext, pimItem, datetime, {}); rsp->setTags({ Protocol::FetchTagsResponse(4, "TAG-3", "PLAIN"), Protocol::FetchTagsResponse(5, "TAG-4", "PLAIN") }); outScenario = TestScenario::create(5, TestScenario::ServerCmd, rsp); } scenarios.clear(); scenarios << FakeAkonadiServer::loginScenario() << FakeAkonadiServer::selectResourceScenario(QStringLiteral("akonadi_fake_resource_0")) << inScenario << outScenario << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create()); QTest::newRow("item with non-existent tags (RID)") << scenarios << Notifications{ notification } << pimItem << parts << flags << tags << uidnext << datetime << false; notification = Protocol::ItemChangeNotificationPtr::create(*notification); updatePimItem(pimItem, QStringLiteral("TEST-17"), 0); updateNotifcationEntity(notification, pimItem); updateTags(tags, { { QLatin1String("PLAIN"), QLatin1String("TAG-1") }, { QLatin1String("PLAIN"), QLatin1String("TAG-2") } }); ++uidnext; { auto cmd = createCommand(pimItem, datetime, {}); cmd->setTags(Scope(Scope::Rid, { QLatin1String("TAG-1"), QLatin1String("TAG-2") })); inScenario = TestScenario::create(5, TestScenario::ClientCmd, cmd); auto rsp = createResponse(uidnext, pimItem, datetime, {}); rsp->setTags({ Protocol::FetchTagsResponse(2, "TAG-1", "PLAIN"), Protocol::FetchTagsResponse(3, "TAG-2", "PLAIN") }); outScenario = TestScenario::create(5, TestScenario::ServerCmd, rsp); } scenarios.clear(); scenarios << FakeAkonadiServer::loginScenario() << FakeAkonadiServer::selectResourceScenario(QStringLiteral("akonadi_fake_resource_0")) << inScenario << outScenario << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create()); QTest::newRow("item with existing tags (RID)") << scenarios << Notifications{ notification } << pimItem << parts << flags << tags << uidnext << datetime << false; notification = Protocol::ItemChangeNotificationPtr::create(*notification); updatePimItem(pimItem, QStringLiteral("TEST-18"), 0); updateNotifcationEntity(notification, pimItem); updateTags(tags, { { QLatin1String("PLAIN"), QLatin1String("TAG-3") }, { QLatin1String("PLAIN"), QLatin1String("TAG-4") } }); ++uidnext; { auto cmd = createCommand(pimItem, datetime, {}); cmd->setTags(Scope(Scope::Gid, { QLatin1String("TAG-3"), QLatin1String("TAG-4") })); inScenario = TestScenario::create(5, TestScenario::ClientCmd, cmd); auto rsp = createResponse(uidnext, pimItem, datetime, {}); rsp->setTags({ Protocol::FetchTagsResponse(4, "TAG-3", "PLAIN"), Protocol::FetchTagsResponse(5, "TAG-4", "PLAIN") }); outScenario = TestScenario::create(5, TestScenario::ServerCmd, rsp); } scenarios.clear(); scenarios << FakeAkonadiServer::loginScenario() << inScenario << outScenario << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create()); QTest::newRow("item with existing tags (GID)") << scenarios << Notifications{ notification } << pimItem << parts << flags << tags << uidnext << datetime << false; notification = Protocol::ItemChangeNotificationPtr::create(*notification); updatePimItem(pimItem, QStringLiteral("TEST-19"), 0); updateFlags(flags, QStringList() << QStringLiteral("\\SEEN") << QStringLiteral("$FLAG")); updateTags(tags, { { QLatin1String("PLAIN"), QLatin1String("TAG-1") }, { QLatin1String("PLAIN"), QLatin1String("TAG-2") } }); updateNotifcationEntity(notification, pimItem); ++uidnext; { auto cmd = createCommand(pimItem, datetime, {}); cmd->setTags(Scope(Scope::Gid, { QLatin1String("TAG-1"), QLatin1String("TAG-2") })); cmd->setFlags({ "\\SEEN", "$FLAG" }); inScenario = TestScenario::create(5, TestScenario::ClientCmd, cmd); auto rsp = createResponse(uidnext, pimItem, datetime, {}); rsp->setTags({ Protocol::FetchTagsResponse(2, "TAG-1", "PLAIN"), Protocol::FetchTagsResponse(3, "TAG-2", "PLAIN") }); rsp->setFlags({ "\\SEEN", "$FLAG" }); outScenario = TestScenario::create(5, TestScenario::ServerCmd, rsp); } scenarios.clear(); scenarios << FakeAkonadiServer::loginScenario() << inScenario << outScenario << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create()); QTest::newRow("item with flags and tags") << scenarios << Notifications{ notification } << pimItem << parts << flags << tags << uidnext << datetime << false; notification = Protocol::ItemChangeNotificationPtr::create(*notification); updatePimItem(pimItem, QStringLiteral("TEST-20"), 0); updateFlags(flags, {}); updateTags(tags, { { QLatin1String("PLAIN"), utf8String } }); updateNotifcationEntity(notification, pimItem); ++uidnext; { auto cmd = createCommand(pimItem, datetime, {}); cmd->setTags(Scope(Scope::Gid, { utf8String })); inScenario = TestScenario::create(5, TestScenario::ClientCmd, cmd); auto rsp = createResponse(uidnext, pimItem, datetime, {}); rsp->setTags({ Protocol::FetchTagsResponse(6, utf8String.toUtf8(), "PLAIN") }); outScenario = TestScenario::create(5, TestScenario::ServerCmd, rsp); } scenarios.clear(); scenarios << FakeAkonadiServer::loginScenario() << inScenario << outScenario << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create()); QTest::newRow("item with UTF-8 tag") << scenarios << Notifications{ notification }<< pimItem << parts << flags << tags << uidnext << datetime << false; notification = Protocol::ItemChangeNotificationPtr::create(*notification); updatePimItem(pimItem, QStringLiteral("TEST-21"), 0); updateFlags(flags, {}); updateTags(tags, {}); pimItem.setGid(QStringLiteral("GID-21")); updateNotifcationEntity(notification, pimItem); scenarios = FakeAkonadiServer::loginScenario(); // Create a normal item with RID { ++uidnext; auto cmd = createCommand(pimItem, datetime, {}); scenarios << TestScenario::create(5, TestScenario::ClientCmd, cmd); auto rsp = createResponse(uidnext, pimItem, datetime, {}); scenarios << TestScenario::create(5, TestScenario::ServerCmd, rsp) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create()); } // Create the same item again (no merging, so it will just be created) { ++uidnext; auto cmd = createCommand(pimItem, datetime, {}); scenarios << TestScenario::create(6, TestScenario::ClientCmd, cmd); auto rsp = createResponse(uidnext, pimItem, datetime, {}); scenarios << TestScenario::create(6, TestScenario::ServerCmd, rsp) << TestScenario::create(6, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create()); } // Now try to create the item once again, but in merge mode, we should fail now { ++uidnext; auto cmd = createCommand(pimItem, datetime, {}); cmd->setMergeModes(Protocol::CreateItemCommand::RemoteID); scenarios << TestScenario::create(7, TestScenario::ClientCmd, cmd); auto rsp = Protocol::CreateItemResponsePtr::create(); rsp->setError(1, QStringLiteral("Multiple merge candidates")); scenarios << TestScenario::create(7, TestScenario::ServerCmd, rsp); } Notifications notifications = { notification, Protocol::ItemChangeNotificationPtr::create(*notification) }; QTest::newRow("multiple merge candidates (RID)") << scenarios << notifications << pimItem << parts << flags << tags << uidnext << datetime << true; notification = Protocol::ItemChangeNotificationPtr::create(*notification); updatePimItem(pimItem, QStringLiteral("TEST-22"), 0); pimItem.setGid(QStringLiteral("GID-22")); updateNotifcationEntity(notification, pimItem); scenarios = FakeAkonadiServer::loginScenario(); // Create a normal item with GID { // Don't increase uidnext, we will reuse the one from previous test, // since that did not actually create a new Item auto cmd = createCommand(pimItem, datetime, {}); scenarios << TestScenario::create(5, TestScenario::ClientCmd, cmd); auto rsp = createResponse(uidnext, pimItem, datetime, {}); scenarios << TestScenario::create(5, TestScenario::ServerCmd, rsp) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create()); } // Create the same item again (no merging, so it will just be created) { ++uidnext; auto cmd = createCommand(pimItem, datetime, {}); scenarios << TestScenario::create(6, TestScenario::ClientCmd, cmd); auto rsp = createResponse(uidnext, pimItem, datetime, {}); scenarios << TestScenario::create(6, TestScenario::ServerCmd, rsp) << TestScenario::create(6, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create()); } // Now try to create the item once again, but in merge mode, we should fail now { ++uidnext; auto cmd = createCommand(pimItem, datetime, {}); cmd->setMergeModes(Protocol::CreateItemCommand::GID); scenarios << TestScenario::create(7, TestScenario::ClientCmd, cmd); auto rsp = Protocol::CreateItemResponsePtr::create(); rsp->setError(1, QStringLiteral("Multiple merge candidates")); scenarios << TestScenario::create(7, TestScenario::ServerCmd, rsp); } notifications = { notification, Protocol::ItemChangeNotificationPtr::create(*notification) }; QTest::newRow("multiple merge candidates (GID)") << scenarios << notifications << pimItem << parts << flags << tags << uidnext << datetime << true; notification = Protocol::ItemChangeNotificationPtr::create(*notification); updatePimItem(pimItem, QStringLiteral("TEST-23"), 0); pimItem.setGid(QString()); updateNotifcationEntity(notification, pimItem); scenarios = FakeAkonadiServer::loginScenario(); // Create a normal item with RID, but with empty GID { // Don't increase uidnext, we will reuse the one from previous test, // since that did not actually create a new Item auto cmd = createCommand(pimItem, datetime, {}); scenarios << TestScenario::create(5, TestScenario::ClientCmd, cmd); auto rsp = createResponse(uidnext, pimItem, datetime, {}); scenarios << TestScenario::create(5, TestScenario::ServerCmd, rsp) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create()); } // Merge by GID - should not create a new Item but actually merge by RID, // since an item with matching RID but empty GID exists { ++uidnext; pimItem.setGid(QStringLiteral("GID-23")); auto cmd = createCommand(pimItem, datetime, {}); cmd->setMergeModes(Protocol::CreateItemCommand::GID); scenarios << TestScenario::create(6, TestScenario::ClientCmd, cmd); auto rsp = createResponse(uidnext, pimItem, datetime, {}); scenarios << TestScenario::create(6, TestScenario::ServerCmd, rsp) << TestScenario::create(6, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create()); } notifications = { notification, Protocol::ItemChangeNotificationPtr::create(*notification) }; QTest::newRow("merge into empty GID if RID matches") << scenarios << notifications << pimItem << parts << flags << tags << uidnext << datetime << false; } - void testAkAppend() + void testItemCreate() { QFETCH(TestScenario::List, scenarios); QFETCH(QVector, notifications); QFETCH(PimItem, pimItem); QFETCH(QVector, parts); QFETCH(QVector, flags); QFETCH(QVector, tags); QFETCH(qint64, uidnext); QFETCH(bool, expectFail); FakeAkonadiServer::instance()->setScenarios(scenarios); FakeAkonadiServer::instance()->runTest(); auto notificationSpy = FakeAkonadiServer::instance()->notificationSpy(); QCOMPARE(notificationSpy->count(), notifications.count()); for (int i = 0; i < notifications.count(); ++i) { const auto incomingNtfs = notificationSpy->at(i).first().value(); QCOMPARE(incomingNtfs.count(), 1); const auto itemNotification = incomingNtfs.at(0).staticCast(); QVERIFY(AkTest::compareNotifications(itemNotification, notifications.at(i), QFlag(AkTest::NtfAll & ~ AkTest::NtfEntities))); QCOMPARE(itemNotification->items().count(), notifications.at(i)->items().count()); } const PimItem actualItem = PimItem::retrieveById(uidnext); if (expectFail) { QVERIFY(!actualItem.isValid()); } else { QVERIFY(actualItem.isValid()); QCOMPARE(actualItem.remoteId(), pimItem.remoteId()); QCOMPARE(actualItem.remoteRevision(), pimItem.remoteRevision()); QCOMPARE(actualItem.gid(), pimItem.gid()); QCOMPARE(actualItem.size(), pimItem.size()); QCOMPARE(actualItem.datetime(), pimItem.datetime()); QCOMPARE(actualItem.collectionId(), pimItem.collectionId()); QCOMPARE(actualItem.mimeTypeId(), pimItem.mimeTypeId()); const QList actualFlags = actualItem.flags().toList(); QCOMPARE(actualFlags.count(), flags.count()); Q_FOREACH (const Flag &flag, flags) { const QList::const_iterator actualFlagIter = std::find_if(actualFlags.constBegin(), actualFlags.constEnd(), [flag](Flag const & actualFlag) { return flag.name() == actualFlag.name(); }); QVERIFY(actualFlagIter != actualFlags.constEnd()); const Flag actualFlag = *actualFlagIter; QVERIFY(actualFlag.isValid()); } const QList actualTags = actualItem.tags().toList(); QCOMPARE(actualTags.count(), tags.count()); Q_FOREACH (const FakeTag &tag, tags) { const QList::const_iterator actualTagIter = std::find_if(actualTags.constBegin(), actualTags.constEnd(), [tag](Tag const & actualTag) { return tag.gid() == actualTag.gid(); }); QVERIFY(actualTagIter != actualTags.constEnd()); const Tag actualTag = *actualTagIter; QVERIFY(actualTag.isValid()); QCOMPARE(actualTag.tagType().name(), tag.tagType().name()); QCOMPARE(actualTag.gid(), tag.gid()); if (!tag.remoteId().isEmpty()) { SelectQueryBuilder qb; qb.addValueCondition(TagRemoteIdResourceRelation::resourceIdFullColumnName(), Query::Equals, QLatin1String("akonadi_fake_resource_0")); qb.addValueCondition(TagRemoteIdResourceRelation::tagIdColumn(), Query::Equals, actualTag.id()); QVERIFY(qb.exec()); QCOMPARE(qb.result().size(), 1); QCOMPARE(qb.result()[0].remoteId(), tag.remoteId()); } } const QList actualParts = actualItem.parts().toList(); QCOMPARE(actualParts.count(), parts.count()); Q_FOREACH (const FakePart &part, parts) { const QList::const_iterator actualPartIter = std::find_if(actualParts.constBegin(), actualParts.constEnd(), [part](Part const & actualPart) { return part.partType().ns() == actualPart.partType().ns() && part.partType().name() == actualPart.partType().name(); }); QVERIFY(actualPartIter != actualParts.constEnd()); const Part actualPart = *actualPartIter; QVERIFY(actualPart.isValid()); QCOMPARE(QString::fromUtf8(actualPart.data()), QString::fromUtf8(part.data())); QCOMPARE(actualPart.data(), part.data()); QCOMPARE(actualPart.datasize(), part.datasize()); QCOMPARE(actualPart.storage(), part.storage()); } } } }; -AKTEST_FAKESERVER_MAIN(AkAppendHandlerTest) +AKTEST_FAKESERVER_MAIN(ItemCreateHandlerTest) -#include "akappendhandlertest.moc" +#include "itemcreatehandlertest.moc" diff --git a/autotests/server/linkhandlertest.cpp b/autotests/server/itemlinkhandlertest.cpp similarity index 98% rename from autotests/server/linkhandlertest.cpp rename to autotests/server/itemlinkhandlertest.cpp index 21453c9a4..29a3fb731 100644 --- a/autotests/server/linkhandlertest.cpp +++ b/autotests/server/itemlinkhandlertest.cpp @@ -1,291 +1,289 @@ /* Copyright (c) 2014 Daniel Vrátil This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include -#include - #include "fakeakonadiserver.h" #include #include "entities.h" #include #include #include using namespace Akonadi; using namespace Akonadi::Server; -class LinkHandlerTest : public QObject +class ItemLinkHandlerTest : public QObject { Q_OBJECT public: - LinkHandlerTest() + ItemLinkHandlerTest() { qRegisterMetaType(); try { FakeAkonadiServer::instance()->init(); } catch (const FakeAkonadiServerException &e) { qWarning() << "Server exception: " << e.what(); qFatal("Fake Akonadi Server failed to start up, aborting test"); } } - ~LinkHandlerTest() + ~ItemLinkHandlerTest() { FakeAkonadiServer::instance()->quit(); } Protocol::LinkItemsResponsePtr createError(const QString &error) { auto resp = Protocol::LinkItemsResponsePtr::create(); resp->setError(1, error); return resp; } Protocol::FetchItemsResponse itemResponse(qint64 id, const QString &rid, const QString &rrev, const QString &mimeType) { Protocol::FetchItemsResponse item; item.setId(id); item.setRemoteId(rid); item.setRemoteRevision(rrev); item.setMimeType(mimeType); return item; } private Q_SLOTS: void testLink_data() { QTest::addColumn("scenarios"); QTest::addColumn("notification"); QTest::addColumn("expectFail"); TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, Protocol::LinkItemsCommandPtr::create(Protocol::LinkItemsCommand::Link, ImapInterval(1, 3), 3)) << TestScenario::create(5, TestScenario::ServerCmd, createError(QStringLiteral("Can't link items to non-virtual collections"))); QTest::newRow("non-virtual collection") << scenarios << Protocol::ItemChangeNotificationPtr::create() << true; auto notification = Protocol::ItemChangeNotificationPtr::create(); notification->setOperation(Protocol::ItemChangeNotification::Link); notification->setItems({ itemResponse(1, QLatin1String("A"), QString(), QLatin1String("application/octet-stream")), itemResponse(2, QLatin1String("B"), QString(), QLatin1String("application/octet-stream")), itemResponse(3, QLatin1String("C"), QString(), QLatin1String("application/octet-stream")) }); notification->setParentCollection(6); notification->setResource("akonadi_fake_resource_with_virtual_collections_0"); notification->setSessionId(FakeAkonadiServer::instanceName().toLatin1()); scenarios.clear(); scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, Protocol::LinkItemsCommandPtr::create(Protocol::LinkItemsCommand::Link, ImapInterval(1, 3), 6)) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::LinkItemsResponsePtr::create()); QTest::newRow("normal") << scenarios << notification << false; notification = Protocol::ItemChangeNotificationPtr::create(*notification); notification->setItems({ itemResponse(4, QLatin1String("D"), QString(), QLatin1String("application/octet-stream")) }); scenarios.clear(); scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, Protocol::LinkItemsCommandPtr::create(Protocol::LinkItemsCommand::Link, QVector{ 4, 123456 }, 6)) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::LinkItemsResponsePtr::create()); QTest::newRow("existent and non-existent item") << scenarios << notification << false; scenarios.clear(); scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, Protocol::LinkItemsCommandPtr::create(Protocol::LinkItemsCommand::Link, 4, 6)) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::LinkItemsResponsePtr::create()); QTest::newRow("non-existent item only") << scenarios << Protocol::ItemChangeNotificationPtr::create() << false; //FIXME: All RID related operations are currently broken because we reset the collection context before every command, //and LINK still relies on SELECT to set the collection context. // scenario.clear(); // scenario << FakeAkonadiServer::defaultScenario() // << FakeAkonadiServer::selectCollectionScenario(QLatin1String("Collection B")) // << "C: 3 UID LINK 6 RID (\"F\" \"G\")\n" // << "S: 3 OK LINK complete"; // notification.clearEntities(); // notification.clearEntities(); // notification.addEntity(6, QLatin1String("F"), QString(), QLatin1String("application/octet-stream")); // notification.addEntity(7, QLatin1String("G"), QString(), QLatin1String("application/octet-stream")); // QTest::newRow("RID items") << scenario << notification << false; // scenario.clear(); // scenario << FakeAkonadiServer::defaultScenario() // << FakeAkonadiServer::selectResourceScenario(QLatin1String("akonadi_fake_resource_with_virtual_collections_0")) // << "C: 4 HRID LINK ((-1, \"virtual2\") (-1, \"virtual\") (-1, \"\")) UID 5" // << "S: 4 OK LINK complete"; // notification.setParentCollection(7); // notification.clearEntities(); // notification.addEntity(5, QLatin1String("E"), QString(), QLatin1String("application/octet-stream")); // QTest::newRow("HRID collection") << scenario << notification << false; // scenario.clear(); // scenario << FakeAkonadiServer::defaultScenario() // << FakeAkonadiServer::selectResourceScenario(QLatin1String("akonadi_fake_resource_with_virtual_collections_0")) // << FakeAkonadiServer::selectCollectionScenario(QLatin1String("Collection B")) // << "C: 4 HRID LINK ((-1, \"virtual2\") (-1, \"virtual\") (-1, \"\")) RID \"H\"" // << "S: 4 OK LINK complete"; // notification.clearEntities(); // notification.addEntity(8, QLatin1String("H"), QString(), QLatin1String("application/octet-stream")); // QTest::newRow("HRID collection, RID items") << scenario << notification << false; } void testLink() { QFETCH(TestScenario::List, scenarios); QFETCH(Protocol::ItemChangeNotificationPtr, notification); QFETCH(bool, expectFail); FakeAkonadiServer::instance()->setScenarios(scenarios); FakeAkonadiServer::instance()->runTest(); auto notificationSpy = FakeAkonadiServer::instance()->notificationSpy(); if (notification->operation() != Protocol::ItemChangeNotification::InvalidOp) { QCOMPARE(notificationSpy->count(), 1); const Protocol::ChangeNotificationList notifications = notificationSpy->takeFirst().first().value(); QCOMPARE(notifications.count(), 1); QCOMPARE(*notifications.first().staticCast(), *notification); } else { QVERIFY(notificationSpy->isEmpty() || notificationSpy->takeFirst().first().value().isEmpty()); } Q_FOREACH (const auto &entity, notification->items()) { if (expectFail) { QVERIFY(!Collection::relatesToPimItem(notification->parentCollection(), entity.id())); } else { QVERIFY(Collection::relatesToPimItem(notification->parentCollection(), entity.id())); } } } void testUnlink_data() { QTest::addColumn("scenarios"); QTest::addColumn("notification"); QTest::addColumn("expectFail"); TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, Protocol::LinkItemsCommandPtr::create(Protocol::LinkItemsCommand::Unlink, ImapInterval(1, 3), 3)) << TestScenario::create(5, TestScenario::ServerCmd, createError(QStringLiteral("Can't link items to non-virtual collections"))); QTest::newRow("non-virtual collection") << scenarios << Protocol::ItemChangeNotificationPtr::create() << true; auto notification = Protocol::ItemChangeNotificationPtr::create(); notification->setOperation(Protocol::ItemChangeNotification::Unlink); notification->setItems({ itemResponse(1, QLatin1String("A"), QString(), QLatin1String("application/octet-stream")), itemResponse(2, QLatin1String("B"), QString(), QLatin1String("application/octet-stream")), itemResponse(3, QLatin1String("C"), QString(), QLatin1String("application/octet-stream")) }); notification->setParentCollection(6); notification->setResource("akonadi_fake_resource_with_virtual_collections_0"); notification->setSessionId(FakeAkonadiServer::instanceName().toLatin1()); scenarios.clear(); scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, Protocol::LinkItemsCommandPtr::create(Protocol::LinkItemsCommand::Unlink, ImapInterval(1, 3), 6)) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::LinkItemsResponsePtr::create()); QTest::newRow("normal") << scenarios << notification << false; notification = Protocol::ItemChangeNotificationPtr::create(*notification); notification->setItems({ itemResponse(4, QLatin1String("D"), QString(), QLatin1String("application/octet-stream")) }); scenarios.clear(); scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, Protocol::LinkItemsCommandPtr::create(Protocol::LinkItemsCommand::Unlink, QVector{ 4, 2048 }, 6)) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::LinkItemsResponsePtr::create()); QTest::newRow("existent and non-existent item") << scenarios << notification << false; scenarios.clear(); scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, Protocol::LinkItemsCommandPtr::create(Protocol::LinkItemsCommand::Unlink, 4096, 6)) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::LinkItemsResponsePtr::create()); QTest::newRow("non-existent item only") << scenarios << Protocol::ItemChangeNotificationPtr::create() << false; //FIXME: All RID related operations are currently broken because we reset the collection context before every command, //and LINK still relies on SELECT to set the collection context. // scenario.clear(); // scenario << FakeAkonadiServer::defaultScenario() // << FakeAkonadiServer::selectCollectionScenario(QLatin1String("Collection B")) // << "C: 4 UID UNLINK 6 RID (\"F\" \"G\")" // << "S: 4 OK LINK complete"; // notification.clearEntities(); // notification.clearEntities(); // notification.addEntity(6, QLatin1String("F"), QString(), QLatin1String("application/octet-stream")); // notification.addEntity(7, QLatin1String("G"), QString(), QLatin1String("application/octet-stream")); // QTest::newRow("RID items") << scenario << notification << false; // scenario.clear(); // scenario << FakeAkonadiServer::defaultScenario() // << FakeAkonadiServer::selectResourceScenario(QLatin1String("akonadi_fake_resource_with_virtual_collections_0")) // << "C: 4 HRID UNLINK ((-1, \"virtual2\") (-1, \"virtual\") (-1, \"\")) UID 5" // << "S: 4 OK LINK complete"; // notification.setParentCollection(7); // notification.clearEntities(); // notification.addEntity(5, QLatin1String("E"), QString(), QLatin1String("application/octet-stream")); // QTest::newRow("HRID collection") << scenario << notification << false; // scenario.clear(); // scenario << FakeAkonadiServer::defaultScenario() // << FakeAkonadiServer::selectCollectionScenario(QLatin1String("Collection B")) // << FakeAkonadiServer::selectResourceScenario(QLatin1String("akonadi_fake_resource_with_virtual_collections_0")) // << "C: 4 HRID UNLINK ((-1, \"virtual2\") (-1, \"virtual\") (-1, \"\")) RID \"H\"" // << "S: 4 OK LINK complete"; // notification.clearEntities(); // notification.addEntity(8, QLatin1String("H"), QString(), QLatin1String("application/octet-stream")); // QTest::newRow("HRID collection, RID items") << scenario << notification << false; } void testUnlink() { QFETCH(TestScenario::List, scenarios); QFETCH(Protocol::ItemChangeNotificationPtr, notification); QFETCH(bool, expectFail); FakeAkonadiServer::instance()->setScenarios(scenarios); FakeAkonadiServer::instance()->runTest(); auto notificationSpy = FakeAkonadiServer::instance()->notificationSpy(); if (notification->operation() != Protocol::ItemChangeNotification::InvalidOp) { QCOMPARE(notificationSpy->count(), 1); const auto notifications = notificationSpy->takeFirst().first().value(); QCOMPARE(notifications.count(), 1); QCOMPARE(*notifications.first().staticCast(), *notification); } else { QVERIFY(notificationSpy->isEmpty() || notificationSpy->takeFirst().first().value().isEmpty()); } Q_FOREACH (const auto &entity, notification->items()) { if (expectFail) { QVERIFY(Collection::relatesToPimItem(notification->parentCollection(), entity.id())); } else { QVERIFY(!Collection::relatesToPimItem(notification->parentCollection(), entity.id())); } } } }; -AKTEST_FAKESERVER_MAIN(LinkHandlerTest) +AKTEST_FAKESERVER_MAIN(ItemLinkHandlerTest) -#include "linkhandlertest.moc" +#include "itemlinkhandlertest.moc" diff --git a/autotests/server/movehandlertest.cpp b/autotests/server/itemmovehandlertest.cpp similarity index 97% rename from autotests/server/movehandlertest.cpp rename to autotests/server/itemmovehandlertest.cpp index 56c3a7be9..807487bfd 100644 --- a/autotests/server/movehandlertest.cpp +++ b/autotests/server/itemmovehandlertest.cpp @@ -1,155 +1,154 @@ /* Copyright (c) 2016 Daniel Vrátil This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include -#include #include #include "fakeakonadiserver.h" #include "aktest.h" #include "entities.h" #include #include #include using namespace Akonadi; using namespace Akonadi::Server; -class MoveHandlerTest : public QObject +class ItemMoveHandlerTest : public QObject { Q_OBJECT public: - MoveHandlerTest() + ItemMoveHandlerTest() { try { FakeAkonadiServer::instance()->init(); } catch (const FakeAkonadiServerException &e) { qWarning() << "Server exception: " << e.what(); qFatal("Fake Akonadi Server failed to start up, aborting test"); } } - ~MoveHandlerTest() + ~ItemMoveHandlerTest() { FakeAkonadiServer::instance()->quit(); } Protocol::FetchItemsResponse fetchResponse(quint64 id, const QString &rid, const QString &rrev, const QString &mt) { Protocol::FetchItemsResponse item; item.setId(id); item.setRemoteId(rid); item.setRemoteRevision(rrev); item.setMimeType(mt); return item; } private Q_SLOTS: void testMove_data() { const Collection srcCol = Collection::retrieveByName(QStringLiteral("Collection B")); const Collection destCol = Collection::retrieveByName(QStringLiteral("Collection A")); QTest::addColumn("scenarios"); QTest::addColumn("expectedNotifications"); QTest::addColumn("newValue"); auto notificationTemplate = Protocol::ItemChangeNotificationPtr::create(); notificationTemplate->setOperation(Protocol::ItemChangeNotification::Move); notificationTemplate->setResource("akonadi_fake_resource_0"); notificationTemplate->setDestinationResource("akonadi_fake_resource_0"); notificationTemplate->setSessionId(FakeAkonadiServer::instanceName().toLatin1()); notificationTemplate->setParentCollection(srcCol.id()); notificationTemplate->setParentDestCollection(destCol.id()); { auto cmd = Protocol::MoveItemsCommandPtr::create(1, destCol.id()); TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, cmd) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::MoveItemsResponsePtr::create()); auto notification = Protocol::ItemChangeNotificationPtr::create(*notificationTemplate); notification->setItems({ fetchResponse(1, QStringLiteral("A"), QString(), QStringLiteral("application/octet-stream")) }); QTest::newRow("move item") << scenarios << Protocol::ChangeNotificationList{ notification } << QVariant::fromValue(destCol.id()); } { auto cmd = Protocol::MoveItemsCommandPtr::create(QVector{ 2, 3 }, destCol.id()); TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, cmd) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::MoveItemsResponsePtr::create()); auto notification = Protocol::ItemChangeNotificationPtr::create(*notificationTemplate); notification->setItems({ fetchResponse(3, QStringLiteral("C"), QString(), QStringLiteral("application/octet-stream")), fetchResponse(2, QStringLiteral("B"), QString(), QStringLiteral("application/octet-stream")) }); QTest::newRow("move items") << scenarios << Protocol::ChangeNotificationList{ notification } << QVariant::fromValue(destCol.id()); } } void testMove() { QFETCH(TestScenario::List, scenarios); QFETCH(Protocol::ChangeNotificationList, expectedNotifications); QFETCH(QVariant, newValue); FakeAkonadiServer::instance()->setScenarios(scenarios); FakeAkonadiServer::instance()->runTest(); auto notificationSpy = FakeAkonadiServer::instance()->notificationSpy(); if (expectedNotifications.isEmpty()) { QVERIFY(notificationSpy->isEmpty() || notificationSpy->takeFirst().first().value().isEmpty()); return; } QCOMPARE(notificationSpy->count(), 1); //Only one notify call QCOMPARE(notificationSpy->first().count(), 1); const auto receivedNotifications = notificationSpy->first().first().value(); QCOMPARE(receivedNotifications.size(), expectedNotifications.count()); for (int i = 0; i < expectedNotifications.size(); i++) { QCOMPARE(*receivedNotifications.at(i).staticCast(), *expectedNotifications.at(i).staticCast()); const auto notification = receivedNotifications.at(i).staticCast(); QCOMPARE(notification->parentDestCollection(), newValue.toInt()); Q_FOREACH (const auto &ntfItem, notification->items()) { const PimItem item = PimItem::retrieveById(ntfItem.id()); QCOMPARE(item.collectionId(), newValue.toInt()); } } } }; -AKTEST_FAKESERVER_MAIN(MoveHandlerTest) +AKTEST_FAKESERVER_MAIN(ItemMoveHandlerTest) -#include "movehandlertest.moc" +#include "itemmovehandlertest.moc" diff --git a/src/server/CMakeLists.txt b/src/server/CMakeLists.txt index fefdd6a8b..db636c104 100644 --- a/src/server/CMakeLists.txt +++ b/src/server/CMakeLists.txt @@ -1,196 +1,196 @@ include_directories(BEFORE ${CMAKE_CURRENT_BINARY_DIR}) include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR}) if(MYSQLD_EXECUTABLE) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DMYSQLD_EXECUTABLE=\"\\\"${MYSQLD_EXECUTABLE}\\\"\"") endif() if(POSTGRES_PATH) set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DPOSTGRES_PATH=\"\\\"${POSTGRES_PATH}\\\"\"") endif() ########### next target ############### set(AKONADI_DB_SCHEMA "${CMAKE_CURRENT_SOURCE_DIR}/storage/akonadidb.xml") akonadi_run_xsltproc( XSL ${CMAKE_CURRENT_SOURCE_DIR}/storage/entities.xsl XML ${AKONADI_DB_SCHEMA} BASENAME entities ) akonadi_run_xsltproc( XSL ${Akonadi_SOURCE_DIR}/src/server/storage/schema.xsl XML ${AKONADI_DB_SCHEMA} CLASSNAME AkonadiSchema BASENAME akonadischema ) akonadi_add_xmllint_test( akonadidb-xmllint XSD ${CMAKE_CURRENT_SOURCE_DIR}/storage/akonadidb.xsd XML ${AKONADI_DB_SCHEMA} ) akonadi_add_xmllint_test( akonadidbupdate-xmllint XSD ${CMAKE_CURRENT_SOURCE_DIR}/storage/akonadidb.xsd XML ${AKONADI_DB_SCHEMA} ) set(libakonadiserver_SRCS akonadi.cpp aggregatedfetchscope.cpp aklocalserver.cpp akthread.cpp commandcontext.cpp connection.cpp collectionscheduler.cpp dbusconnectionpool.cpp handler.cpp handlerhelper.cpp intervalcheck.cpp collectionreferencemanager.cpp - handler/akappend.cpp - handler/copy.cpp - handler/colcopy.cpp - handler/colmove.cpp - handler/create.cpp - handler/delete.cpp - handler/fetch.cpp - handler/fetchhelper.cpp - handler/link.cpp - handler/list.cpp - handler/login.cpp - handler/logout.cpp - handler/modify.cpp - handler/move.cpp - handler/remove.cpp - handler/resourceselect.cpp - handler/relationstore.cpp - handler/relationremove.cpp - handler/relationfetch.cpp - handler/search.cpp + handler/collectioncopyhandler.cpp + handler/collectioncreatehandler.cpp + handler/collectiondeletehandler.cpp + handler/collectionfetchhandler.cpp + handler/collectionmodifyhandler.cpp + handler/collectionmovehandler.cpp + handler/collectionstatsfetchhandler.cpp + handler/itemcopyhandler.cpp + handler/itemcreatehandler.cpp + handler/itemdeletehandler.cpp + handler/itemfetchhandler.cpp + handler/itemfetchhelper.cpp + handler/itemlinkhandler.cpp + handler/itemmodifyhandler.cpp + handler/itemmovehandler.cpp + handler/loginhandler.cpp + handler/logouthandler.cpp + handler/relationfetchhandler.cpp + handler/relationmodifyhandler.cpp + handler/relationremovehandler.cpp + handler/resourceselecthandler.cpp + handler/searchhandler.cpp handler/searchhelper.cpp - handler/searchpersistent.cpp - handler/searchresult.cpp - handler/status.cpp - handler/store.cpp - handler/tagappend.cpp - handler/tagfetch.cpp + handler/searchcreatehandler.cpp + handler/searchresulthandler.cpp + handler/tagcreatehandler.cpp + handler/tagdeletehandler.cpp + handler/tagfetchhandler.cpp handler/tagfetchhelper.cpp - handler/tagremove.cpp - handler/tagstore.cpp - handler/transaction.cpp + handler/tagmodifyhandler.cpp + handler/transactionhandler.cpp search/agentsearchengine.cpp search/agentsearchinstance.cpp search/searchtaskmanager.cpp search/searchrequest.cpp search/searchmanager.cpp storage/collectionqueryhelper.cpp storage/collectionstatistics.cpp storage/entity.cpp ${CMAKE_CURRENT_BINARY_DIR}/entities.cpp ${CMAKE_CURRENT_BINARY_DIR}/akonadischema.cpp storage/datastore.cpp storage/dbconfig.cpp storage/dbconfigmysql.cpp storage/dbconfigpostgresql.cpp storage/dbconfigsqlite.cpp storage/dbexception.cpp storage/dbinitializer.cpp storage/dbinitializer_p.cpp storage/dbintrospector.cpp storage/dbintrospector_impl.cpp storage/dbupdater.cpp storage/dbtype.cpp storage/itemqueryhelper.cpp storage/itemretriever.cpp storage/itemretrievalmanager.cpp storage/itemretrievaljob.cpp storage/notificationcollector.cpp storage/parthelper.cpp storage/parttypehelper.cpp storage/query.cpp storage/querybuilder.cpp storage/querycache.cpp storage/queryhelper.cpp storage/schematypes.cpp storage/tagqueryhelper.cpp storage/transaction.cpp storage/parthelper.cpp storage/partstreamer.cpp storage/storagedebugger.cpp tracer.cpp utils.cpp dbustracer.cpp filetracer.cpp notificationmanager.cpp notificationsubscriber.cpp resourcemanager.cpp cachecleaner.cpp debuginterface.cpp preprocessorinstance.cpp preprocessormanager.cpp storagejanitor.cpp ) set(akonadiserver_SRCS main.cpp ) ecm_qt_declare_logging_category(akonadiserver_SRCS HEADER akonadiserver_debug.h IDENTIFIER AKONADISERVER_LOG CATEGORY_NAME org.kde.pim.akonadiserver) ecm_qt_declare_logging_category(akonadiserver_SRCS HEADER akonadiserver_search_debug.h IDENTIFIER AKONADISERVER_SEARCH_LOG CATEGORY_NAME org.kde.pim.akonadiserver.search) qt5_generate_dbus_interface(debuginterface.h org.freedesktop.Akonadi.DebugInterface.xml) qt5_add_dbus_adaptor(libakonadiserver_SRCS ${Akonadi_SOURCE_DIR}/src/interfaces/org.freedesktop.Akonadi.TracerNotification.xml dbustracer.h Akonadi::Server::DBusTracer) qt5_add_dbus_adaptor(libakonadiserver_SRCS ${Akonadi_SOURCE_DIR}/src/interfaces/org.freedesktop.Akonadi.Tracer.xml tracer.h Akonadi::Server::Tracer) qt5_add_dbus_adaptor(libakonadiserver_SRCS ${Akonadi_SOURCE_DIR}/src/interfaces/org.freedesktop.Akonadi.Server.xml akonadi.h Akonadi::Server::AkonadiServer) qt5_add_dbus_adaptor(libakonadiserver_SRCS ${Akonadi_SOURCE_DIR}/src/interfaces/org.freedesktop.Akonadi.StorageDebugger.xml storage/storagedebugger.h Akonadi::Server::StorageDebugger) qt5_add_dbus_adaptor(libakonadiserver_SRCS ${CMAKE_CURRENT_BINARY_DIR}/org.freedesktop.Akonadi.DebugInterface.xml debuginterface.h Akonadi::Server::DebugInterface) qt5_add_dbus_adaptor(libakonadiserver_SRCS ${Akonadi_SOURCE_DIR}/src/interfaces/org.freedesktop.Akonadi.ResourceManager.xml resourcemanager.h Akonadi::Server::ResourceManager) qt5_add_dbus_adaptor(libakonadiserver_SRCS ${Akonadi_SOURCE_DIR}/src/interfaces/org.freedesktop.Akonadi.PreprocessorManager.xml preprocessormanager.h Akonadi::Server::PreprocessorManager) qt5_add_dbus_interface(libakonadiserver_SRCS ${Akonadi_SOURCE_DIR}/src/interfaces/org.freedesktop.Akonadi.AgentManager.xml agentmanagerinterface) qt5_add_dbus_interface(libakonadiserver_SRCS ${Akonadi_SOURCE_DIR}/src/interfaces/org.freedesktop.Akonadi.Resource.xml resourceinterface) qt5_add_dbus_interface(libakonadiserver_SRCS ${Akonadi_SOURCE_DIR}/src/interfaces/org.freedesktop.Akonadi.Preprocessor.xml preprocessorinterface) qt5_add_dbus_interface(libakonadiserver_SRCS ${Akonadi_SOURCE_DIR}/src/interfaces/org.freedesktop.Akonadi.Agent.Control.xml agentcontrolinterface) qt5_add_dbus_interface(libakonadiserver_SRCS ${Akonadi_SOURCE_DIR}/src/interfaces/org.freedesktop.Akonadi.Agent.Search.xml agentsearchinterface) qt5_add_resources(libakonadiserver_SRCS storage/akonadidb.qrc) add_library(libakonadiserver STATIC ${libakonadiserver_SRCS}) set_target_properties(libakonadiserver PROPERTIES OUTPUT_NAME akonadiserver) target_link_libraries(libakonadiserver akonadi_shared KF5AkonadiPrivate Qt5::Core Qt5::Network Qt5::Sql Qt5::DBus Qt5::Xml ) add_executable(akonadiserver ${akonadiserver_SRCS}) set_target_properties(akonadiserver PROPERTIES OUTPUT_NAME akonadiserver) target_link_libraries(akonadiserver libakonadiserver KF5::CoreAddons ) install(TARGETS akonadiserver ${KF5_INSTALL_TARGETS_DEFAULT_ARGS} ) install(FILES storage/mysql-global.conf storage/mysql-global-mobile.conf DESTINATION ${CONFIG_INSTALL_DIR}/akonadi ) install(FILES search/abstractsearchplugin.h DESTINATION ${KF5_INCLUDE_INSTALL_DIR}/akonadi ) ## DBus XML files install(FILES ${CMAKE_CURRENT_BINARY_DIR}/org.freedesktop.Akonadi.DebugInterface.xml DESTINATION ${AKONADI_DBUS_INTERFACES_INSTALL_DIR} ) diff --git a/src/server/handler.cpp b/src/server/handler.cpp index 6b4534d01..c78edb7e3 100644 --- a/src/server/handler.cpp +++ b/src/server/handler.cpp @@ -1,265 +1,264 @@ /*************************************************************************** * Copyright (C) 2006 by Till Adam * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * 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 Library 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 "handler.h" #include #include #include "connection.h" -#include "handler/akappend.h" -#include "handler/copy.h" -#include "handler/colcopy.h" -#include "handler/colmove.h" -#include "handler/create.h" -#include "handler/delete.h" -#include "handler/fetch.h" -#include "handler/link.h" -#include "handler/list.h" -#include "handler/login.h" -#include "handler/logout.h" -#include "handler/modify.h" -#include "handler/move.h" -#include "handler/remove.h" -#include "handler/resourceselect.h" -#include "handler/search.h" -#include "handler/searchpersistent.h" -#include "handler/searchresult.h" -#include "handler/status.h" -#include "handler/store.h" -#include "handler/transaction.h" -#include "handler/tagappend.h" -#include "handler/tagfetch.h" -#include "handler/tagremove.h" -#include "handler/tagstore.h" -#include "handler/relationstore.h" -#include "handler/relationremove.h" -#include "handler/relationfetch.h" - +#include "handler/collectioncopyhandler.h" +#include "handler/collectioncreatehandler.h" +#include "handler/collectiondeletehandler.h" +#include "handler/collectionfetchhandler.h" +#include "handler/collectionmodifyhandler.h" +#include "handler/collectionmovehandler.h" +#include "handler/collectionstatsfetchhandler.h" +#include "handler/itemcopyhandler.h" +#include "handler/itemcreatehandler.h" +#include "handler/itemdeletehandler.h" +#include "handler/itemfetchhandler.h" +#include "handler/itemlinkhandler.h" +#include "handler/itemmodifyhandler.h" +#include "handler/itemmovehandler.h" +#include "handler/loginhandler.h" +#include "handler/logouthandler.h" +#include "handler/relationfetchhandler.h" +#include "handler/relationremovehandler.h" +#include "handler/relationmodifyhandler.h" +#include "handler/resourceselecthandler.h" +#include "handler/searchcreatehandler.h" +#include "handler/searchhandler.h" +#include "handler/searchresulthandler.h" +#include "handler/tagcreatehandler.h" +#include "handler/tagdeletehandler.h" +#include "handler/tagfetchhandler.h" +#include "handler/tagmodifyhandler.h" +#include "handler/transactionhandler.h" #include "storage/querybuilder.h" using namespace Akonadi; using namespace Akonadi::Server; Handler *Handler::findHandlerForCommandNonAuthenticated(Protocol::Command::Type cmd) { // allowed are LOGIN if (cmd == Protocol::Command::Login) { - return new Login(); + return new LoginHandler(); } return nullptr; } Handler *Handler::findHandlerForCommandAlwaysAllowed(Protocol::Command::Type cmd) { // allowed is LOGOUT if (cmd == Protocol::Command::Logout) { - return new Logout(); + return new LogoutHandler(); } return nullptr; } Handler *Handler::findHandlerForCommandAuthenticated(Protocol::Command::Type cmd) { switch (cmd) { case Protocol::Command::Invalid: Q_ASSERT_X(cmd != Protocol::Command::Invalid, __FUNCTION__, "Invalid command is not allowed"); return nullptr; case Protocol::Command::Hello: Q_ASSERT_X(cmd != Protocol::Command::Hello, __FUNCTION__, "Hello command is not allowed in this context"); return nullptr; case Protocol::Command::Login: return nullptr; case Protocol::Command::Logout: return nullptr; case Protocol::Command::_ResponseBit: Q_ASSERT_X(cmd != Protocol::Command::_ResponseBit, __FUNCTION__, "ResponseBit is not a valid command type"); return nullptr; case Protocol::Command::Transaction: return new TransactionHandler(); case Protocol::Command::CreateItem: - return new AkAppend(); + return new ItemCreateHandler(); case Protocol::Command::CopyItems: - return new Copy(); + return new ItemCopyHandler(); case Protocol::Command::DeleteItems: - return new Remove(); + return new ItemDeleteHandler(); case Protocol::Command::FetchItems: - return new Fetch(); + return new ItemFetchHandler(); case Protocol::Command::LinkItems: - return new Link(); + return new ItemLinkHandler(); case Protocol::Command::ModifyItems: - return new Store(); + return new ItemModifyHandler(); case Protocol::Command::MoveItems: - return new Move(); + return new ItemMoveHandler(); case Protocol::Command::CreateCollection: - return new Create(); + return new CollectionCreateHandler(); case Protocol::Command::CopyCollection: - return new ColCopy(); + return new CollectionCopyHandler(); case Protocol::Command::DeleteCollection: - return new Delete(); + return new CollectionDeleteHandler(); case Protocol::Command::FetchCollections: - return new List(); + return new CollectionFetchHandler(); case Protocol::Command::FetchCollectionStats: - return new Status(); + return new CollectionStatsFetchHandler(); case Protocol::Command::ModifyCollection: - return new Modify(); + return new CollectionModifyHandler(); case Protocol::Command::MoveCollection: - return new ColMove(); + return new CollectionMoveHandler(); case Protocol::Command::Search: - return new Search(); + return new SearchHandler(); case Protocol::Command::SearchResult: - return new SearchResult(); + return new SearchResultHandler(); case Protocol::Command::StoreSearch: - return new SearchPersistent(); + return new SearchCreateHandler(); case Protocol::Command::CreateTag: - return new TagAppend(); + return new TagCreateHandler(); case Protocol::Command::DeleteTag: - return new TagRemove(); + return new TagDeleteHandler(); case Protocol::Command::FetchTags: - return new TagFetch(); + return new TagFetchHandler(); case Protocol::Command::ModifyTag: - return new TagStore(); + return new TagModifyHandler(); case Protocol::Command::FetchRelations: - return new RelationFetch(); + return new RelationFetchHandler(); case Protocol::Command::ModifyRelation: - return new RelationStore(); + return new RelationModifyHandler(); case Protocol::Command::RemoveRelations: - return new RelationRemove(); + return new RelationRemoveHandler(); case Protocol::Command::SelectResource: - return new ResourceSelect(); + return new ResourceSelectHandler(); case Protocol::Command::StreamPayload: Q_ASSERT_X(cmd != Protocol::Command::StreamPayload, __FUNCTION__, "StreamPayload command is not allowed in this context"); return nullptr; case Protocol::Command::ItemChangeNotification: Q_ASSERT_X(cmd != Protocol::Command::ItemChangeNotification, __FUNCTION__, "ItemChangeNotification command is not allowed on this connection"); return nullptr; case Protocol::Command::CollectionChangeNotification: Q_ASSERT_X(cmd != Protocol::Command::CollectionChangeNotification, __FUNCTION__, "CollectionChangeNotification command is not allowed on this connection"); return nullptr; case Protocol::Command::TagChangeNotification: Q_ASSERT_X(cmd != Protocol::Command::TagChangeNotification, __FUNCTION__, "TagChangeNotification command is not allowed on this connection"); return nullptr; case Protocol::Command::RelationChangeNotification: Q_ASSERT_X(cmd != Protocol::Command::RelationChangeNotification, __FUNCTION__, "RelationChangeNotification command is not allowed on this connection"); return nullptr; case Protocol::Command::SubscriptionChangeNotification: Q_ASSERT_X(cmd != Protocol::Command::SubscriptionChangeNotification, __FUNCTION__, "SubscriptionChangeNotification command is not allowed on this connection"); return nullptr; case Protocol::Command::DebugChangeNotification: Q_ASSERT_X(cmd != Protocol::Command::DebugChangeNotification, __FUNCTION__, "DebugChangeNotification command is not allowed on this connection"); return nullptr; case Protocol::Command::ModifySubscription: Q_ASSERT_X(cmd != Protocol::Command::ModifySubscription, __FUNCTION__, "ModifySubscription command is not allowed on this connection"); return nullptr; case Protocol::Command::CreateSubscription: Q_ASSERT_X(cmd != Protocol::Command::CreateSubscription, __FUNCTION__, "CreateSubscription command is not allowed on this connection"); return nullptr; } return nullptr; } void Handler::setTag(quint64 tag) { m_tag = tag; } quint64 Handler::tag() const { return m_tag; } void Handler::setCommand(const Protocol::CommandPtr &cmd) { m_command = cmd; } Protocol::CommandPtr Handler::command() const { return m_command; } void Handler::setConnection(Connection *connection) { m_connection = connection; } Connection *Handler::connection() const { return m_connection; } DataStore *Handler::storageBackend() const { return m_connection->storageBackend(); } bool Handler::failureResponse(const QByteArray &failureMessage) { return failureResponse(QString::fromUtf8(failureMessage)); } bool Handler::failureResponse(const char *failureMessage) { return failureResponse(QString::fromUtf8(failureMessage)); } bool Handler::failureResponse(const QString &failureMessage) { // Prevent sending multiple error responses from a single handler (or from // a handler and then from Connection, since clients only expect a single // error response if (!m_sentFailureResponse) { m_sentFailureResponse = true; Protocol::ResponsePtr r = Protocol::Factory::response(m_command->type()); // FIXME: Error enums? r->setError(1, failureMessage); m_connection->sendResponse(m_tag, r); } return false; } bool Handler::checkScopeConstraints(const Akonadi::Scope &scope, int permittedScopes) { return scope.scope() & permittedScopes; } diff --git a/src/server/handler/colcopy.cpp b/src/server/handler/collectioncopyhandler.cpp similarity index 93% rename from src/server/handler/colcopy.cpp rename to src/server/handler/collectioncopyhandler.cpp index 308d8efd7..b5242d51e 100644 --- a/src/server/handler/colcopy.cpp +++ b/src/server/handler/collectioncopyhandler.cpp @@ -1,120 +1,120 @@ /* Copyright (c) 2008 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "colcopy.h" +#include "collectioncopyhandler.h" #include "connection.h" #include "handlerhelper.h" #include "cachecleaner.h" #include "storage/datastore.h" #include "storage/transaction.h" #include "storage/itemretriever.h" #include "storage/collectionqueryhelper.h" using namespace Akonadi; using namespace Akonadi::Server; -bool ColCopy::copyCollection(const Collection &source, const Collection &target) +bool CollectionCopyHandler::copyCollection(const Collection &source, const Collection &target) { if (!CollectionQueryHelper::canBeMovedTo(source, target)) { // We don't accept source==target, or source being an ancestor of target. return false; } // copy the source collection Collection col = source; col.setParentId(target.id()); col.setResourceId(target.resourceId()); // clear remote id and revision on inter-resource copies if (source.resourceId() != target.resourceId()) { col.setRemoteId(QString()); col.setRemoteRevision(QString()); } const auto sourceMimeTypes = source.mimeTypes(); QStringList mimeTypes; mimeTypes.reserve(sourceMimeTypes.size()); std::transform(sourceMimeTypes.cbegin(), sourceMimeTypes.cend(), std::back_inserter(mimeTypes), [](const MimeType &mt) { return mt.name(); }); const auto sourceAttributes = source.attributes(); QMap attributes; for (const auto &attr : sourceAttributes) { attributes.insert(attr.type(), attr.value()); } if (!storageBackend()->appendCollection(col, mimeTypes, attributes)) { return false; } // copy sub-collections const Collection::List lstCols = source.children(); for (const Collection &child : lstCols) { if (!copyCollection(child, col)) { return false; } } // copy items Q_FOREACH (const PimItem &item, source.items()) { if (!copyItem(item, col)) { return false; } } return true; } -bool ColCopy::parseStream() +bool CollectionCopyHandler::parseStream() { const auto &cmd = Protocol::cmdCast(m_command); const Collection source = HandlerHelper::collectionFromScope(cmd.collection(), connection()); if (!source.isValid()) { return failureResponse(QStringLiteral("No valid source specified")); } const Collection target = HandlerHelper::collectionFromScope(cmd.destination(), connection()); if (!target.isValid()) { return failureResponse(QStringLiteral("No valid target specified")); } CacheCleanerInhibitor inhibitor; // retrieve all not yet cached items of the source ItemRetriever retriever(connection()); retriever.setCollection(source, true); retriever.setRetrieveFullPayload(true); if (!retriever.exec()) { return failureResponse(retriever.lastError()); } - Transaction transaction(storageBackend(), QStringLiteral("COLCOPY")); + Transaction transaction(storageBackend(), QStringLiteral("CollectionCopyHandler")); if (!copyCollection(source, target)) { return failureResponse(QStringLiteral("Failed to copy collection")); } if (!transaction.commit()) { return failureResponse(QStringLiteral("Cannot commit transaction.")); } return successResponse(); } diff --git a/src/server/handler/colcopy.h b/src/server/handler/collectioncopyhandler.h similarity index 85% rename from src/server/handler/colcopy.h rename to src/server/handler/collectioncopyhandler.h index 115720d28..abe2257bb 100644 --- a/src/server/handler/colcopy.h +++ b/src/server/handler/collectioncopyhandler.h @@ -1,63 +1,63 @@ /* Copyright (c) 2008 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef AKONADI_COLCOPY_H -#define AKONADI_COLCOPY_H +#ifndef AKONADI_COLLECTIONCOPYHANDLER_H_ +#define AKONADI_COLLECTIONCOPYHANDLER_H_ -#include "handler/copy.h" +#include "handler/itemcopyhandler.h" #include "entities.h" namespace Akonadi { namespace Server { /** @ingroup akonadi_server_handler - Handler for the COLCOPY command. + Handler for the CollectionCopyHandler command. This command is used to copy a single collection into another collection, including all sub-collections and their content. The copied items differ in the following points from the originals: - new unique id - empty remote id - possible located in a different collection (and thus resource) The copied collections differ in the following points from the originals: - new unique id - empty remote id - owning resource is the same as the one of the target collection */ -class ColCopy : public Copy +class CollectionCopyHandler : public ItemCopyHandler { public: - ~ColCopy() override = default; + ~CollectionCopyHandler() override = default; bool parseStream() override; private: bool copyCollection(const Collection &source, const Collection &target); }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler/create.cpp b/src/server/handler/collectioncreatehandler.cpp similarity index 98% rename from src/server/handler/create.cpp rename to src/server/handler/collectioncreatehandler.cpp index f4dcda806..d23cad013 100644 --- a/src/server/handler/create.cpp +++ b/src/server/handler/collectioncreatehandler.cpp @@ -1,134 +1,134 @@ /*************************************************************************** * Copyright (C) 2006 by Ingo Kloecker * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * 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 Library 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 "create.h" +#include "collectioncreatehandler.h" #include "connection.h" #include "handlerhelper.h" #include "storage/datastore.h" #include "storage/transaction.h" #include "storage/selectquerybuilder.h" #include using namespace Akonadi; using namespace Akonadi::Server; -bool Create::parseStream() +bool CollectionCreateHandler::parseStream() { const auto &cmd = Protocol::cmdCast(m_command); if (cmd.name().isEmpty()) { return failureResponse(QStringLiteral("Invalid collection name")); } Collection parent; qint64 resourceId = 0; bool forceVirtual = false; MimeType::List parentContentTypes; // Invalid or empty scope means we refer to root collection if (cmd.parent().scope() != Scope::Invalid && !cmd.parent().isEmpty()) { parent = HandlerHelper::collectionFromScope(cmd.parent(), connection()); if (!parent.isValid()) { return failureResponse(QStringLiteral("Invalid parent collection")); } // check if parent can contain a sub-folder parentContentTypes = parent.mimeTypes(); bool found = false, foundVirtual = false; for (const MimeType &mt : qAsConst(parentContentTypes)) { const QString mtName{mt.name()}; if (mtName == QLatin1String("inode/directory")) { found = true; } else if (mtName == QLatin1String("application/x-vnd.akonadi.collection.virtual")) { foundVirtual = true; } if (found && foundVirtual) { break; } } if (!found && !foundVirtual) { return failureResponse(QStringLiteral("Parent collection can not contain sub-collections")); } // If only virtual collections are supported, force every new collection to // be virtual. Otherwise depend on VIRTUAL attribute in the command if (foundVirtual && !found) { forceVirtual = true; } // inherit resource resourceId = parent.resourceId(); } else { const QString sessionId = QString::fromUtf8(connection()->sessionId()); Resource res = Resource::retrieveByName(sessionId); if (!res.isValid()) { return failureResponse(QStringLiteral("Cannot create top-level collection")); } resourceId = res.id(); } Collection collection; if (parent.isValid()) { collection.setParentId(parent.id()); } collection.setName(cmd.name()); collection.setResourceId(resourceId); collection.setRemoteId(cmd.remoteId()); collection.setRemoteRevision(cmd.remoteRevision()); collection.setIsVirtual(cmd.isVirtual() || forceVirtual); collection.setEnabled(cmd.enabled()); collection.setSyncPref(static_cast(cmd.syncPref())); collection.setDisplayPref(static_cast(cmd.displayPref())); collection.setIndexPref(static_cast(cmd.indexPref())); const Protocol::CachePolicy &cp = cmd.cachePolicy(); collection.setCachePolicyCacheTimeout(cp.cacheTimeout()); collection.setCachePolicyCheckInterval(cp.checkInterval()); collection.setCachePolicyInherit(cp.inherit()); collection.setCachePolicyLocalParts(cp.localParts().join(QLatin1Char(' '))); collection.setCachePolicySyncOnDemand(cp.syncOnDemand()); DataStore *db = connection()->storageBackend(); Transaction transaction(db, QStringLiteral("CREATE")); QStringList effectiveMimeTypes = cmd.mimeTypes(); if (effectiveMimeTypes.isEmpty()) { effectiveMimeTypes.reserve(parentContentTypes.count()); for (const MimeType &mt : qAsConst(parentContentTypes)) { effectiveMimeTypes << mt.name(); } } if (!db->appendCollection(collection, effectiveMimeTypes, cmd.attributes())) { return failureResponse(QStringLiteral("Could not create collection ") % cmd.name() % QStringLiteral(", resourceId: ") % QString::number(resourceId)); } if (!transaction.commit()) { return failureResponse(QStringLiteral("Unable to commit transaction.")); } db->activeCachePolicy(collection); sendResponse( HandlerHelper::fetchCollectionsResponse(collection)); return successResponse(); } diff --git a/src/server/handler/status.h b/src/server/handler/collectioncreatehandler.h similarity index 89% rename from src/server/handler/status.h rename to src/server/handler/collectioncreatehandler.h index cb2d60694..91c80d31b 100644 --- a/src/server/handler/status.h +++ b/src/server/handler/collectioncreatehandler.h @@ -1,45 +1,43 @@ /*************************************************************************** * Copyright (C) 2006 by Ingo Kloecker * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * 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 Library 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 AKONADISTATUS_H -#define AKONADISTATUS_H +#ifndef AKONADI_COLLECTIONCREATEHANDLER_H_ +#define AKONADI_COLLECTIONCREATEHANDLER_H_ #include "handler.h" namespace Akonadi { namespace Server { /** @ingroup akonadi_server_handler - - Handler for the STATUS command. */ -class Status : public Handler +class CollectionCreateHandler: public Handler { public: - ~Status() override = default; + ~CollectionCreateHandler() override = default; bool parseStream() override; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler/delete.cpp b/src/server/handler/collectiondeletehandler.cpp similarity index 94% rename from src/server/handler/delete.cpp rename to src/server/handler/collectiondeletehandler.cpp index 46480399e..8078339cf 100644 --- a/src/server/handler/delete.cpp +++ b/src/server/handler/collectiondeletehandler.cpp @@ -1,76 +1,76 @@ /* Copyright (c) 2006 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "delete.h" +#include "collectiondeletehandler.h" #include "connection.h" #include "handlerhelper.h" #include "storage/datastore.h" #include "storage/transaction.h" #include "storage/selectquerybuilder.h" #include "storage/collectionqueryhelper.h" #include "search/searchmanager.h" #include using namespace Akonadi; using namespace Akonadi::Server; -bool Delete::deleteRecursive(Collection &col) +bool CollectionDeleteHandler::deleteRecursive(Collection &col) { Collection::List children = col.children(); for (Collection &child : children) { if (!deleteRecursive(child)) { return false; } } DataStore *db = connection()->storageBackend(); return db->cleanupCollection(col); } -bool Delete::parseStream() +bool CollectionDeleteHandler::parseStream() { const auto &cmd = Protocol::cmdCast(m_command); Collection collection = HandlerHelper::collectionFromScope(cmd.collection(), connection()); if (!collection.isValid()) { return failureResponse(QStringLiteral("No such collection.")); } // handle virtual folders if (collection.resource().name() == QLatin1String(AKONADI_SEARCH_RESOURCE)) { // don't delete virtual root if (collection.parentId() == 0) { return failureResponse(QStringLiteral("Cannot delete virtual root collection")); } } Transaction transaction(storageBackend(), QStringLiteral("DELETE")); if (!deleteRecursive(collection)) { return failureResponse(QStringLiteral("Unable to delete collection")); } if (!transaction.commit()) { return failureResponse(QStringLiteral("Unable to commit transaction")); } return successResponse(); } diff --git a/src/server/handler/delete.h b/src/server/handler/collectiondeletehandler.h similarity index 87% rename from src/server/handler/delete.h rename to src/server/handler/collectiondeletehandler.h index 5464c6173..99dba6360 100644 --- a/src/server/handler/delete.h +++ b/src/server/handler/collectiondeletehandler.h @@ -1,55 +1,55 @@ /* Copyright (c) 2006 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef AKONADI_DELETE_H -#define AKONADI_DELETE_H +#ifndef AKONADI_COLLECTIONDELETEHANDLER_H_ +#define AKONADI_COLLECTIONDELETEHANDLER_H_ #include "handler.h" namespace Akonadi { namespace Server { class Collection; /** @ingroup akonadi_server_handler Handler for the collection deletion command. This commands deletes the selected collections including all their content and that of any child collection. */ -class Delete : public Handler +class CollectionDeleteHandler: public Handler { public: - ~Delete() override = default; + ~CollectionDeleteHandler() override = default; bool parseStream() override; private: bool deleteRecursive(Collection &col); }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler/list.cpp b/src/server/handler/collectionfetchhandler.cpp similarity index 96% rename from src/server/handler/list.cpp rename to src/server/handler/collectionfetchhandler.cpp index 296808556..e53849349 100644 --- a/src/server/handler/list.cpp +++ b/src/server/handler/collectionfetchhandler.cpp @@ -1,606 +1,608 @@ /* Copyright (c) 2007 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "list.h" +#include "collectionfetchhandler.h" #include "akonadiserver_debug.h" #include "connection.h" #include "handlerhelper.h" #include "collectionreferencemanager.h" #include "storage/datastore.h" #include "storage/selectquerybuilder.h" #include "storage/collectionqueryhelper.h" #include using namespace Akonadi; using namespace Akonadi::Server; template static bool intersect(const QVector &l1, const QVector &l2) { for (const T &e2 : l2) { if (l1.contains(e2.id())) { return true; } } return false; } -QStack List::ancestorsForCollection(const Collection &col) +QStack CollectionFetchHandler::ancestorsForCollection(const Collection &col) { if (mAncestorDepth <= 0) { return QStack(); } QStack ancestors; Collection parent = col; for (int i = 0; i < mAncestorDepth; ++i) { if (parent.parentId() == 0) { break; } if (mAncestors.contains(parent.parentId())) { parent = mAncestors.value(parent.parentId()); } else { parent = mCollections.value(parent.parentId()); } if (!parent.isValid()) { qCWarning(AKONADISERVER_LOG) << "Found an invalid parent in ancestors of Collection" << col.name() << "(ID:" << col.id() << ")"; throw HandlerException("Found invalid parent in ancestors"); } ancestors.prepend(parent); } return ancestors; } -CollectionAttribute::List List::getAttributes(const Collection &col, const QSet &filter) +CollectionAttribute::List CollectionFetchHandler::getAttributes(const Collection &col, + const QSet &filter) { CollectionAttribute::List attributes; auto it = mCollectionAttributes.find(col.id()); while (it != mCollectionAttributes.end() && it.key() == col.id()) { if (filter.isEmpty() || filter.contains(it.value().type())) { attributes << it.value(); } ++it; } // We need the reference and enabled status, to not need to request the server multiple times. // Mostly interesting for ancestors for i.e. updates provided by the monitor. const bool isReferenced = connection()->collectionReferenceManager()->isReferenced(col.id(), connection()->sessionId()); if (isReferenced) { CollectionAttribute attr; attr.setType(AKONADI_PARAM_REFERENCED); attr.setValue("TRUE"); attributes << attr; } { CollectionAttribute attr; attr.setType(AKONADI_PARAM_ENABLED); attr.setValue(col.enabled() ? "TRUE" : "FALSE"); attributes << attr; } return attributes; } -void List::listCollection(const Collection &root, const QStack &ancestors, - const QStringList &mimeTypes, - const CollectionAttribute::List &attributes) +void CollectionFetchHandler::listCollection(const Collection &root, + const QStack &ancestors, + const QStringList &mimeTypes, + const CollectionAttribute::List &attributes) { const bool isReferencedFromSession = connection()->collectionReferenceManager()->isReferenced(root.id(), connection()->sessionId()); //We always expose referenced collections to the resource as referenced (although it's a different session) //Otherwise syncing wouldn't work. const bool resourceIsSynchronizing = root.referenced() && mCollectionsToSynchronize && connection()->context()->resource().isValid(); QStack ancestorAttributes; //backwards compatibility, collectionToByteArray will automatically fall-back to id + remoteid if (!mAncestorAttributes.isEmpty()) { ancestorAttributes.reserve(ancestors.size()); for (const Collection &col : ancestors) { ancestorAttributes.push(getAttributes(col, mAncestorAttributes)); } } // write out collection details Collection dummy = root; storageBackend()->activeCachePolicy(dummy); sendResponse(HandlerHelper::fetchCollectionsResponse(dummy, attributes, mIncludeStatistics, mAncestorDepth, ancestors, ancestorAttributes, isReferencedFromSession || resourceIsSynchronizing, mimeTypes)); } static Query::Condition filterCondition(const QString &column) { Query::Condition orCondition(Query::Or); orCondition.addValueCondition(column, Query::Equals, (int)Collection::True); Query::Condition andCondition(Query::And); andCondition.addValueCondition(column, Query::Equals, (int)Collection::Undefined); andCondition.addValueCondition(Collection::enabledFullColumnName(), Query::Equals, true); orCondition.addCondition(andCondition); orCondition.addValueCondition(Collection::referencedFullColumnName(), Query::Equals, true); return orCondition; } -bool List::checkFilterCondition(const Collection &col) const +bool CollectionFetchHandler::checkFilterCondition(const Collection &col) const { //Don't include the collection when only looking for enabled collections if (mEnabledCollections && !col.enabled()) { return false; } //Don't include the collection when only looking for collections to display/index/sync if (mCollectionsToDisplay && (((col.displayPref() == Collection::Undefined) && !col.enabled()) || (col.displayPref() == Collection::False))) { return false; } if (mCollectionsToIndex && (((col.indexPref() == Collection::Undefined) && !col.enabled()) || (col.indexPref() == Collection::False))) { return false; } //Single collection sync will still work since that is using a base fetch if (mCollectionsToSynchronize && (((col.syncPref() == Collection::Undefined) && !col.enabled()) || (col.syncPref() == Collection::False))) { return false; } return true; } static QSqlQuery getAttributeQuery(const QVariantList &ids, const QSet &requestedAttributes) { QueryBuilder qb(CollectionAttribute::tableName()); qb.addValueCondition(CollectionAttribute::collectionIdFullColumnName(), Query::In, ids); qb.addColumn(CollectionAttribute::collectionIdFullColumnName()); qb.addColumn(CollectionAttribute::typeFullColumnName()); qb.addColumn(CollectionAttribute::valueFullColumnName()); if (!requestedAttributes.isEmpty()) { QVariantList attributes; attributes.reserve(requestedAttributes.size()); for (const QByteArray &type : requestedAttributes) { attributes << type; } qb.addValueCondition(CollectionAttribute::typeFullColumnName(), Query::In, attributes); } qb.addSortColumn(CollectionAttribute::collectionIdFullColumnName(), Query::Ascending); if (!qb.exec()) { throw HandlerException("Unable to retrieve attributes for listing"); } return qb.query(); } -void List::retrieveAttributes(const QVariantList &collectionIds) +void CollectionFetchHandler::retrieveAttributes(const QVariantList &collectionIds) { //We are querying for the attributes in batches because something can't handle WHERE IN queries with sets larger than 999 int start = 0; const int size = 999; while (start < collectionIds.size()) { const QVariantList ids = collectionIds.mid(start, size); QSqlQuery attributeQuery = getAttributeQuery(ids, mAncestorAttributes); while (attributeQuery.next()) { CollectionAttribute attr; attr.setType(attributeQuery.value(1).toByteArray()); attr.setValue(attributeQuery.value(2).toByteArray()); // qCDebug(AKONADISERVER_LOG) << "found attribute " << attr.type() << attr.value(); mCollectionAttributes.insert(attributeQuery.value(0).toLongLong(), attr); } attributeQuery.finish(); start += size; } } static QSqlQuery getMimeTypeQuery(const QVariantList &ids) { QueryBuilder qb(CollectionMimeTypeRelation::tableName()); qb.addJoin(QueryBuilder::LeftJoin, MimeType::tableName(), MimeType::idFullColumnName(), CollectionMimeTypeRelation::rightFullColumnName()); qb.addValueCondition(CollectionMimeTypeRelation::leftFullColumnName(), Query::In, ids); qb.addColumn(CollectionMimeTypeRelation::leftFullColumnName()); qb.addColumn(CollectionMimeTypeRelation::rightFullColumnName()); qb.addColumn(MimeType::nameFullColumnName()); qb.addSortColumn(CollectionMimeTypeRelation::leftFullColumnName(), Query::Ascending); if (!qb.exec()) { throw HandlerException("Unable to retrieve mimetypes for listing"); } return qb.query(); } -void List::retrieveCollections(const Collection &topParent, int depth) +void CollectionFetchHandler::retrieveCollections(const Collection &topParent, int depth) { /* * Retrieval of collections: * The aim is to reduce the amount of queries as much as possible, as this has the largest performance impact for large queries. * * First all collections that match the given criteria are queried * * We then filter the false positives: * ** all collections out that are not part of the tree we asked for are filtered * ** all collections that are referenced but not by this session or by the owning resource are filtered * * Finally we complete the tree by adding missing collections * * Mimetypes and attributes are also retrieved in single queries to avoid spawning two queries per collection (the N+1 problem). * Note that we're not querying attributes and mimetypes for the collections that are only included to complete the tree, * this results in no items being queried for those collections. */ const qint64 parentId = topParent.isValid() ? topParent.id() : 0; { SelectQueryBuilder qb; if (depth == 0) { qb.addValueCondition(Collection::idFullColumnName(), Query::Equals, parentId); } else if (depth == 1) { if (topParent.isValid()) { qb.addValueCondition(Collection::parentIdFullColumnName(), Query::Equals, parentId); } else { qb.addValueCondition(Collection::parentIdFullColumnName(), Query::Is, QVariant()); } } else { if (topParent.isValid()) { qb.addValueCondition(Collection::resourceIdFullColumnName(), Query::Equals, topParent.resourceId()); } else { // Gimme gimme gimme...everything! } } //Base listings should succeed always if (depth != 0) { if (mCollectionsToSynchronize) { qb.addCondition(filterCondition(Collection::syncPrefFullColumnName())); } else if (mCollectionsToDisplay) { qb.addCondition(filterCondition(Collection::displayPrefFullColumnName())); } else if (mCollectionsToIndex) { qb.addCondition(filterCondition(Collection::indexPrefFullColumnName())); } else if (mEnabledCollections) { Query::Condition orCondition(Query::Or); orCondition.addValueCondition(Collection::enabledFullColumnName(), Query::Equals, true); orCondition.addValueCondition(Collection::referencedFullColumnName(), Query::Equals, true); qb.addCondition(orCondition); } if (mResource.isValid()) { qb.addValueCondition(Collection::resourceIdFullColumnName(), Query::Equals, mResource.id()); } if (!mMimeTypes.isEmpty()) { qb.addJoin(QueryBuilder::LeftJoin, CollectionMimeTypeRelation::tableName(), CollectionMimeTypeRelation::leftColumn(), Collection::idFullColumnName()); QVariantList mimeTypeFilter; mimeTypeFilter.reserve(mMimeTypes.size()); for (MimeType::Id mtId : qAsConst(mMimeTypes)) { mimeTypeFilter << mtId; } qb.addValueCondition(CollectionMimeTypeRelation::rightColumn(), Query::In, mimeTypeFilter); qb.addGroupColumn(Collection::idFullColumnName()); } } if (!qb.exec()) { throw HandlerException("Unable to retrieve collection for listing"); } Q_FOREACH (const Collection &col, qb.result()) { mCollections.insert(col.id(), col); } } //Post filtering that we couldn't do as part of the sql query if (depth > 0) { auto it = mCollections.begin(); while (it != mCollections.end()) { if (topParent.isValid()) { //Check that each collection is linked to the root collection bool foundParent = false; //We iterate over parents to link it to topParent if possible Collection::Id id = it->parentId(); while (id > 0) { if (id == parentId) { foundParent = true; break; } Collection col = mCollections.value(id); if (!col.isValid()) { col = Collection::retrieveById(id); } id = col.parentId(); } if (!foundParent) { it = mCollections.erase(it); continue; } } //If we matched referenced collections we need to ensure the collection was referenced from this session const bool isReferencedFromSession = connection()->collectionReferenceManager()->isReferenced(it->id(), connection()->sessionId()); //The collection is referenced, but not from this session. We need to reevaluate the filter condition if (it->referenced() && !isReferencedFromSession) { //Don't include the collection when only looking for enabled collections. //However, a referenced collection should be still synchronized by the resource, so we exclude this case. if (!checkFilterCondition(*it) && !(mCollectionsToSynchronize && connection()->context()->resource().isValid())) { it = mCollections.erase(it); continue; } } ++it; } } QVariantList mimeTypeIds; QVariantList attributeIds; QVariantList ancestorIds; const int collectionSize{mCollections.size()}; mimeTypeIds.reserve(collectionSize); attributeIds.reserve(collectionSize); //We'd only require the non-leaf collections, but we don't know which those are, so we take all. ancestorIds.reserve(collectionSize); for (auto it = mCollections.cbegin(), end = mCollections.cend(); it != end; ++it) { mimeTypeIds << it.key(); attributeIds << it.key(); ancestorIds << it.key(); } if (mAncestorDepth > 0 && topParent.isValid()) { //unless depth is 0 the base collection is not part of the listing mAncestors.insert(topParent.id(), topParent); ancestorIds << topParent.id(); //We need to retrieve additional ancestors to what we already have in the tree Collection parent = topParent; for (int i = 0; i < mAncestorDepth; ++i) { if (parent.parentId() == 0) { break; } parent = parent.parent(); mAncestors.insert(parent.id(), parent); //We also require the attributes ancestorIds << parent.id(); } } QSet missingCollections; if (depth > 0) { for (const Collection &col : qAsConst(mCollections)) { if (col.parentId() != parentId && !mCollections.contains(col.parentId())) { missingCollections.insert(col.parentId()); } } } /* QSet knownIds; for (const Collection &col : mCollections) { knownIds.insert(col.id()); } qCDebug(AKONADISERVER_LOG) << "HAS:" << knownIds; qCDebug(AKONADISERVER_LOG) << "MISSING:" << missingCollections; */ //Fetch missing collections that are part of the tree while (!missingCollections.isEmpty()) { SelectQueryBuilder qb; QVariantList ids; ids.reserve(missingCollections.size()); for (qint64 id : qAsConst(missingCollections)) { ids << id; } qb.addValueCondition(Collection::idFullColumnName(), Query::In, ids); if (!qb.exec()) { throw HandlerException("Unable to retrieve collections for listing"); } missingCollections.clear(); Q_FOREACH (const Collection &missingCol, qb.result()) { mCollections.insert(missingCol.id(), missingCol); ancestorIds << missingCol.id(); attributeIds << missingCol.id(); mimeTypeIds << missingCol.id(); //We have to do another round if the parents parent is missing if (missingCol.parentId() != parentId && !mCollections.contains(missingCol.parentId())) { missingCollections.insert(missingCol.parentId()); } } } //Since we don't know when we'll need the ancestor attributes, we have to fetch them all together. //The alternative would be to query for each collection which would reintroduce the N+1 query performance problem. if (!mAncestorAttributes.isEmpty()) { retrieveAttributes(ancestorIds); } //We are querying in batches because something can't handle WHERE IN queries with sets larger than 999 const int querySizeLimit = 999; int mimetypeQueryStart = 0; int attributeQueryStart = 0; QSqlQuery mimeTypeQuery(storageBackend()->database()); QSqlQuery attributeQuery(storageBackend()->database()); auto it = mCollections.begin(); while (it != mCollections.end()) { const Collection col = it.value(); QStringList mimeTypes; { //Get new query if necessary if (!mimeTypeQuery.isValid() && mimetypeQueryStart < mimeTypeIds.size()) { const QVariantList ids = mimeTypeIds.mid(mimetypeQueryStart, querySizeLimit); mimetypeQueryStart += querySizeLimit; mimeTypeQuery = getMimeTypeQuery(ids); mimeTypeQuery.next(); //place at first record } while (mimeTypeQuery.isValid() && mimeTypeQuery.value(0).toLongLong() < col.id()) { if (!mimeTypeQuery.next()) { break; } } //Advance query while a mimetype for this collection is returned while (mimeTypeQuery.isValid() && mimeTypeQuery.value(0).toLongLong() == col.id()) { mimeTypes << mimeTypeQuery.value(2).toString(); if (!mimeTypeQuery.next()) { break; } } } CollectionAttribute::List attributes; { //Get new query if necessary if (!attributeQuery.isValid() && attributeQueryStart < attributeIds.size()) { const QVariantList ids = attributeIds.mid(attributeQueryStart, querySizeLimit); attributeQueryStart += querySizeLimit; attributeQuery = getAttributeQuery(ids, QSet()); attributeQuery.next(); //place at first record } while (attributeQuery.isValid() && attributeQuery.value(0).toLongLong() < col.id()) { if (!attributeQuery.next()) { break; } } //Advance query while a mimetype for this collection is returned while (attributeQuery.isValid() && attributeQuery.value(0).toLongLong() == col.id()) { CollectionAttribute attr; attr.setType(attributeQuery.value(1).toByteArray()); attr.setValue(attributeQuery.value(2).toByteArray()); attributes << attr; if (!attributeQuery.next()) { break; } } } listCollection(col, ancestorsForCollection(col), mimeTypes, attributes); it++; } attributeQuery.finish(); mimeTypeQuery.finish(); } -bool List::parseStream() +bool CollectionFetchHandler::parseStream() { const auto &cmd = Protocol::cmdCast(m_command); if (!cmd.resource().isEmpty()) { mResource = Resource::retrieveByName(cmd.resource()); if (!mResource.isValid()) { return failureResponse("Unknown resource"); } } const QStringList lstMimeTypes = cmd.mimeTypes(); for (const QString &mtName : lstMimeTypes) { const MimeType mt = MimeType::retrieveByNameOrCreate(mtName); if (!mt.isValid()) { return failureResponse("Failed to create mimetype record"); } mMimeTypes.append(mt.id()); } mEnabledCollections = cmd.enabled(); mCollectionsToSynchronize = cmd.syncPref(); mCollectionsToDisplay = cmd.displayPref(); mCollectionsToIndex = cmd.indexPref(); mIncludeStatistics = cmd.fetchStats(); int depth = 0; switch (cmd.depth()) { case Protocol::FetchCollectionsCommand::BaseCollection: depth = 0; break; case Protocol::FetchCollectionsCommand::ParentCollection: depth = 1; break; case Protocol::FetchCollectionsCommand::AllCollections: depth = INT_MAX; break; } switch (cmd.ancestorsDepth()) { case Protocol::Ancestor::NoAncestor: mAncestorDepth = 0; break; case Protocol::Ancestor::ParentAncestor: mAncestorDepth = 1; break; case Protocol::Ancestor::AllAncestors: mAncestorDepth = INT_MAX; break; } mAncestorAttributes = cmd.ancestorsAttributes(); Scope scope = cmd.collections(); if (!scope.isEmpty()) { // not root Collection col; if (scope.scope() == Scope::Uid) { col = Collection::retrieveById(scope.uid()); } else if (scope.scope() == Scope::Rid) { SelectQueryBuilder qb; qb.addValueCondition(Collection::remoteIdFullColumnName(), Query::Equals, scope.rid()); qb.addJoin(QueryBuilder::InnerJoin, Resource::tableName(), Collection::resourceIdFullColumnName(), Resource::idFullColumnName()); if (mCollectionsToSynchronize) { qb.addCondition(filterCondition(Collection::syncPrefFullColumnName())); } else if (mCollectionsToDisplay) { qb.addCondition(filterCondition(Collection::displayPrefFullColumnName())); } else if (mCollectionsToIndex) { qb.addCondition(filterCondition(Collection::indexPrefFullColumnName())); } if (mResource.isValid()) { qb.addValueCondition(Resource::idFullColumnName(), Query::Equals, mResource.id()); } else if (connection()->context()->resource().isValid()) { qb.addValueCondition(Resource::idFullColumnName(), Query::Equals, connection()->context()->resource().id()); } else { return failureResponse("Cannot retrieve collection based on remote identifier without a resource context"); } if (!qb.exec()) { return failureResponse("Unable to retrieve collection for listing"); } Collection::List results = qb.result(); if (results.count() != 1) { return failureResponse(QString::number(results.count()) + QStringLiteral(" collections found")); } col = results.first(); } else if (scope.scope() == Scope::HierarchicalRid) { if (!connection()->context()->resource().isValid()) { return failureResponse("Cannot retrieve collection based on hierarchical remote identifier without a resource context"); } col = CollectionQueryHelper::resolveHierarchicalRID(scope.hridChain(), connection()->context()->resource().id()); } else { return failureResponse("Unexpected error"); } if (!col.isValid()) { return failureResponse("Collection does not exist"); } retrieveCollections(col, depth); } else { //Root folder listing if (depth != 0) { retrieveCollections(Collection(), depth); } } return successResponse(); } diff --git a/src/server/handler/list.h b/src/server/handler/collectionfetchhandler.h similarity index 94% rename from src/server/handler/list.h rename to src/server/handler/collectionfetchhandler.h index 1e9b1177c..d4a5bf7bd 100644 --- a/src/server/handler/list.h +++ b/src/server/handler/collectionfetchhandler.h @@ -1,103 +1,103 @@ /* Copyright (c) 2007 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef AKONADI_LIST_H -#define AKONADI_LIST_H +#ifndef AKONADI_COLLECTIONFETCHHANDLER_H_ +#define AKONADI_COLLECTIONFETCHHANDLER_H_ #include "entities.h" #include "handler.h" template class QStack; namespace Akonadi { namespace Server { /** @ingroup akonadi_server_handler Handler for the LIST command. This command is used to get a (limited) listing of the available collections. It is different from the LIST command and is more similar to FETCH. The @c RID command prefix indicates that @c collection-id is a remote identifier instead of a unique identifier. In this case a resource context has to be specified previously using the @c RESSELECT command. @c depths chooses between recursive (@c INF), flat (1) and local (0, ie. just the base collection) listing, 0 indicates the root collection. The @c filter-list is used to restrict the listing to collection of a specific resource or content type. The @c option-list allows to specify the response content to some extend: - @c STATISTICS (boolean) allows to include the collection statistics (see Status) - @c ANCESTORDEPTH (numeric) allows you to specify the number of ancestor nodes that should be included additionally to the @c parent-id included anyway. Possible values are @c 0 (the default), @c 1 for the direct parent node and @c INF for all, terminating with the root collection. The name is encoded as an quoted UTF-8 string. There is no order defined for the single responses. The ancestors property is encoded as a list of UID/RID pairs. */ -class List : public Handler +class CollectionFetchHandler: public Handler { public: - List() = default; - ~List() override = default; + CollectionFetchHandler() = default; + ~CollectionFetchHandler() override = default; bool parseStream() override; private: void listCollection(const Collection &root, const QStack &ancestors, const QStringList &mimeTypes, const CollectionAttribute::List &attributes); QStack ancestorsForCollection(const Collection &col); void retrieveCollections(const Collection &topParent, int depth); bool checkFilterCondition(const Collection &col) const; bool checkChildrenForMimeTypes(const QHash &collectionsMap, const QHash &parentMap, const Collection &col); CollectionAttribute::List getAttributes(const Collection &colId, const QSet &filter = QSet()); void retrieveAttributes(const QVariantList &collectionIds); Resource mResource; QVector mMimeTypes; int mAncestorDepth = 0; bool mIncludeStatistics = false; bool mEnabledCollections = false; bool mCollectionsToDisplay = false; bool mCollectionsToSynchronize = false; bool mCollectionsToIndex = false; QSet mAncestorAttributes; QMap mCollections; QHash mAncestors; QMultiHash mCollectionAttributes; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler/modify.cpp b/src/server/handler/collectionmodifyhandler.cpp similarity index 99% rename from src/server/handler/modify.cpp rename to src/server/handler/collectionmodifyhandler.cpp index f826d428c..64c1486f5 100644 --- a/src/server/handler/modify.cpp +++ b/src/server/handler/collectionmodifyhandler.cpp @@ -1,313 +1,313 @@ /* Copyright (c) 2006 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "modify.h" +#include "collectionmodifyhandler.h" #include "akonadi.h" #include "connection.h" #include "handlerhelper.h" #include "cachecleaner.h" #include "collectionreferencemanager.h" #include "intervalcheck.h" #include "storage/datastore.h" #include "storage/transaction.h" #include "storage/itemretriever.h" #include "storage/selectquerybuilder.h" #include "storage/collectionqueryhelper.h" #include "search/searchmanager.h" #include "akonadiserver_debug.h" using namespace Akonadi; using namespace Akonadi::Server; -bool Modify::parseStream() +bool CollectionModifyHandler::parseStream() { const auto &cmd = Protocol::cmdCast(m_command); Collection collection = HandlerHelper::collectionFromScope(cmd.collection(), connection()); if (!collection.isValid()) { return failureResponse("No such collection"); } CacheCleanerInhibitor inhibitor(false); if (cmd.modifiedParts() & Protocol::ModifyCollectionCommand::ParentID) { const Collection newParent = Collection::retrieveById(cmd.parentId()); if (newParent.isValid() && collection.parentId() != newParent.id() && collection.resourceId() != newParent.resourceId()) { inhibitor.inhibit(); ItemRetriever retriever(connection()); retriever.setCollection(collection, true); retriever.setRetrieveFullPayload(true); if (!retriever.exec()) { throw HandlerException(retriever.lastError()); } } } DataStore *db = connection()->storageBackend(); Transaction transaction(db, QStringLiteral("MODIFY")); QList changes; bool referencedChanged = false; if (cmd.modifiedParts() & Protocol::ModifyCollectionCommand::MimeTypes) { QStringList mts = cmd.mimeTypes(); const MimeType::List currentMts = collection.mimeTypes(); bool equal = true; for (const MimeType ¤tMt : currentMts) { const int removeMts = mts.removeAll(currentMt.name()); if (removeMts > 0) { continue; } equal = false; if (!collection.removeMimeType(currentMt)) { return failureResponse("Unable to remove collection mimetype"); } } if (!db->appendMimeTypeForCollection(collection.id(), mts)) { return failureResponse("Unable to add collection mimetypes"); } if (!equal || !mts.isEmpty()) { changes.append(AKONADI_PARAM_MIMETYPE); } } if (cmd.modifiedParts() & Protocol::ModifyCollectionCommand::CachePolicy) { bool changed = false; const Protocol::CachePolicy newCp = cmd.cachePolicy(); if (collection.cachePolicyCacheTimeout() != newCp.cacheTimeout()) { collection.setCachePolicyCacheTimeout(newCp.cacheTimeout()); changed = true; } if (collection.cachePolicyCheckInterval() != newCp.checkInterval()) { collection.setCachePolicyCheckInterval(newCp.checkInterval()); changed = true; } if (collection.cachePolicyInherit() != newCp.inherit()) { collection.setCachePolicyInherit(newCp.inherit()); changed = true; } QStringList parts = newCp.localParts(); std::sort(parts.begin(), parts.end()); const QString localParts = parts.join(QLatin1Char(' ')); if (collection.cachePolicyLocalParts() != localParts) { collection.setCachePolicyLocalParts(localParts); changed = true; } if (collection.cachePolicySyncOnDemand() != newCp.syncOnDemand()) { collection.setCachePolicySyncOnDemand(newCp.syncOnDemand()); changed = true; } if (changed) { changes.append(AKONADI_PARAM_CACHEPOLICY); } } if (cmd.modifiedParts() & Protocol::ModifyCollectionCommand::Name) { if (cmd.name() != collection.name()) { if (!CollectionQueryHelper::hasAllowedName(collection, cmd.name(), collection.parentId())) { return failureResponse("Collection with the same name exists already"); } collection.setName(cmd.name()); changes.append(AKONADI_PARAM_NAME); } } if (cmd.modifiedParts() & Protocol::ModifyCollectionCommand::ParentID) { if (collection.parentId() != cmd.parentId()) { if (!db->moveCollection(collection, Collection::retrieveById(cmd.parentId()))) { return failureResponse("Unable to reparent collection"); } changes.append(AKONADI_PARAM_PARENT); } } if (cmd.modifiedParts() & Protocol::ModifyCollectionCommand::RemoteID) { if (cmd.remoteId() != collection.remoteId() && !cmd.remoteId().isEmpty()) { if (!connection()->isOwnerResource(collection)) { qCWarning(AKONADISERVER_LOG) << "Invalid attempt to modify the collection remoteID from" << collection.remoteId() << "to" << cmd.remoteId(); return failureResponse("Only resources can modify remote identifiers"); } collection.setRemoteId(cmd.remoteId()); changes.append(AKONADI_PARAM_REMOTEID); } } if (cmd.modifiedParts() & Protocol::ModifyCollectionCommand::RemoteRevision) { if (cmd.remoteRevision() != collection.remoteRevision()) { collection.setRemoteRevision(cmd.remoteRevision()); changes.append(AKONADI_PARAM_REMOTEREVISION); } } if (cmd.modifiedParts() & Protocol::ModifyCollectionCommand::PersistentSearch) { bool changed = false; if (cmd.persistentSearchQuery() != collection.queryString()) { collection.setQueryString(cmd.persistentSearchQuery()); changed = true; } QList queryAttributes = collection.queryAttributes().toUtf8().split(' '); if (cmd.persistentSearchRemote() != queryAttributes.contains(AKONADI_PARAM_REMOTE)) { if (cmd.persistentSearchRemote()) { queryAttributes.append(AKONADI_PARAM_REMOTE); } else { queryAttributes.removeOne(AKONADI_PARAM_REMOTE); } changed = true; } if (cmd.persistentSearchRecursive() != queryAttributes.contains(AKONADI_PARAM_RECURSIVE)) { if (cmd.persistentSearchRecursive()) { queryAttributes.append(AKONADI_PARAM_RECURSIVE); } else { queryAttributes.removeOne(AKONADI_PARAM_RECURSIVE); } changed = true; } if (changed) { collection.setQueryAttributes(QString::fromLatin1(queryAttributes.join(' '))); } QStringList cols; cols.reserve(cmd.persistentSearchCollections().size()); QVector inCols = cmd.persistentSearchCollections(); std::sort(inCols.begin(), inCols.end()); for (qint64 col : qAsConst(inCols)) { cols.append(QString::number(col)); } const QString colStr = cols.join(QLatin1Char(' ')); if (colStr != collection.queryCollections()) { collection.setQueryCollections(colStr); changed = true; } if (changed || cmd.modifiedParts() & Protocol::ModifyCollectionCommand::MimeTypes) { changes.append(AKONADI_PARAM_PERSISTENTSEARCH); } } if (cmd.modifiedParts() & Protocol::ModifyCollectionCommand::ListPreferences) { if (cmd.enabled() != collection.enabled()) { collection.setEnabled(cmd.enabled()); changes.append(AKONADI_PARAM_ENABLED); } if (cmd.syncPref() != static_cast(collection.syncPref())) { collection.setSyncPref(static_cast(cmd.syncPref())); changes.append(AKONADI_PARAM_SYNC); } if (cmd.displayPref() != static_cast(collection.displayPref())) { collection.setDisplayPref(static_cast(cmd.displayPref())); changes.append(AKONADI_PARAM_DISPLAY); } if (cmd.indexPref() != static_cast(collection.indexPref())) { collection.setIndexPref(static_cast(cmd.indexPref())); changes.append(AKONADI_PARAM_INDEX); } } if (cmd.modifiedParts() & Protocol::ModifyCollectionCommand::Referenced) { const bool wasReferencedFromSession = connection()->collectionReferenceManager()->isReferenced(collection.id(), connection()->sessionId()); connection()->collectionReferenceManager()->referenceCollection(connection()->sessionId(), collection, cmd.referenced()); const bool referenced = connection()->collectionReferenceManager()->isReferenced(collection.id()); if (cmd.referenced() != wasReferencedFromSession) { changes.append(AKONADI_PARAM_REFERENCED); } if (referenced != collection.referenced()) { referencedChanged = true; collection.setReferenced(referenced); } } if (cmd.modifiedParts() & Protocol::ModifyCollectionCommand::RemovedAttributes) { Q_FOREACH (const QByteArray &attr, cmd.removedAttributes()) { if (db->removeCollectionAttribute(collection, attr)) { changes.append(attr); } } } if (cmd.modifiedParts() & Protocol::ModifyCollectionCommand::Attributes) { const QMap attrs = cmd.attributes(); for (auto iter = attrs.cbegin(), end = attrs.cend(); iter != end; ++iter) { SelectQueryBuilder qb; qb.addValueCondition(CollectionAttribute::collectionIdColumn(), Query::Equals, collection.id()); qb.addValueCondition(CollectionAttribute::typeColumn(), Query::Equals, iter.key()); if (!qb.exec()) { return failureResponse("Unable to retrieve collection attribute"); } const CollectionAttribute::List attrs = qb.result(); if (attrs.isEmpty()) { CollectionAttribute newAttr; newAttr.setCollectionId(collection.id()); newAttr.setType(iter.key()); newAttr.setValue(iter.value()); if (!newAttr.insert()) { return failureResponse("Unable to add collection attribute"); } changes.append(iter.key()); } else if (attrs.size() == 1) { CollectionAttribute currAttr = attrs.first(); if (currAttr.value() == iter.value()) { continue; } currAttr.setValue(iter.value()); if (!currAttr.update()) { return failureResponse("Unable to update collection attribute"); } changes.append(iter.key()); } else { return failureResponse("WTF: more than one attribute with the same name"); } } } if (!changes.isEmpty()) { if (collection.hasPendingChanges() && !collection.update()) { return failureResponse("Unable to update collection"); } //This must be after the collection was updated in the db. The resource will immediately request a copy of the collection. if (AkonadiServer::instance()->intervalChecker() && collection.referenced() && referencedChanged) { AkonadiServer::instance()->intervalChecker()->requestCollectionSync(collection); } db->notificationCollector()->collectionChanged(collection, changes); //For backwards compatibility. Must be after the changed notification (otherwise the compression removes it). if (changes.contains(AKONADI_PARAM_ENABLED)) { if (collection.enabled()) { db->notificationCollector()->collectionSubscribed(collection); } else { db->notificationCollector()->collectionUnsubscribed(collection); } } if (!transaction.commit()) { return failureResponse("Unable to commit transaction"); } // Only request Search update AFTER committing the transaction to avoid // transaction deadlock with SQLite if (changes.contains(AKONADI_PARAM_PERSISTENTSEARCH)) { SearchManager::instance()->updateSearch(collection); } } return successResponse(); } diff --git a/src/server/handler/modify.h b/src/server/handler/collectionmodifyhandler.h similarity index 86% rename from src/server/handler/modify.h rename to src/server/handler/collectionmodifyhandler.h index 3f00c4e13..d1d19b257 100644 --- a/src/server/handler/modify.h +++ b/src/server/handler/collectionmodifyhandler.h @@ -1,49 +1,47 @@ /* Copyright (c) 2006 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef AKONADI_MODIFY_H -#define AKONADI_MODIFY_H +#ifndef AKONADI_COLLECTIONMODIFYHANDLER_H_ +#define AKONADI_COLLECTIONMODIFYHANDLER_H_ #include "handler.h" namespace Akonadi { namespace Server { /** @ingroup akonadi_server_handler - Handler for the MODIFY command (not in RFC 3501). - This command is used to modify collections. Its syntax is similar to the STORE command. */ -class Modify : public Handler +class CollectionModifyHandler: public Handler { public: - ~Modify() override = default; + ~CollectionModifyHandler() override = default; bool parseStream() override; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler/colmove.cpp b/src/server/handler/collectionmovehandler.cpp similarity index 94% rename from src/server/handler/colmove.cpp rename to src/server/handler/collectionmovehandler.cpp index 7eebceeaa..3b6f5d950 100644 --- a/src/server/handler/colmove.cpp +++ b/src/server/handler/collectionmovehandler.cpp @@ -1,78 +1,78 @@ /* Copyright (c) 2009 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "colmove.h" +#include "collectionmovehandler.h" #include "handlerhelper.h" #include "connection.h" #include "cachecleaner.h" #include "storage/datastore.h" #include "storage/itemretriever.h" #include "storage/transaction.h" #include "storage/collectionqueryhelper.h" using namespace Akonadi; using namespace Akonadi::Server; -bool ColMove::parseStream() +bool CollectionMoveHandler::parseStream() { const auto &cmd = Protocol::cmdCast(m_command); Collection source = HandlerHelper::collectionFromScope(cmd.collection(), connection()); if (!source.isValid()) { return failureResponse(QStringLiteral("Invalid collection to move")); } Collection target; if (cmd.destination().isEmpty()) { target.setId(0); } else { target = HandlerHelper::collectionFromScope(cmd.destination(), connection()); if (!target.isValid()) { return failureResponse(QStringLiteral("Invalid destination collection")); } } if (source.parentId() == target.id()) { return successResponse(); } CacheCleanerInhibitor inhibitor; // retrieve all not yet cached items of the source ItemRetriever retriever(connection()); retriever.setCollection(source, true); retriever.setRetrieveFullPayload(true); if (!retriever.exec()) { return failureResponse(retriever.lastError()); } DataStore *store = connection()->storageBackend(); - Transaction transaction(store, QStringLiteral("COLMOVE")); + Transaction transaction(store, QStringLiteral("CollectionMoveHandler")); if (!store->moveCollection(source, target)) { return failureResponse(QStringLiteral("Unable to reparent collection")); } if (!transaction.commit()) { return failureResponse(QStringLiteral("Cannot commit transaction.")); } return successResponse(); } diff --git a/src/server/handler/colmove.h b/src/server/handler/collectionmovehandler.h similarity index 84% rename from src/server/handler/colmove.h rename to src/server/handler/collectionmovehandler.h index 589129231..c35e6026b 100644 --- a/src/server/handler/colmove.h +++ b/src/server/handler/collectionmovehandler.h @@ -1,49 +1,49 @@ /* Copyright (c) 2009 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef AKONADI_COLMOVE_H -#define AKONADI_COLMOVE_H +#ifndef AKONADI_COLLECTIONMOVEHANDLER_H_ +#define AKONADI_COLLECTIONMOVEHANDLER_H_ #include "handler.h" namespace Akonadi { namespace Server { /** @ingroup akonadi_server_handler - Handler for the COLMOVE command. + Handler for the MoveCollection command This command is used to move a set of collections into another collection, including all sub-collections and their content. */ -class ColMove : public Handler +class CollectionMoveHandler : public Handler { public: - ~ColMove() override = default; + ~CollectionMoveHandler() override = default; bool parseStream() override; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler/status.cpp b/src/server/handler/collectionstatsfetchhandler.cpp similarity index 96% rename from src/server/handler/status.cpp rename to src/server/handler/collectionstatsfetchhandler.cpp index 66e38cee6..1b04c595d 100644 --- a/src/server/handler/status.cpp +++ b/src/server/handler/collectionstatsfetchhandler.cpp @@ -1,51 +1,51 @@ /*************************************************************************** * Copyright (C) 2006 by Ingo Kloecker * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * 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 Library 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 "status.h" +#include "collectionstatsfetchhandler.h" #include "connection.h" #include "handlerhelper.h" #include "storage/datastore.h" #include "storage/collectionstatistics.h" #include using namespace Akonadi; using namespace Akonadi::Server; -bool Status::parseStream() +bool CollectionStatsFetchHandler::parseStream() { const auto &cmd = Protocol::cmdCast(m_command); const Collection col = HandlerHelper::collectionFromScope(cmd.collection(), connection()); if (!col.isValid()) { return failureResponse(QStringLiteral("No status for this folder")); } const CollectionStatistics::Statistics stats = CollectionStatistics::self()->statistics(col); if (stats.count == -1) { return failureResponse(QStringLiteral("Failed to query statistics.")); } Protocol::FetchCollectionStatsResponse resp; resp.setCount(stats.count); resp.setUnseen(stats.count - stats.read); resp.setSize(stats.size); return successResponse(std::move(resp)); } diff --git a/src/server/handler/create.h b/src/server/handler/collectionstatsfetchhandler.h similarity index 87% rename from src/server/handler/create.h rename to src/server/handler/collectionstatsfetchhandler.h index fd70a122c..6079a6c36 100644 --- a/src/server/handler/create.h +++ b/src/server/handler/collectionstatsfetchhandler.h @@ -1,43 +1,45 @@ /*************************************************************************** * Copyright (C) 2006 by Ingo Kloecker * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * 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 Library 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 AKONADICREATE_H -#define AKONADICREATE_H +#ifndef AKONADI_COLLECTIONSTATSFETCHHANDLER_H_ +#define AKONADI_COLLECTIONSTATSFETCHHANDLER_H_ #include "handler.h" namespace Akonadi { namespace Server { /** @ingroup akonadi_server_handler + + Handler for the STATUS command. */ -class Create : public Handler +class CollectionStatsFetchHandler: public Handler { public: - ~Create() override = default; + ~CollectionStatsFetchHandler() override = default; bool parseStream() override; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler/copy.cpp b/src/server/handler/itemcopyhandler.cpp similarity index 95% rename from src/server/handler/copy.cpp rename to src/server/handler/itemcopyhandler.cpp index 28984b538..bde974598 100644 --- a/src/server/handler/copy.cpp +++ b/src/server/handler/itemcopyhandler.cpp @@ -1,126 +1,126 @@ /* Copyright (c) 2008 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "copy.h" +#include "itemcopyhandler.h" #include "connection.h" #include "handlerhelper.h" #include "cachecleaner.h" #include "storage/datastore.h" #include "storage/itemqueryhelper.h" #include "storage/itemretriever.h" #include "storage/selectquerybuilder.h" #include "storage/transaction.h" #include "storage/parthelper.h" #include using namespace Akonadi; using namespace Akonadi::Server; -bool Copy::copyItem(const PimItem &item, const Collection &target) +bool ItemCopyHandler::copyItem(const PimItem &item, const Collection &target) { PimItem newItem = item; newItem.setId(-1); newItem.setRev(0); newItem.setDatetime(QDateTime::currentDateTimeUtc()); newItem.setAtime(QDateTime::currentDateTimeUtc()); newItem.setRemoteId(QString()); newItem.setRemoteRevision(QString()); newItem.setCollectionId(target.id()); Part::List parts; parts.reserve(item.parts().count()); Q_FOREACH (const Part &part, item.parts()) { Part newPart(part); newPart.setData(PartHelper::translateData(newPart.data(), part.storage())); newPart.setPimItemId(-1); newPart.setStorage(Part::Internal); parts << newPart; } DataStore *store = connection()->storageBackend(); if (!store->appendPimItem(parts, item.flags(), item.mimeType(), target, QDateTime::currentDateTimeUtc(), QString(), QString(), item.gid(), newItem)) { return false; } return true; } -void Copy::processItems(const QList &ids) +void ItemCopyHandler::processItems(const QList &ids) { SelectQueryBuilder qb; ItemQueryHelper::itemSetToQuery(ImapSet(ids), qb); if (!qb.exec()) { failureResponse(QStringLiteral("Unable to retrieve items")); return; } const PimItem::List items = qb.result(); qb.query().finish(); DataStore *store = connection()->storageBackend(); Transaction transaction(store, QStringLiteral("COPY")); for (const PimItem &item : items) { if (!copyItem(item, mTargetCollection)) { failureResponse(QStringLiteral("Unable to copy item")); return; } } if (!transaction.commit()) { failureResponse(QStringLiteral("Cannot commit transaction.")); return; } } -bool Copy::parseStream() +bool ItemCopyHandler::parseStream() { const auto &cmd = Protocol::cmdCast(m_command); if (!checkScopeConstraints(cmd.items(), Scope::Uid)) { return failureResponse(QStringLiteral("Only UID copy is allowed")); } if (cmd.items().isEmpty()) { return failureResponse(QStringLiteral("No items specified")); } mTargetCollection = HandlerHelper::collectionFromScope(cmd.destination(), connection()); if (!mTargetCollection.isValid()) { return failureResponse(QStringLiteral("No valid target specified")); } if (mTargetCollection.isVirtual()) { return failureResponse(QStringLiteral("Copying items into virtual collections is not allowed")); } CacheCleanerInhibitor inhibitor; ItemRetriever retriever(connection()); retriever.setItemSet(cmd.items().uidSet()); retriever.setRetrieveFullPayload(true); QObject::connect(&retriever, &ItemRetriever::itemsRetrieved, [this](const QList &ids) { processItems(ids); }); if (!retriever.exec()) { return failureResponse(retriever.lastError()); } return successResponse(); } diff --git a/src/server/handler/copy.h b/src/server/handler/itemcopyhandler.h similarity index 92% rename from src/server/handler/copy.h rename to src/server/handler/itemcopyhandler.h index 08e2e230f..abc41872e 100644 --- a/src/server/handler/copy.h +++ b/src/server/handler/itemcopyhandler.h @@ -1,69 +1,69 @@ /* Copyright (c) 2008 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef AKONADI_COPY_H -#define AKONADI_COPY_H +#ifndef AKONADI_ITEMCOPYHANDLER_H_ +#define AKONADI_ITEMCOPYHANDLER_H_ #include "handler.h" #include "entities.h" namespace Akonadi { namespace Server { /** @ingroup akonadi_server_handler Handler for the COPY command. This command is used to copy a set of items into the specific collection. It is syntactically identical to the IMAP COPY command. The copied items differ in the following points from the originals: - new unique id - empty remote id - possible located in a different collection (and thus resource) There is only the usual status response indicating success or failure of the COPY command */ -class Copy : public Handler +class ItemCopyHandler: public Handler { public: - ~Copy() override = default; + ~ItemCopyHandler() override = default; bool parseStream() override; protected: /** Copy the given item and all its parts into the @p target. The changes mentioned above are applied. */ bool copyItem(const PimItem &item, const Collection &target); void processItems(const QList &ids); private: Collection mTargetCollection; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler/akappend.cpp b/src/server/handler/itemcreatehandler.cpp similarity index 93% rename from src/server/handler/akappend.cpp rename to src/server/handler/itemcreatehandler.cpp index 1e09781df..da13b3b2f 100644 --- a/src/server/handler/akappend.cpp +++ b/src/server/handler/itemcreatehandler.cpp @@ -1,450 +1,450 @@ /*************************************************************************** * Copyright (C) 2007 by Robert Zwerus * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * 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 Library 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 "akappend.h" +#include "itemcreatehandler.h" -#include "fetchhelper.h" +#include "itemfetchhelper.h" #include "connection.h" #include "preprocessormanager.h" #include "handlerhelper.h" #include "storage/datastore.h" #include "storage/transaction.h" #include "storage/parttypehelper.h" #include "storage/dbconfig.h" #include "storage/partstreamer.h" #include "storage/parthelper.h" #include "storage/selectquerybuilder.h" #include #include //std::accumulate using namespace Akonadi; using namespace Akonadi::Server; static QVector localFlagsToPreserve = QVector() << "$ATTACHMENT" << "$INVITATION" << "$ENCRYPTED" << "$SIGNED" << "$WATCHED"; -bool AkAppend::buildPimItem(const Protocol::CreateItemCommand &cmd, PimItem &item, - Collection &parentCol) +bool ItemCreateHandler::buildPimItem(const Protocol::CreateItemCommand &cmd, PimItem &item, + Collection &parentCol) { parentCol = HandlerHelper::collectionFromScope(cmd.collection(), connection()); if (!parentCol.isValid()) { return failureResponse(QStringLiteral("Invalid parent collection")); } if (parentCol.isVirtual()) { return failureResponse(QStringLiteral("Cannot append item into virtual collection")); } MimeType mimeType = MimeType::retrieveByNameOrCreate(cmd.mimeType()); if (!mimeType.isValid()) { return failureResponse(QStringLiteral("Unable to create mimetype '") % cmd.mimeType() % QStringLiteral("'.")); } item.setRev(0); item.setSize(cmd.itemSize()); item.setMimeTypeId(mimeType.id()); item.setCollectionId(parentCol.id()); item.setDatetime(cmd.dateTime()); if (cmd.remoteId().isEmpty()) { // from application item.setDirty(true); } else { // from resource item.setRemoteId(cmd.remoteId()); item.setDirty(false); } item.setRemoteRevision(cmd.remoteRevision()); item.setGid(cmd.gid()); item.setAtime(QDateTime::currentDateTimeUtc()); return true; } -bool AkAppend::insertItem(const Protocol::CreateItemCommand &cmd, PimItem &item, - const Collection &parentCol) +bool ItemCreateHandler::insertItem(const Protocol::CreateItemCommand &cmd, PimItem &item, + const Collection &parentCol) { if (!item.datetime().isValid()) { item.setDatetime(QDateTime::currentDateTimeUtc()); } if (!item.insert()) { return failureResponse(QStringLiteral("Failed to append item")); } // set message flags const QSet flags = cmd.mergeModes() == Protocol::CreateItemCommand::None ? cmd.flags() : cmd.addedFlags(); if (!flags.isEmpty()) { // This will hit an entry in cache inserted there in buildPimItem() const Flag::List flagList = HandlerHelper::resolveFlags(flags); bool flagsChanged = false; if (!storageBackend()->appendItemsFlags({item}, flagList, &flagsChanged, false, parentCol, true)) { return failureResponse("Unable to append item flags."); } } const Scope tags = cmd.mergeModes() == Protocol::CreateItemCommand::None ? cmd.tags() : cmd.addedTags(); if (!tags.isEmpty()) { const Tag::List tagList = HandlerHelper::tagsFromScope(tags, connection()); bool tagsChanged = false; if (!storageBackend()->appendItemsTags({item}, tagList, &tagsChanged, false, parentCol, true)) { return failureResponse(QStringLiteral("Unable to append item tags.")); } } // Handle individual parts qint64 partSizes = 0; PartStreamer streamer(connection(), item); Q_FOREACH (const QByteArray &partName, cmd.parts()) { qint64 partSize = 0; try { streamer.stream(true, partName, partSize); } catch (const PartStreamerException &e) { return failureResponse(e.what()); } partSizes += partSize; } const Protocol::Attributes attrs = cmd.attributes(); for (auto iter = attrs.cbegin(), end = attrs.cend(); iter != end; ++iter) { try { streamer.streamAttribute(true, iter.key(), iter.value()); } catch (const PartStreamerException &e) { return failureResponse(e.what()); } } // TODO: Try to avoid this addition query if (partSizes > item.size()) { item.setSize(partSizes); item.update(); } // Preprocessing if (PreprocessorManager::instance()->isActive()) { Part hiddenAttribute; hiddenAttribute.setPimItemId(item.id()); hiddenAttribute.setPartType(PartTypeHelper::fromFqName(QStringLiteral(AKONADI_ATTRIBUTE_HIDDEN))); hiddenAttribute.setData(QByteArray()); hiddenAttribute.setDatasize(0); // TODO: Handle errors? Technically, this is not a critical issue as no data are lost PartHelper::insert(&hiddenAttribute); } const bool seen = flags.contains(AKONADI_FLAG_SEEN) || flags.contains(AKONADI_FLAG_IGNORED); notify(item, seen, item.collection()); sendResponse(item, Protocol::CreateItemCommand::None); return true; } -bool AkAppend::mergeItem(const Protocol::CreateItemCommand &cmd, - PimItem &newItem, PimItem ¤tItem, - const Collection &parentCol) +bool ItemCreateHandler::mergeItem(const Protocol::CreateItemCommand &cmd, + PimItem &newItem, PimItem ¤tItem, + const Collection &parentCol) { bool needsUpdate = false; QSet changedParts; if (!newItem.remoteId().isEmpty() && currentItem.remoteId() != newItem.remoteId()) { currentItem.setRemoteId(newItem.remoteId()); changedParts.insert(AKONADI_PARAM_REMOTEID); needsUpdate = true; } if (!newItem.remoteRevision().isEmpty() && currentItem.remoteRevision() != newItem.remoteRevision()) { currentItem.setRemoteRevision(newItem.remoteRevision()); changedParts.insert(AKONADI_PARAM_REMOTEREVISION); needsUpdate = true; } if (!newItem.gid().isEmpty() && currentItem.gid() != newItem.gid()) { currentItem.setGid(newItem.gid()); changedParts.insert(AKONADI_PARAM_GID); needsUpdate = true; } if (newItem.datetime().isValid() && newItem.datetime() != currentItem.datetime()) { currentItem.setDatetime(newItem.datetime()); needsUpdate = true; } if (newItem.size() > 0 && newItem.size() != currentItem.size()) { currentItem.setSize(newItem.size()); needsUpdate = true; } const Collection col = Collection::retrieveById(parentCol.id()); if (cmd.flags().isEmpty() && !cmd.flagsOverwritten()) { bool flagsAdded = false, flagsRemoved = false; if (!cmd.addedFlags().isEmpty()) { const auto addedFlags = HandlerHelper::resolveFlags(cmd.addedFlags()); storageBackend()->appendItemsFlags({currentItem}, addedFlags, &flagsAdded, true, col, true); } if (!cmd.removedFlags().isEmpty()) { const auto removedFlags = HandlerHelper::resolveFlags(cmd.removedFlags()); storageBackend()->removeItemsFlags({currentItem}, removedFlags, &flagsRemoved, col, true); } if (flagsAdded || flagsRemoved) { changedParts.insert(AKONADI_PARAM_FLAGS); needsUpdate = true; } } else { bool flagsChanged = false; QSet flagNames = cmd.flags(); // Make sure we don't overwrite some local-only flags that can't come // through from Resource during ItemSync, like $ATTACHMENT, because the // resource is not aware of them (they are usually assigned by client // upon inspecting the payload) Q_FOREACH (const Flag ¤tFlag, currentItem.flags()) { const QByteArray currentFlagName = currentFlag.name().toLatin1(); if (localFlagsToPreserve.contains(currentFlagName)) { flagNames.insert(currentFlagName); } } const auto flags = HandlerHelper::resolveFlags(flagNames); storageBackend()->setItemsFlags({currentItem}, flags, &flagsChanged, col, true); if (flagsChanged) { changedParts.insert(AKONADI_PARAM_FLAGS); needsUpdate = true; } } if (cmd.tags().isEmpty()) { bool tagsAdded = false, tagsRemoved = false; if (!cmd.addedTags().isEmpty()) { const auto addedTags = HandlerHelper::tagsFromScope(cmd.addedTags(), connection()); storageBackend()->appendItemsTags({currentItem}, addedTags, &tagsAdded, true, col, true); } if (!cmd.removedTags().isEmpty()) { const Tag::List removedTags = HandlerHelper::tagsFromScope(cmd.removedTags(), connection()); storageBackend()->removeItemsTags({currentItem}, removedTags, &tagsRemoved, true); } if (tagsAdded || tagsRemoved) { changedParts.insert(AKONADI_PARAM_TAGS); needsUpdate = true; } } else { bool tagsChanged = false; const auto tags = HandlerHelper::tagsFromScope(cmd.tags(), connection()); storageBackend()->setItemsTags({currentItem}, tags, &tagsChanged, true); if (tagsChanged) { changedParts.insert(AKONADI_PARAM_TAGS); needsUpdate = true; } } const Part::List existingParts = Part::retrieveFiltered(Part::pimItemIdColumn(), currentItem.id()); QMap partsSizes; for (const Part &part : existingParts) { partsSizes.insert(PartTypeHelper::fullName(part.partType()).toLatin1(), part.datasize()); } PartStreamer streamer(connection(), currentItem); Q_FOREACH (const QByteArray &partName, cmd.parts()) { bool changed = false; qint64 partSize = 0; try { streamer.stream(true, partName, partSize, &changed); } catch (const PartStreamerException &e) { return failureResponse(e.what()); } if (changed) { changedParts.insert(partName); partsSizes.insert(partName, partSize); needsUpdate = true; } } const qint64 size = std::accumulate(partsSizes.begin(), partsSizes.end(), 0); if (size > currentItem.size()) { currentItem.setSize(size); needsUpdate = true; } if (needsUpdate) { currentItem.setRev(qMax(newItem.rev(), currentItem.rev()) + 1); currentItem.setAtime(QDateTime::currentDateTimeUtc()); // Only mark dirty when merged from application currentItem.setDirty(!connection()->context()->resource().isValid()); // Store all changes if (!currentItem.update()) { return failureResponse("Failed to store merged item"); } notify(currentItem, currentItem.collection(), changedParts); } sendResponse(currentItem, cmd.mergeModes()); return true; } -bool AkAppend::sendResponse(const PimItem &item, Protocol::CreateItemCommand::MergeModes mergeModes) +bool ItemCreateHandler::sendResponse(const PimItem &item, Protocol::CreateItemCommand::MergeModes mergeModes) { if (mergeModes & Protocol::CreateItemCommand::Silent || mergeModes & Protocol::CreateItemCommand::None) { Protocol::FetchItemsResponse resp; resp.setId(item.id()); resp.setMTime(item.datetime()); Handler::sendResponse(std::move(resp)); return true; } Protocol::ItemFetchScope fetchScope; fetchScope.setAncestorDepth(Protocol::ItemFetchScope::ParentAncestor); fetchScope.setFetch(Protocol::ItemFetchScope::AllAttributes | Protocol::ItemFetchScope::FullPayload | Protocol::ItemFetchScope::CacheOnly | Protocol::ItemFetchScope::Flags | Protocol::ItemFetchScope::GID | Protocol::ItemFetchScope::MTime | Protocol::ItemFetchScope::RemoteID | Protocol::ItemFetchScope::RemoteRevision | Protocol::ItemFetchScope::Size | Protocol::ItemFetchScope::Tags); ImapSet set; set.add(QVector() << item.id()); Scope scope; scope.setUidSet(set); - FetchHelper fetchHelper(connection(), scope, fetchScope, Protocol::TagFetchScope{}); + ItemFetchHelper fetchHelper(connection(), scope, fetchScope, Protocol::TagFetchScope{}); if (!fetchHelper.fetchItems()) { return failureResponse("Failed to retrieve item"); } return true; } -bool AkAppend::notify(const PimItem &item, bool seen, const Collection &collection) +bool ItemCreateHandler::notify(const PimItem &item, bool seen, const Collection &collection) { storageBackend()->notificationCollector()->itemAdded(item, seen, collection); if (PreprocessorManager::instance()->isActive()) { // enqueue the item for preprocessing PreprocessorManager::instance()->beginHandleItem(item, storageBackend()); } return true; } -bool AkAppend::notify(const PimItem &item, const Collection &collection, +bool ItemCreateHandler::notify(const PimItem &item, const Collection &collection, const QSet &changedParts) { if (!changedParts.isEmpty()) { storageBackend()->notificationCollector()->itemChanged(item, changedParts, collection); } return true; } -bool AkAppend::parseStream() +bool ItemCreateHandler::parseStream() { const auto &cmd = Protocol::cmdCast(m_command); // FIXME: The streaming/reading of all item parts can hold the transaction for // unnecessary long time -> should we wrap the PimItem into one transaction // and try to insert Parts independently? In case we fail to insert a part, // it's not a problem as it can be re-fetched at any time, except for attributes. - Transaction transaction(storageBackend(), QStringLiteral("AKAPPEND")); + Transaction transaction(storageBackend(), QStringLiteral("ItemCreateHandler")); ExternalPartStorageTransaction storageTrx; PimItem item; Collection parentCol; if (!buildPimItem(cmd, item, parentCol)) { return false; } if (cmd.mergeModes() == Protocol::CreateItemCommand::None) { if (!insertItem(cmd, item, parentCol)) { return false; } if (!transaction.commit()) { return failureResponse(QStringLiteral("Failed to commit transaction")); } storageTrx.commit(); } else { // Merging is always restricted to the same collection SelectQueryBuilder qb; qb.setForUpdate(); qb.addValueCondition(PimItem::collectionIdColumn(), Query::Equals, parentCol.id()); Query::Condition rootCondition(Query::Or); Query::Condition mergeCondition(Query::And); if (cmd.mergeModes() & Protocol::CreateItemCommand::GID) { mergeCondition.addValueCondition(PimItem::gidColumn(), Query::Equals, item.gid()); } if (cmd.mergeModes() & Protocol::CreateItemCommand::RemoteID) { mergeCondition.addValueCondition(PimItem::remoteIdColumn(), Query::Equals, item.remoteId()); } rootCondition.addCondition(mergeCondition); // If an Item with matching RID but empty GID exists during GID merge, // merge into this item instead of creating a new one if (cmd.mergeModes() & Protocol::CreateItemCommand::GID && !item.remoteId().isEmpty()) { mergeCondition = Query::Condition(Query::And); mergeCondition.addValueCondition(PimItem::remoteIdColumn(), Query::Equals, item.remoteId()); mergeCondition.addValueCondition(PimItem::gidColumn(), Query::Equals, QStringLiteral("")); rootCondition.addCondition(mergeCondition); } qb.addCondition(rootCondition); if (!qb.exec()) { return failureResponse("Failed to query database for item"); } const QVector result = qb.result(); if (result.isEmpty()) { - // No item with such GID/RID exists, so call AkAppend::insert() and behave + // No item with such GID/RID exists, so call ItemCreateHandler::insert() and behave // like if this was a new item if (!insertItem(cmd, item, parentCol)) { return false; } if (!transaction.commit()) { return failureResponse("Failed to commit transaction"); } storageTrx.commit(); } else if (result.count() == 1) { // Item with matching GID/RID combination exists, so merge this item into it // and send itemChanged() PimItem existingItem = result.at(0); if (!mergeItem(cmd, item, existingItem, parentCol)) { return false; } if (!transaction.commit()) { return failureResponse("Failed to commit transaction"); } storageTrx.commit(); } else { qCWarning(AKONADISERVER_LOG) << "Multiple merge candidates:"; for (const PimItem &item : result) { qCWarning(AKONADISERVER_LOG) << "\tID:" << item.id() << ", RID:" << item.remoteId() << ", GID:" << item.gid() << ", Collection:" << item.collection().name() << "(" << item.collectionId() << ")" << ", Resource:" << item.collection().resource().name() << "(" << item.collection().resourceId() << ")"; } // Nor GID or RID are guaranteed to be unique, so make sure we don't merge // something we don't want return failureResponse(QStringLiteral("Multiple merge candidates, aborting")); } } return successResponse(); } diff --git a/src/server/handler/akappend.h b/src/server/handler/itemcreatehandler.h similarity index 94% rename from src/server/handler/akappend.h rename to src/server/handler/itemcreatehandler.h index 98c9bc206..a6dafe742 100644 --- a/src/server/handler/akappend.h +++ b/src/server/handler/itemcreatehandler.h @@ -1,69 +1,70 @@ /*************************************************************************** * Copyright (C) 2007 by Robert Zwerus * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * 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 Library 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_AKAPPEND_H -#define AKONADI_AKAPPEND_H + +#ifndef AKONADI_ITEMCREATEHANDLER_H_ +#define AKONADI_ITEMCREATEHANDLER_H_ #include "handler.h" #include "entities.h" namespace Akonadi { namespace Server { /** @ingroup akonadi_server_handler Handler for the X-AKAPPEND command. This command is used to append an item with multiple parts. */ -class AkAppend : public Handler +class ItemCreateHandler: public Handler { public: - ~AkAppend() override = default; + ~ItemCreateHandler() override = default; bool parseStream() override; private: bool buildPimItem(const Protocol::CreateItemCommand &cmd, PimItem &item, Collection &parentCollection); bool insertItem(const Protocol::CreateItemCommand &cmd, PimItem &item, const Collection &parentCollection); bool mergeItem(const Protocol::CreateItemCommand &cmd, PimItem &newItem, PimItem ¤tItem, const Collection &parentCollection); bool sendResponse(const PimItem &item, Protocol::CreateItemCommand::MergeModes mergeModes); bool notify(const PimItem &item, bool seen, const Collection &collection); bool notify(const PimItem &item, const Collection &collection, const QSet &changedParts); }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler/remove.cpp b/src/server/handler/itemdeletehandler.cpp similarity index 96% rename from src/server/handler/remove.cpp rename to src/server/handler/itemdeletehandler.cpp index 9c502164e..18e0aa4f6 100644 --- a/src/server/handler/remove.cpp +++ b/src/server/handler/itemdeletehandler.cpp @@ -1,62 +1,62 @@ /* Copyright (c) 2009 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "remove.h" +#include "itemdeletehandler.h" #include "connection.h" #include "storage/datastore.h" #include "storage/itemqueryhelper.h" #include "storage/selectquerybuilder.h" #include "storage/transaction.h" #include using namespace Akonadi; using namespace Akonadi::Server; -bool Remove::parseStream() +bool ItemDeleteHandler::parseStream() { const auto &cmd = Protocol::cmdCast(m_command); connection()->context()->setScopeContext(cmd.scopeContext()); SelectQueryBuilder qb; ItemQueryHelper::scopeToQuery(cmd.items(), connection()->context(), qb); DataStore *store = connection()->storageBackend(); Transaction transaction(store, QStringLiteral("REMOVE")); if (!qb.exec()) { return failureResponse("Unable to execute query"); } const QVector items = qb.result(); if (items.isEmpty()) { return failureResponse("No items found"); } if (!store->cleanupPimItems(items)) { return failureResponse("Deletion failed"); } if (!transaction.commit()) { return failureResponse("Unable to commit transaction"); } return successResponse(); } diff --git a/src/server/handler/remove.h b/src/server/handler/itemdeletehandler.h similarity index 89% rename from src/server/handler/remove.h rename to src/server/handler/itemdeletehandler.h index 1c6c46016..b55abeac0 100644 --- a/src/server/handler/remove.h +++ b/src/server/handler/itemdeletehandler.h @@ -1,52 +1,52 @@ /* Copyright (c) 2009 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef AKONADI_REMOVE_H -#define AKONADI_REMOVE_H +#ifndef AKONADI_ITEMDELETEHANDLER_H_ +#define AKONADI_ITEMDELETEHANDLER_H_ #include "handler.h" namespace Akonadi { namespace Server { /** @ingroup akonadi_server_handler Handler for the item deletion command.

Semantics

Removes the selected items. Item selection can happen within the usual three scopes: - based on a uid set relative to the currently selected collection - based on a global uid set (UID) - based on a remote identifier within the currently selected collection (RID) */ -class Remove : public Handler +class ItemDeleteHandler: public Handler { public: - ~Remove() override = default; + ~ItemDeleteHandler() override = default; bool parseStream() override; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler/fetch.cpp b/src/server/handler/itemfetchhandler.cpp similarity index 91% rename from src/server/handler/fetch.cpp rename to src/server/handler/itemfetchhandler.cpp index 6c3a23c1a..70c24be32 100644 --- a/src/server/handler/fetch.cpp +++ b/src/server/handler/itemfetchhandler.cpp @@ -1,50 +1,50 @@ /*************************************************************************** * Copyright (C) 2006 by Tobias Koenig * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * 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 Library 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 "fetch.h" +#include "itemfetchhandler.h" #include "connection.h" -#include "fetchhelper.h" +#include "itemfetchhelper.h" #include "cachecleaner.h" using namespace Akonadi; using namespace Akonadi::Server; -bool Fetch::parseStream() +bool ItemFetchHandler::parseStream() { const auto &cmd = Protocol::cmdCast(m_command); if (!connection()->context()->setScopeContext(cmd.scopeContext())) { return failureResponse(QStringLiteral("Invalid scope context")); } // We require context in case we do RID fetch if (connection()->context()->isEmpty() && cmd.scope().scope() == Scope::Rid) { return failureResponse(QStringLiteral("No FETCH context specified")); } CacheCleanerInhibitor inhibitor; - FetchHelper fetchHelper(connection(), cmd.scope(), cmd.itemFetchScope(), cmd.tagFetchScope()); + ItemFetchHelper fetchHelper(connection(), cmd.scope(), cmd.itemFetchScope(), cmd.tagFetchScope()); if (!fetchHelper.fetchItems()) { return failureResponse(QStringLiteral("Failed to fetch items")); } return successResponse(); } diff --git a/src/server/handler/fetch.h b/src/server/handler/itemfetchhandler.h similarity index 91% rename from src/server/handler/fetch.h rename to src/server/handler/itemfetchhandler.h index dd591e46b..662dd3cb8 100644 --- a/src/server/handler/fetch.h +++ b/src/server/handler/itemfetchhandler.h @@ -1,46 +1,46 @@ /*************************************************************************** * Copyright (C) 2006 by Tobias Koenig * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * 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 Library 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 AKONADIFETCH_H -#define AKONADIFETCH_H +#ifndef AKONADI_ITEMFETCHHANDLER_H_ +#define AKONADI_ITEMFETCHHANDLER_H_ #include "handler.h" namespace Akonadi { namespace Server { /** @ingroup akonadi_server_handler Handler for the fetch command. */ -class Fetch : public Handler +class ItemFetchHandler: public Handler { public: - ~Fetch() override = default; + ~ItemFetchHandler() override = default; bool parseStream() override; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler/fetchhelper.cpp b/src/server/handler/itemfetchhelper.cpp similarity index 94% rename from src/server/handler/fetchhelper.cpp rename to src/server/handler/itemfetchhelper.cpp index 218902b55..e34721852 100644 --- a/src/server/handler/fetchhelper.cpp +++ b/src/server/handler/itemfetchhelper.cpp @@ -1,762 +1,764 @@ /*************************************************************************** * Copyright (C) 2006-2009 by Tobias Koenig * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * 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 Library 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 "fetchhelper.h" +#include "itemfetchhelper.h" #include "akonadi.h" #include "connection.h" #include "handler.h" #include "handlerhelper.h" #include "storage/selectquerybuilder.h" #include "storage/itemqueryhelper.h" #include "storage/itemretrievalmanager.h" #include "storage/itemretrievalrequest.h" #include "storage/parthelper.h" #include "storage/parttypehelper.h" #include "storage/transaction.h" #include "utils.h" #include "intervalcheck.h" #include "agentmanagerinterface.h" #include "dbusconnectionpool.h" #include "tagfetchhelper.h" -#include "relationfetch.h" +#include "relationfetchhandler.h" #include "akonadiserver_debug.h" #include #include #include #include #include #include #include using namespace Akonadi; using namespace Akonadi::Server; #define ENABLE_FETCH_PROFILING 0 #if ENABLE_FETCH_PROFILING #define BEGIN_TIMER(name) \ QElapsedTimer name##Timer; \ name##Timer.start(); #define END_TIMER(name) \ const double name##Elapsed = name##Timer.nsecsElapsed() / 1000000.0; #define PROF_INC(name) \ ++name; #else #define BEGIN_TIMER(name) #define END_TIMER(name) #define PROF_INC(name) #endif -FetchHelper::FetchHelper(Connection *connection, const Scope &scope, - const Protocol::ItemFetchScope &itemFetchScope, - const Protocol::TagFetchScope &tagFetchScope) - : FetchHelper(connection, connection->context(), scope, itemFetchScope, tagFetchScope) +ItemFetchHelper::ItemFetchHelper(Connection *connection, const Scope &scope, + const Protocol::ItemFetchScope &itemFetchScope, + const Protocol::TagFetchScope &tagFetchScope) + : ItemFetchHelper(connection, connection->context(), scope, itemFetchScope, tagFetchScope) { } -FetchHelper::FetchHelper(Connection *connection, CommandContext *context, const Scope &scope, - const Protocol::ItemFetchScope &itemFetchScope, - const Protocol::TagFetchScope &tagFetchScope) +ItemFetchHelper::ItemFetchHelper(Connection *connection, CommandContext *context, + const Scope &scope, + const Protocol::ItemFetchScope &itemFetchScope, + const Protocol::TagFetchScope &tagFetchScope) : mConnection(connection) , mContext(context) , mScope(scope) , mItemFetchScope(itemFetchScope) , mTagFetchScope(tagFetchScope) { std::fill(mItemQueryColumnMap, mItemQueryColumnMap + ItemQueryColumnCount, -1); } -void FetchHelper::disableATimeUpdates() +void ItemFetchHelper::disableATimeUpdates() { mUpdateATimeEnabled = false; } enum PartQueryColumns { PartQueryPimIdColumn, PartQueryTypeIdColumn, PartQueryDataColumn, PartQueryStorageColumn, PartQueryVersionColumn, PartQueryDataSizeColumn }; -QSqlQuery FetchHelper::buildPartQuery(const QVector &partList, bool allPayload, bool allAttrs) +QSqlQuery ItemFetchHelper::buildPartQuery(const QVector &partList, bool allPayload, bool allAttrs) { ///TODO: merge with ItemQuery QueryBuilder partQuery(PimItem::tableName()); if (!partList.isEmpty() || allPayload || allAttrs) { partQuery.addJoin(QueryBuilder::InnerJoin, Part::tableName(), PimItem::idFullColumnName(), Part::pimItemIdFullColumnName()); partQuery.addColumn(PimItem::idFullColumnName()); partQuery.addColumn(Part::partTypeIdFullColumnName()); partQuery.addColumn(Part::dataFullColumnName()); partQuery.addColumn(Part::storageFullColumnName()); partQuery.addColumn(Part::versionFullColumnName()); partQuery.addColumn(Part::datasizeFullColumnName()); partQuery.addSortColumn(PimItem::idFullColumnName(), Query::Descending); if (!partList.isEmpty() || allPayload || allAttrs) { Query::Condition cond(Query::Or); for (const QByteArray &b : qAsConst(partList)) { if (b.startsWith("PLD") || b.startsWith("ATR")) { cond.addValueCondition(Part::partTypeIdFullColumnName(), Query::Equals, PartTypeHelper::fromFqName(b).id()); } } if (allPayload || allAttrs) { partQuery.addJoin(QueryBuilder::InnerJoin, PartType::tableName(), Part::partTypeIdFullColumnName(), PartType::idFullColumnName()); if (allPayload) { cond.addValueCondition(PartType::nsFullColumnName(), Query::Equals, QStringLiteral("PLD")); } if (allAttrs) { cond.addValueCondition(PartType::nsFullColumnName(), Query::Equals, QStringLiteral("ATR")); } } partQuery.addCondition(cond); } ItemQueryHelper::scopeToQuery(mScope, mContext, partQuery); if (!partQuery.exec()) { throw HandlerException("Unable to list item parts"); } partQuery.query().next(); } return partQuery.query(); } -QSqlQuery FetchHelper::buildItemQuery() +QSqlQuery ItemFetchHelper::buildItemQuery() { QueryBuilder itemQuery(PimItem::tableName()); int column = 0; #define ADD_COLUMN(colName, colId) { itemQuery.addColumn( colName ); mItemQueryColumnMap[colId] = column++; } ADD_COLUMN(PimItem::idFullColumnName(), ItemQueryPimItemIdColumn); if (mItemFetchScope.fetchRemoteId()) { ADD_COLUMN(PimItem::remoteIdFullColumnName(), ItemQueryPimItemRidColumn) } ADD_COLUMN(PimItem::mimeTypeIdFullColumnName(), ItemQueryMimeTypeIdColumn) ADD_COLUMN(PimItem::revFullColumnName(), ItemQueryRevColumn) if (mItemFetchScope.fetchRemoteRevision()) { ADD_COLUMN(PimItem::remoteRevisionFullColumnName(), ItemQueryRemoteRevisionColumn) } if (mItemFetchScope.fetchSize()) { ADD_COLUMN(PimItem::sizeFullColumnName(), ItemQuerySizeColumn) } if (mItemFetchScope.fetchMTime()) { ADD_COLUMN(PimItem::datetimeFullColumnName(), ItemQueryDatetimeColumn) } ADD_COLUMN(PimItem::collectionIdFullColumnName(), ItemQueryCollectionIdColumn) if (mItemFetchScope.fetchGID()) { ADD_COLUMN(PimItem::gidFullColumnName(), ItemQueryPimItemGidColumn) } #undef ADD_COLUMN itemQuery.addSortColumn(PimItem::idFullColumnName(), Query::Descending); ItemQueryHelper::scopeToQuery(mScope, mContext, itemQuery); if (mItemFetchScope.changedSince().isValid()) { itemQuery.addValueCondition(PimItem::datetimeFullColumnName(), Query::GreaterOrEqual, mItemFetchScope.changedSince().toUTC()); } if (!itemQuery.exec()) { throw HandlerException("Unable to list items"); } itemQuery.query().next(); return itemQuery.query(); } enum FlagQueryColumns { FlagQueryPimItemIdColumn, FlagQueryFlagIdColumn }; -QSqlQuery FetchHelper::buildFlagQuery() +QSqlQuery ItemFetchHelper::buildFlagQuery() { QueryBuilder flagQuery(PimItem::tableName()); flagQuery.addJoin(QueryBuilder::InnerJoin, PimItemFlagRelation::tableName(), PimItem::idFullColumnName(), PimItemFlagRelation::leftFullColumnName()); flagQuery.addColumn(PimItem::idFullColumnName()); flagQuery.addColumn(PimItemFlagRelation::rightFullColumnName()); ItemQueryHelper::scopeToQuery(mScope, mContext, flagQuery); flagQuery.addSortColumn(PimItem::idFullColumnName(), Query::Descending); if (!flagQuery.exec()) { throw HandlerException("Unable to retrieve item flags"); } flagQuery.query().next(); return flagQuery.query(); } enum TagQueryColumns { TagQueryItemIdColumn, TagQueryTagIdColumn, }; -QSqlQuery FetchHelper::buildTagQuery() +QSqlQuery ItemFetchHelper::buildTagQuery() { QueryBuilder tagQuery(PimItem::tableName()); tagQuery.addJoin(QueryBuilder::InnerJoin, PimItemTagRelation::tableName(), PimItem::idFullColumnName(), PimItemTagRelation::leftFullColumnName()); tagQuery.addJoin(QueryBuilder::InnerJoin, Tag::tableName(), Tag::idFullColumnName(), PimItemTagRelation::rightFullColumnName()); tagQuery.addColumn(PimItem::idFullColumnName()); tagQuery.addColumn(Tag::idFullColumnName()); ItemQueryHelper::scopeToQuery(mScope, mContext, tagQuery); tagQuery.addSortColumn(PimItem::idFullColumnName(), Query::Descending); if (!tagQuery.exec()) { throw HandlerException("Unable to retrieve item tags"); } tagQuery.query().next(); return tagQuery.query(); } enum VRefQueryColumns { VRefQueryCollectionIdColumn, VRefQueryItemIdColumn }; -QSqlQuery FetchHelper::buildVRefQuery() +QSqlQuery ItemFetchHelper::buildVRefQuery() { QueryBuilder vRefQuery(PimItem::tableName()); vRefQuery.addJoin(QueryBuilder::LeftJoin, CollectionPimItemRelation::tableName(), CollectionPimItemRelation::rightFullColumnName(), PimItem::idFullColumnName()); vRefQuery.addColumn(CollectionPimItemRelation::leftFullColumnName()); vRefQuery.addColumn(CollectionPimItemRelation::rightFullColumnName()); ItemQueryHelper::scopeToQuery(mScope, mContext, vRefQuery); vRefQuery.addSortColumn(PimItem::idFullColumnName(), Query::Descending); if (!vRefQuery.exec()) { throw HandlerException("Unable to retrieve virtual references"); } vRefQuery.query().next(); return vRefQuery.query(); } -bool FetchHelper::isScopeLocal(const Scope &scope) +bool ItemFetchHelper::isScopeLocal(const Scope &scope) { // The only agent allowed to override local scope is the Baloo Indexer if (!mConnection->sessionId().startsWith("akonadi_indexing_agent")) { return false; } // Get list of all resources that own all items in the scope QueryBuilder qb(PimItem::tableName(), QueryBuilder::Select); qb.setDistinct(true); qb.addColumn(Resource::nameFullColumnName()); qb.addJoin(QueryBuilder::LeftJoin, Collection::tableName(), PimItem::collectionIdFullColumnName(), Collection::idFullColumnName()); qb.addJoin(QueryBuilder::LeftJoin, Resource::tableName(), Collection::resourceIdFullColumnName(), Resource::idFullColumnName()); ItemQueryHelper::scopeToQuery(scope, mContext, qb); if (mContext->resource().isValid()) { qb.addValueCondition(Resource::nameFullColumnName(), Query::NotEquals, mContext->resource().name()); } if (!qb.exec()) { throw HandlerException("Failed to query database"); return false; } // If there is more than one resource, i.e. this is a fetch from multiple // collections, then don't bother and just return FALSE. This case is aimed // specifically on Baloo, which fetches items from each collection independently, // so it will pass this check. QSqlQuery query = qb.query(); if (query.size() != 1) { return false; } query.next(); const QString resourceName = query.value(0).toString(); query.finish(); org::freedesktop::Akonadi::AgentManager manager(DBus::serviceName(DBus::Control), QStringLiteral("/AgentManager"), DBusConnectionPool::threadConnection()); const QString typeIdentifier = manager.agentInstanceType(resourceName); const QVariantMap properties = manager.agentCustomProperties(typeIdentifier); return properties.value(QStringLiteral("HasLocalStorage"), false).toBool(); } -DataStore *FetchHelper::storageBackend() const +DataStore *ItemFetchHelper::storageBackend() const { if (mConnection) { if (auto store = mConnection->storageBackend()) { return store; } } return DataStore::self(); } -bool FetchHelper::fetchItems(std::function &&itemCallback) +bool ItemFetchHelper::fetchItems(std::function &&itemCallback) { BEGIN_TIMER(fetch) // retrieve missing parts // HACK: isScopeLocal() is a workaround for resources that have cache expiration // because when the cache expires, Baloo is not able to content of the items. So // we allow fetch of items that belong to local resources (like maildir) to ignore // cacheOnly and retrieve missing parts from the resource. However ItemRetriever // is painfully slow with many items and is generally designed to fetch a few // messages, not all of them. In the long term, we need a better way to do this. BEGIN_TIMER(itemRetriever) BEGIN_TIMER(scopeLocal) #if ENABLE_FETCH_PROFILING double scopeLocalElapsed = 0; #endif if (!mItemFetchScope.cacheOnly() || isScopeLocal(mScope)) { #if ENABLE_FETCH_PROFILING scopeLocalElapsed = scopeLocalTimer.elapsed(); #endif // trigger a collection sync if configured to do so triggerOnDemandFetch(); // Prepare for a call to ItemRetriever::exec(); // From a resource perspective the only parts that can be fetched are payloads. ItemRetriever retriever(mConnection); retriever.setScope(mScope); retriever.setRetrieveParts(mItemFetchScope.requestedPayloads()); retriever.setRetrieveFullPayload(mItemFetchScope.fullPayload()); retriever.setChangedSince(mItemFetchScope.changedSince()); if (!retriever.exec() && !mItemFetchScope.ignoreErrors()) { // There we go, retrieve the missing parts from the resource. if (mContext->resource().isValid()) { throw HandlerException(QStringLiteral("Unable to fetch item from backend (collection %1, resource %2) : %3") .arg(mContext->collectionId()) .arg(mContext->resource().id()) .arg(QString::fromLatin1(retriever.lastError()))); } else { throw HandlerException(QStringLiteral("Unable to fetch item from backend (collection %1) : %2") .arg(mContext->collectionId()) .arg(QString::fromLatin1(retriever.lastError()))); } } } END_TIMER(itemRetriever) BEGIN_TIMER(items) QSqlQuery itemQuery = buildItemQuery(); END_TIMER(items) // error if query did not find any item and scope is not listing items but // a request for a specific item if (!itemQuery.isValid()) { if (mItemFetchScope.ignoreErrors()) { return true; } switch (mScope.scope()) { case Scope::Uid: // fall through case Scope::Rid: // fall through case Scope::HierarchicalRid: // fall through case Scope::Gid: throw HandlerException("Item query returned empty result set"); break; default: break; } } // build part query if needed BEGIN_TIMER(parts) QSqlQuery partQuery(storageBackend()->database()); if (!mItemFetchScope.requestedParts().isEmpty() || mItemFetchScope.fullPayload() || mItemFetchScope.allAttributes()) { partQuery = buildPartQuery(mItemFetchScope.requestedParts(), mItemFetchScope.fullPayload(), mItemFetchScope.allAttributes()); } END_TIMER(parts) // build flag query if needed BEGIN_TIMER(flags) QSqlQuery flagQuery(storageBackend()->database()); if (mItemFetchScope.fetchFlags()) { flagQuery = buildFlagQuery(); } END_TIMER(flags) // build tag query if needed BEGIN_TIMER(tags) QSqlQuery tagQuery(storageBackend()->database()); if (mItemFetchScope.fetchTags()) { tagQuery = buildTagQuery(); } END_TIMER(tags) BEGIN_TIMER(vRefs) QSqlQuery vRefQuery(storageBackend()->database()); if (mItemFetchScope.fetchVirtualReferences()) { vRefQuery = buildVRefQuery(); } END_TIMER(vRefs) #if ENABLE_FETCH_PROFILING int itemsCount = 0; int flagsCount = 0; int partsCount = 0; int tagsCount = 0; int vRefsCount = 0; #endif BEGIN_TIMER(processing) QHash flagIdNameCache; QHash mimeTypeIdNameCache; QHash partTypeIdNameCache; while (itemQuery.isValid()) { PROF_INC(itemsCount) const qint64 pimItemId = extractQueryResult(itemQuery, ItemQueryPimItemIdColumn).toLongLong(); const int pimItemRev = extractQueryResult(itemQuery, ItemQueryRevColumn).toInt(); Protocol::FetchItemsResponse response; response.setId(pimItemId); response.setRevision(pimItemRev); const qint64 mimeTypeId = extractQueryResult(itemQuery, ItemQueryMimeTypeIdColumn).toLongLong(); auto mtIter = mimeTypeIdNameCache.find(mimeTypeId); if (mtIter == mimeTypeIdNameCache.end()) { mtIter = mimeTypeIdNameCache.insert(mimeTypeId, MimeType::retrieveById(mimeTypeId).name()); } response.setMimeType(mtIter.value()); if (mItemFetchScope.fetchRemoteId()) { response.setRemoteId(extractQueryResult(itemQuery, ItemQueryPimItemRidColumn).toString()); } response.setParentId(extractQueryResult(itemQuery, ItemQueryCollectionIdColumn).toLongLong()); if (mItemFetchScope.fetchSize()) { response.setSize(extractQueryResult(itemQuery, ItemQuerySizeColumn).toLongLong()); } if (mItemFetchScope.fetchMTime()) { response.setMTime(Utils::variantToDateTime(extractQueryResult(itemQuery, ItemQueryDatetimeColumn))); } if (mItemFetchScope.fetchRemoteRevision()) { response.setRemoteRevision(extractQueryResult(itemQuery, ItemQueryRemoteRevisionColumn).toString()); } if (mItemFetchScope.fetchGID()) { response.setGid(extractQueryResult(itemQuery, ItemQueryPimItemGidColumn).toString()); } if (mItemFetchScope.fetchFlags()) { QVector flags; while (flagQuery.isValid()) { const qint64 id = flagQuery.value(FlagQueryPimItemIdColumn).toLongLong(); if (id > pimItemId) { flagQuery.next(); continue; } else if (id < pimItemId) { break; } const qint64 flagId = flagQuery.value(FlagQueryFlagIdColumn).toLongLong(); auto flagNameIter = flagIdNameCache.find(flagId); if (flagNameIter == flagIdNameCache.end()) { flagNameIter = flagIdNameCache.insert(flagId, Flag::retrieveById(flagId).name().toUtf8()); } flags << flagNameIter.value(); flagQuery.next(); } response.setFlags(flags); } if (mItemFetchScope.fetchTags()) { QVector tagIds; QVector tags; while (tagQuery.isValid()) { PROF_INC(tagsCount) const qint64 id = tagQuery.value(TagQueryItemIdColumn).toLongLong(); if (id > pimItemId) { tagQuery.next(); continue; } else if (id < pimItemId) { break; } tagIds << tagQuery.value(TagQueryTagIdColumn).toLongLong(); tagQuery.next(); } tags.reserve(tagIds.count()); if (mTagFetchScope.fetchIdOnly()) { for (qint64 tagId : qAsConst(tagIds)) { Protocol::FetchTagsResponse resp; resp.setId(tagId); tags << resp; } } else { for (qint64 tagId : qAsConst(tagIds)) { tags.push_back(HandlerHelper::fetchTagsResponse(Tag::retrieveById(tagId), mTagFetchScope, mConnection)); } } response.setTags(tags); } if (mItemFetchScope.fetchVirtualReferences()) { QVector vRefs; while (vRefQuery.isValid()) { PROF_INC(vRefsCount) const qint64 id = vRefQuery.value(VRefQueryItemIdColumn).toLongLong(); if (id > pimItemId) { vRefQuery.next(); continue; } else if (id < pimItemId) { break; } vRefs << vRefQuery.value(VRefQueryCollectionIdColumn).toLongLong(); vRefQuery.next(); } response.setVirtualReferences(vRefs); } if (mItemFetchScope.fetchRelations()) { SelectQueryBuilder qb; Query::Condition condition; condition.setSubQueryMode(Query::Or); condition.addValueCondition(Relation::leftIdFullColumnName(), Query::Equals, pimItemId); condition.addValueCondition(Relation::rightIdFullColumnName(), Query::Equals, pimItemId); qb.addCondition(condition); qb.addGroupColumns(QStringList() << Relation::leftIdColumn() << Relation::rightIdColumn() << Relation::typeIdColumn() << Relation::remoteIdColumn()); if (!qb.exec()) { throw HandlerException("Unable to list item relations"); } QVector relations; const auto result = qb.result(); relations.reserve(result.size()); for (const Relation &rel : result) { relations.push_back(HandlerHelper::fetchRelationsResponse(rel));; } response.setRelations(relations); } if (mItemFetchScope.ancestorDepth() != Protocol::ItemFetchScope::NoAncestor) { response.setAncestors(ancestorsForItem(response.parentId())); } bool skipItem = false; QVector cachedParts; QVector parts; while (partQuery.isValid()) { PROF_INC(partsCount) const qint64 id = partQuery.value(PartQueryPimIdColumn).toLongLong(); if (id > pimItemId) { partQuery.next(); continue; } else if (id < pimItemId) { break; } const qint64 partTypeId = partQuery.value(PartQueryTypeIdColumn).toLongLong(); auto ptIter = partTypeIdNameCache.find(partTypeId); if (ptIter == partTypeIdNameCache.end()) { ptIter = partTypeIdNameCache.insert(partTypeId, PartTypeHelper::fullName(PartType::retrieveById(partTypeId)).toUtf8()); } Protocol::PartMetaData metaPart; Protocol::StreamPayloadResponse partData; partData.setPayloadName(ptIter.value()); metaPart.setName(ptIter.value()); metaPart.setVersion(partQuery.value(PartQueryVersionColumn).toInt()); metaPart.setSize(partQuery.value(PartQueryDataSizeColumn).toLongLong()); const QByteArray data = Utils::variantToByteArray(partQuery.value(PartQueryDataColumn)); if (mItemFetchScope.checkCachedPayloadPartsOnly()) { if (!data.isEmpty()) { cachedParts << ptIter.value(); } partQuery.next(); } else { if (mItemFetchScope.ignoreErrors() && data.isEmpty()) { //We wanted the payload, couldn't get it, and are ignoring errors. Skip the item. //This is not an error though, it's fine to have empty payload parts (to denote existing but not cached parts) qCDebug(AKONADISERVER_LOG) << "item" << id << "has an empty payload part in parttable for part" << metaPart.name(); skipItem = true; break; } metaPart.setStorageType(static_cast( partQuery.value(PartQueryStorageColumn).toInt())); if (data.isEmpty()) { partData.setData(QByteArray("")); } else { partData.setData(data); } partData.setMetaData(metaPart); if (mItemFetchScope.requestedParts().contains(ptIter.value()) || mItemFetchScope.fullPayload() || mItemFetchScope.allAttributes()) { parts.append(partData); } partQuery.next(); } } response.setParts(parts); if (skipItem) { itemQuery.next(); continue; } if (mItemFetchScope.checkCachedPayloadPartsOnly()) { response.setCachedParts(cachedParts); } if (itemCallback) { itemCallback(std::move(response)); } else { mConnection->sendResponse(std::move(response)); } itemQuery.next(); } tagQuery.finish(); flagQuery.finish(); partQuery.finish(); vRefQuery.finish(); itemQuery.finish(); END_TIMER(processing) // update atime (only if the payload was actually requested, otherwise a simple resource sync prevents cache clearing) BEGIN_TIMER(aTime) if (mUpdateATimeEnabled && (needsAccessTimeUpdate(mItemFetchScope.requestedParts()) || mItemFetchScope.fullPayload())) { updateItemAccessTime(); } END_TIMER(aTime) END_TIMER(fetch) #if ENABLE_FETCH_PROFILING - qCDebug(AKONADISERVER_LOG) << "FetchHelper execution stats:"; + qCDebug(AKONADISERVER_LOG) << "ItemFetchHelper execution stats:"; qCDebug(AKONADISERVER_LOG) << "\tItems query:" << itemsElapsed << "ms," << itemsCount << " items in total"; qCDebug(AKONADISERVER_LOG) << "\tFlags query:" << flagsElapsed << "ms, " << flagsCount << " flags in total"; qCDebug(AKONADISERVER_LOG) << "\tParts query:" << partsElapsed << "ms, " << partsCount << " parts in total"; qCDebug(AKONADISERVER_LOG) << "\tTags query: " << tagsElapsed << "ms, " << tagsCount << " tags in total"; qCDebug(AKONADISERVER_LOG) << "\tVRefs query:" << vRefsElapsed << "ms, " << vRefsCount << " vRefs in total"; qCDebug(AKONADISERVER_LOG) << "\t------------"; qCDebug(AKONADISERVER_LOG) << "\tItem retriever:" << itemRetrieverElapsed << "ms (scope local:" << scopeLocalElapsed << "ms)"; qCDebug(AKONADISERVER_LOG) << "\tTotal query:" << (itemsElapsed + flagsElapsed + partsElapsed + tagsElapsed + vRefsElapsed) << "ms"; qCDebug(AKONADISERVER_LOG) << "\tTotal processing: " << processingElapsed << "ms"; qCDebug(AKONADISERVER_LOG) << "\tATime update:" << aTimeElapsed << "ms"; qCDebug(AKONADISERVER_LOG) << "\t============"; qCDebug(AKONADISERVER_LOG) << "\tTotal FETCH:" << fetchElapsed << "ms"; qCDebug(AKONADISERVER_LOG); qCDebug(AKONADISERVER_LOG); #endif return true; } -bool FetchHelper::needsAccessTimeUpdate(const QVector &parts) +bool ItemFetchHelper::needsAccessTimeUpdate(const QVector &parts) { // TODO technically we should compare the part list with the cache policy of // the parent collection of the retrieved items, but that's kinda expensive // Only updating the atime if the full payload was requested is a good // approximation though. return parts.contains(AKONADI_PARAM_PLD_RFC822); } -void FetchHelper::updateItemAccessTime() +void ItemFetchHelper::updateItemAccessTime() { Transaction transaction(storageBackend(), QStringLiteral("update atime")); QueryBuilder qb(PimItem::tableName(), QueryBuilder::Update); qb.setColumnValue(PimItem::atimeColumn(), QDateTime::currentDateTimeUtc()); ItemQueryHelper::scopeToQuery(mScope, mContext, qb); if (!qb.exec()) { qCWarning(AKONADISERVER_LOG) << "Unable to update item access time"; } else { transaction.commit(); } } -void FetchHelper::triggerOnDemandFetch() +void ItemFetchHelper::triggerOnDemandFetch() { if (mContext->collectionId() <= 0 || mItemFetchScope.cacheOnly()) { return; } Collection collection = mContext->collection(); // HACK: don't trigger on-demand syncing if the resource is the one triggering it if (mConnection->sessionId() == collection.resource().name().toLatin1()) { return; } storageBackend()->activeCachePolicy(collection); if (!collection.cachePolicySyncOnDemand()) { return; } if (AkonadiServer::instance()->intervalChecker()) { AkonadiServer::instance()->intervalChecker()->requestCollectionSync(collection); } } -QVector FetchHelper::ancestorsForItem(Collection::Id parentColId) +QVector ItemFetchHelper::ancestorsForItem(Collection::Id parentColId) { if (mItemFetchScope.ancestorDepth() == Protocol::ItemFetchScope::NoAncestor || parentColId == 0) { return QVector(); } const auto it = mAncestorCache.constFind(parentColId); if (it != mAncestorCache.cend()) { return *it; } QVector ancestors; Collection col = Collection::retrieveById(parentColId); const int depthNum = mItemFetchScope.ancestorDepth() == Protocol::ItemFetchScope::ParentAncestor ? 1 : INT_MAX; for (int i = 0; i < depthNum; ++i) { if (!col.isValid()) { Protocol::Ancestor ancestor; ancestor.setId(0); ancestors << ancestor; break; } Protocol::Ancestor ancestor; ancestor.setId(col.id()); ancestor.setRemoteId(col.remoteId()); ancestors << ancestor; col = col.parent(); } mAncestorCache.insert(parentColId, ancestors); return ancestors; } -QVariant FetchHelper::extractQueryResult(const QSqlQuery &query, FetchHelper::ItemQueryColumns column) const +QVariant ItemFetchHelper::extractQueryResult(const QSqlQuery &query, + ItemFetchHelper::ItemQueryColumns column) const { const int colId = mItemQueryColumnMap[column]; Q_ASSERT(colId >= 0); return query.value(colId); } diff --git a/src/server/handler/fetchhelper.h b/src/server/handler/itemfetchhelper.h similarity index 92% rename from src/server/handler/fetchhelper.h rename to src/server/handler/itemfetchhelper.h index 98795f217..7aae1b4e8 100644 --- a/src/server/handler/fetchhelper.h +++ b/src/server/handler/itemfetchhelper.h @@ -1,106 +1,106 @@ /*************************************************************************** * Copyright (C) 2006-2009 by Tobias Koenig * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * 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 Library 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_FETCHHELPER_H -#define AKONADI_FETCHHELPER_H +#ifndef AKONADI_ITEMFETCHHELPER_H +#define AKONADI_ITEMFETCHHELPER_H #include "storage/countquerybuilder.h" #include "storage/datastore.h" #include "storage/itemretriever.h" #include "commandcontext.h" #include #include #include #include -class FetchHelperTest; +class ItemFetchHelperTest; namespace Akonadi { namespace Server { class AggregatedItemFetchScope; class Connection; -class FetchHelper +class ItemFetchHelper { public: - FetchHelper(Connection *connection, const Scope &scope, + ItemFetchHelper(Connection *connection, const Scope &scope, const Protocol::ItemFetchScope &itemFetchScope, const Protocol::TagFetchScope &tagFagScope); - FetchHelper(Connection *connection, CommandContext *context, const Scope &scope, + ItemFetchHelper(Connection *connection, CommandContext *context, const Scope &scope, const Protocol::ItemFetchScope &itemFetchScope, const Protocol::TagFetchScope &tagFetchScope); bool fetchItems(std::function &&callback = {}); void disableATimeUpdates(); private: enum ItemQueryColumns { ItemQueryPimItemIdColumn, ItemQueryPimItemRidColumn, ItemQueryMimeTypeIdColumn, ItemQueryRevColumn, ItemQueryRemoteRevisionColumn, ItemQuerySizeColumn, ItemQueryDatetimeColumn, ItemQueryCollectionIdColumn, ItemQueryPimItemGidColumn, ItemQueryColumnCount }; void updateItemAccessTime(); void triggerOnDemandFetch(); QSqlQuery buildItemQuery(); QSqlQuery buildPartQuery(const QVector &partList, bool allPayload, bool allAttrs); QSqlQuery buildFlagQuery(); QSqlQuery buildTagQuery(); QSqlQuery buildVRefQuery(); QVector ancestorsForItem(Collection::Id parentColId); static bool needsAccessTimeUpdate(const QVector &parts); QVariant extractQueryResult(const QSqlQuery &query, ItemQueryColumns column) const; bool isScopeLocal(const Scope &scope); DataStore *storageBackend() const; static QByteArray tagsToByteArray(const Tag::List &tags); static QByteArray relationsToByteArray(const Relation::List &relations); private: Connection *mConnection = nullptr; CommandContext *mContext = nullptr; QHash> mAncestorCache; Scope mScope; Protocol::ItemFetchScope mItemFetchScope; Protocol::TagFetchScope mTagFetchScope; int mItemQueryColumnMap[ItemQueryColumnCount]; bool mUpdateATimeEnabled = true; - friend class ::FetchHelperTest; + friend class ::ItemFetchHelperTest; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler/link.cpp b/src/server/handler/itemlinkhandler.cpp similarity index 98% rename from src/server/handler/link.cpp rename to src/server/handler/itemlinkhandler.cpp index f8f4e9748..c85a00805 100644 --- a/src/server/handler/link.cpp +++ b/src/server/handler/itemlinkhandler.cpp @@ -1,109 +1,109 @@ /* Copyright (c) 2008 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "link.h" +#include "itemlinkhandler.h" #include "connection.h" #include "handlerhelper.h" #include "storage/datastore.h" #include "storage/itemqueryhelper.h" #include "storage/transaction.h" #include "storage/selectquerybuilder.h" #include "storage/collectionqueryhelper.h" #include "akonadiserver_debug.h" #include using namespace Akonadi; using namespace Akonadi::Server; -bool Link::parseStream() +bool ItemLinkHandler::parseStream() { const auto &cmd = Protocol::cmdCast(m_command); const Collection collection = HandlerHelper::collectionFromScope(cmd.destination(), connection()); if (!collection.isVirtual()) { return failureResponse(QStringLiteral("Can't link items to non-virtual collections")); } /* FIXME BIN Resource originalContext; Scope::SelectionScope itemSelectionScope = Scope::selectionScopeFromByteArray(m_streamParser->peekString()); if (itemSelectionScope != Scope::None) { m_streamParser->readString(); // Unset Resource context if destination collection is specified using HRID/RID, // because otherwise the Resource context is relative to the destination collection // instead of the source collection (collection context) if ((mDestinationScope.scope() == Scope::HierarchicalRid || mDestinationScope.scope() == Scope::Rid) && itemSelectionScope == Scope::Rid) { originalContext = connection()->context()->resource(); connection()->context()->setResource(Resource()); } } Scope itemScope(itemSelectionScope); itemScope.parseScope(m_streamParser); */ SelectQueryBuilder qb; ItemQueryHelper::scopeToQuery(cmd.items(), connection()->context(), qb); /* if (originalContext.isValid()) { connection()->context()->setResource(originalContext); } */ if (!qb.exec()) { return failureResponse(QStringLiteral("Unable to execute item query")); } const PimItem::List items = qb.result(); const bool createLinks = (cmd.action() == Protocol::LinkItemsCommand::Link); DataStore *store = connection()->storageBackend(); Transaction transaction(store, createLinks ? QStringLiteral("LINK") : QStringLiteral("UNLINK")); PimItem::List toLink, toUnlink; for (const PimItem &item : items) { const bool alreadyLinked = collection.relatesToPimItem(item); bool result = true; if (createLinks && !alreadyLinked) { result = collection.addPimItem(item); toLink << item; } else if (!createLinks && alreadyLinked) { result = collection.removePimItem(item); toUnlink << item; } if (!result) { return failureResponse(QStringLiteral("Failed to modify item reference")); } } if (!transaction.commit()) { return failureResponse(QStringLiteral("Cannot commit transaction.")); } if (!toLink.isEmpty()) { store->notificationCollector()->itemsLinked(toLink, collection); } else if (!toUnlink.isEmpty()) { store->notificationCollector()->itemsUnlinked(toUnlink, collection); } return successResponse(); } diff --git a/src/server/handler/link.h b/src/server/handler/itemlinkhandler.h similarity index 88% rename from src/server/handler/link.h rename to src/server/handler/itemlinkhandler.h index 6b031de1a..d5fefd35b 100644 --- a/src/server/handler/link.h +++ b/src/server/handler/itemlinkhandler.h @@ -1,49 +1,49 @@ /* Copyright (c) 2008 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef AKONADI_LINK_H -#define AKONADI_LINK_H +#ifndef AKONADI_ITEMLINKHANDLER_H_ +#define AKONADI_ITEMLINKHANDLER_H_ #include "handler.h" namespace Akonadi { namespace Server { /** * @ingroup akonadi_server_handler * * Handler for the LINK and UNLINK commands. * * These commands are used to add and remove references of a set of items to a * virtual collection. */ -class Link : public Handler +class ItemLinkHandler: public Handler { public: - ~Link() override = default; + ~ItemLinkHandler() override = default; bool parseStream() override; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler/store.cpp b/src/server/handler/itemmodifyhandler.cpp similarity index 91% rename from src/server/handler/store.cpp rename to src/server/handler/itemmodifyhandler.cpp index 2c0e339f4..7942245e1 100644 --- a/src/server/handler/store.cpp +++ b/src/server/handler/itemmodifyhandler.cpp @@ -1,391 +1,391 @@ /*************************************************************************** * Copyright (C) 2006 by Tobias Koenig * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * 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 Library 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 "store.h" +#include "itemmodifyhandler.h" #include "connection.h" #include "handlerhelper.h" #include "storage/datastore.h" #include "storage/transaction.h" #include "storage/itemqueryhelper.h" #include "storage/selectquerybuilder.h" #include "storage/parthelper.h" #include "storage/dbconfig.h" #include "storage/itemretriever.h" #include "storage/parttypehelper.h" #include "storage/partstreamer.h" #include #include "akonadiserver_debug.h" #include #include using namespace Akonadi; using namespace Akonadi::Server; static bool payloadChanged(const QSet &changes) { for (const QByteArray &change : changes) { if (change.startsWith(AKONADI_PARAM_PLD)) { return true; } } return false; } -bool Store::replaceFlags(const PimItem::List &item, const QSet &flags, bool &flagsChanged) +bool ItemModifyHandler::replaceFlags(const PimItem::List &item, const QSet &flags, bool &flagsChanged) { Flag::List flagList = HandlerHelper::resolveFlags(flags); DataStore *store = connection()->storageBackend(); if (!store->setItemsFlags(item, flagList, &flagsChanged)) { - qCWarning(AKONADISERVER_LOG) << "Store::replaceFlags: Unable to replace flags"; + qCWarning(AKONADISERVER_LOG) << "ItemModifyHandler::replaceFlags: Unable to replace flags"; return false; } return true; } -bool Store::addFlags(const PimItem::List &items, const QSet &flags, bool &flagsChanged) +bool ItemModifyHandler::addFlags(const PimItem::List &items, const QSet &flags, bool &flagsChanged) { const Flag::List flagList = HandlerHelper::resolveFlags(flags); DataStore *store = connection()->storageBackend(); if (!store->appendItemsFlags(items, flagList, &flagsChanged)) { - qCWarning(AKONADISERVER_LOG) << "Store::addFlags: Unable to add new item flags"; + qCWarning(AKONADISERVER_LOG) << "ItemModifyHandler::addFlags: Unable to add new item flags"; return false; } return true; } -bool Store::deleteFlags(const PimItem::List &items, const QSet &flags, bool &flagsChanged) +bool ItemModifyHandler::deleteFlags(const PimItem::List &items, const QSet &flags, bool &flagsChanged) { DataStore *store = connection()->storageBackend(); QVector flagList; flagList.reserve(flags.size()); for (auto iter = flags.cbegin(), end = flags.cend(); iter != end; ++iter) { Flag flag = Flag::retrieveByName(QString::fromUtf8(*iter)); if (!flag.isValid()) { continue; } flagList.append(flag); } if (!store->removeItemsFlags(items, flagList, &flagsChanged)) { - qCWarning(AKONADISERVER_LOG) << "Store::deleteFlags: Unable to remove item flags"; + qCWarning(AKONADISERVER_LOG) << "ItemModifyHandler::deleteFlags: Unable to remove item flags"; return false; } return true; } -bool Store::replaceTags(const PimItem::List &item, const Scope &tags, bool &tagsChanged) +bool ItemModifyHandler::replaceTags(const PimItem::List &item, const Scope &tags, bool &tagsChanged) { const Tag::List tagList = HandlerHelper::tagsFromScope(tags, connection()); if (!connection()->storageBackend()->setItemsTags(item, tagList, &tagsChanged)) { - qCWarning(AKONADISERVER_LOG) << "Store::replaceTags: unable to replace tags"; + qCWarning(AKONADISERVER_LOG) << "ItemModifyHandler::replaceTags: unable to replace tags"; return false; } return true; } -bool Store::addTags(const PimItem::List &items, const Scope &tags, bool &tagsChanged) +bool ItemModifyHandler::addTags(const PimItem::List &items, const Scope &tags, bool &tagsChanged) { const Tag::List tagList = HandlerHelper::tagsFromScope(tags, connection()); if (!connection()->storageBackend()->appendItemsTags(items, tagList, &tagsChanged)) { - qCWarning(AKONADISERVER_LOG) << "Store::addTags: Unable to add new item tags"; + qCWarning(AKONADISERVER_LOG) << "ItemModifyHandler::addTags: Unable to add new item tags"; return false; } return true; } -bool Store::deleteTags(const PimItem::List &items, const Scope &tags, bool &tagsChanged) +bool ItemModifyHandler::deleteTags(const PimItem::List &items, const Scope &tags, bool &tagsChanged) { const Tag::List tagList = HandlerHelper::tagsFromScope(tags, connection()); if (!connection()->storageBackend()->removeItemsTags(items, tagList, &tagsChanged)) { - qCWarning(AKONADISERVER_LOG) << "Store::deleteTags: Unable to remove item tags"; + qCWarning(AKONADISERVER_LOG) << "ItemModifyHandler::deleteTags: Unable to remove item tags"; return false; } return true; } -bool Store::parseStream() +bool ItemModifyHandler::parseStream() { const auto &cmd = Protocol::cmdCast(m_command); //parseCommand(); DataStore *store = connection()->storageBackend(); Transaction transaction(store, QStringLiteral("STORE")); ExternalPartStorageTransaction storageTrx; // Set the same modification time for each item. QDateTime modificationtime = QDateTime::currentDateTimeUtc(); if (DbType::type(store->database()) != DbType::Sqlite) { // Remove milliseconds from the modificationtime. PSQL and MySQL don't // support milliseconds in DATETIME column, so FETCHed Items will report // time without milliseconds, while this command would return answer // with milliseconds modificationtime = modificationtime.addMSecs(-modificationtime.time().msec()); } // retrieve selected items SelectQueryBuilder qb; qb.setForUpdate(); ItemQueryHelper::scopeToQuery(cmd.items(), connection()->context(), qb); if (!qb.exec()) { return failureResponse("Unable to retrieve items"); } PimItem::List pimItems = qb.result(); if (pimItems.isEmpty()) { return failureResponse("No items found"); } for (int i = 0; i < pimItems.size(); ++i) { if (cmd.oldRevision() > -1) { // check for conflicts if a resources tries to overwrite an item with dirty payload const PimItem &pimItem = pimItems.at(i); if (connection()->isOwnerResource(pimItem)) { if (pimItem.dirty()) { const QString error = QStringLiteral("[LRCONFLICT] Resource %1 tries to modify item %2 (%3) (in collection %4) with dirty payload, aborting STORE."); return failureResponse( error.arg(pimItem.collection().resource().name()) .arg(pimItem.id()) .arg(pimItem.remoteId()).arg(pimItem.collectionId())); } } // check and update revisions if (pimItems.at(i).rev() != (int) cmd.oldRevision()) { const QString error = QStringLiteral("[LLCONFLICT] Resource %1 tries to modify item %2 (%3) (in collection %4) with revision %5; the item was modified elsewhere and has revision %6, aborting STORE."); return failureResponse(error.arg(pimItem.collection().resource().name()) .arg(pimItem.id()) .arg(pimItem.remoteId()).arg(pimItem.collectionId()) .arg(cmd.oldRevision()).arg(pimItems.at(i).rev())); } } } PimItem &item = pimItems.first(); QSet changes; qint64 partSizes = 0; qint64 size = 0; bool flagsChanged = false; bool tagsChanged = false; if (cmd.modifiedParts() & Protocol::ModifyItemsCommand::AddedFlags) { if (!addFlags(pimItems, cmd.addedFlags(), flagsChanged)) { return failureResponse("Unable to add item flags"); } } if (cmd.modifiedParts() & Protocol::ModifyItemsCommand::RemovedFlags) { if (!deleteFlags(pimItems, cmd.removedFlags(), flagsChanged)) { return failureResponse("Unable to remove item flags"); } } if (cmd.modifiedParts() & Protocol::ModifyItemsCommand::Flags) { if (!replaceFlags(pimItems, cmd.flags(), flagsChanged)) { return failureResponse("Unable to reset flags"); } } if (flagsChanged) { changes << AKONADI_PARAM_FLAGS; } if (cmd.modifiedParts() & Protocol::ModifyItemsCommand::AddedTags) { if (!addTags(pimItems, cmd.addedTags(), tagsChanged)) { return failureResponse("Unable to add item tags"); } } if (cmd.modifiedParts() & Protocol::ModifyItemsCommand::RemovedTags) { if (!deleteTags(pimItems, cmd.removedTags(), tagsChanged)) { return failureResponse("Unable to remove item tags"); } } if (cmd.modifiedParts() & Protocol::ModifyItemsCommand::Tags) { if (!replaceTags(pimItems, cmd.tags(), tagsChanged)) { return failureResponse("Unable to reset item tags"); } } if (tagsChanged) { changes << AKONADI_PARAM_TAGS; } if (item.isValid() && cmd.modifiedParts() & Protocol::ModifyItemsCommand::RemoteID) { if (item.remoteId() != cmd.remoteId() && !cmd.remoteId().isEmpty()) { if (!connection()->isOwnerResource(item)) { qCWarning(AKONADISERVER_LOG) << "Invalid attempt to modify the remoteID for item" << item.id() << "from" << item.remoteId() << "to" << cmd.remoteId(); return failureResponse("Only resources can modify remote identifiers"); } item.setRemoteId(cmd.remoteId()); changes << AKONADI_PARAM_REMOTEID; } } if (item.isValid() && cmd.modifiedParts() & Protocol::ModifyItemsCommand::GID) { if (item.gid() != cmd.gid()) { item.setGid(cmd.gid()); } changes << AKONADI_PARAM_GID; } if (item.isValid() && cmd.modifiedParts() & Protocol::ModifyItemsCommand::RemoteRevision) { if (item.remoteRevision() != cmd.remoteRevision()) { if (!connection()->isOwnerResource(item)) { return failureResponse("Only resources can modify remote revisions"); } item.setRemoteRevision(cmd.remoteRevision()); changes << AKONADI_PARAM_REMOTEREVISION; } } if (item.isValid() && !cmd.dirty()) { item.setDirty(false); } if (item.isValid() && cmd.modifiedParts() & Protocol::ModifyItemsCommand::Size) { size = cmd.itemSize(); changes << AKONADI_PARAM_SIZE; } if (item.isValid() && cmd.modifiedParts() & Protocol::ModifyItemsCommand::RemovedParts) { if (!cmd.removedParts().isEmpty()) { if (!store->removeItemParts(item, cmd.removedParts())) { return failureResponse("Unable to remove item parts"); } Q_FOREACH (const QByteArray &part, cmd.removedParts()) { changes.insert(part); } } } if (item.isValid() && cmd.modifiedParts() & Protocol::ModifyItemsCommand::Parts) { PartStreamer streamer(connection(), item); Q_FOREACH (const QByteArray &partName, cmd.parts()) { qint64 partSize = 0; try { streamer.stream(true, partName, partSize); } catch (const PartStreamerException &e) { return failureResponse(e.what()); } changes.insert(partName); partSizes += partSize; } } if (item.isValid() && cmd.modifiedParts() & Protocol::ModifyItemsCommand::Attributes) { PartStreamer streamer(connection(), item); const Protocol::Attributes attrs = cmd.attributes(); for (auto iter = attrs.cbegin(), end = attrs.cend(); iter != end; ++iter) { bool changed = false; try { streamer.streamAttribute(true, iter.key(), iter.value(), &changed); } catch (const PartStreamerException &e) { return failureResponse(e.what()); } if (changed) { changes.insert(iter.key()); } } } QDateTime datetime; if (!changes.isEmpty() || cmd.invalidateCache() || !cmd.dirty()) { // update item size if (pimItems.size() == 1 && (size > 0 || partSizes > 0)) { pimItems.first().setSize(qMax(size, partSizes)); } const bool onlyRemoteIdChanged = (changes.size() == 1 && changes.contains(AKONADI_PARAM_REMOTEID)); const bool onlyRemoteRevisionChanged = (changes.size() == 1 && changes.contains(AKONADI_PARAM_REMOTEREVISION)); const bool onlyRemoteIdAndRevisionChanged = (changes.size() == 2 && changes.contains(AKONADI_PARAM_REMOTEID) && changes.contains(AKONADI_PARAM_REMOTEREVISION)); const bool onlyFlagsChanged = (changes.size() == 1 && changes.contains(AKONADI_PARAM_FLAGS)); const bool onlyGIDChanged = (changes.size() == 1 && changes.contains(AKONADI_PARAM_GID)); // If only the remote id and/or the remote revision changed, we don't have to increase the REV, // because these updates do not change the payload and can only be done by the owning resource -> no conflicts possible const bool revisionNeedsUpdate = (!changes.isEmpty() && !onlyRemoteIdChanged && !onlyRemoteRevisionChanged && !onlyRemoteIdAndRevisionChanged && !onlyGIDChanged); // run update query and prepare change notifications for (int i = 0; i < pimItems.count(); ++i) { if (revisionNeedsUpdate) { pimItems[i].setRev(pimItems[i].rev() + 1); } PimItem &item = pimItems[i]; item.setDatetime(modificationtime); item.setAtime(modificationtime); if (!connection()->isOwnerResource(item) && payloadChanged(changes)) { item.setDirty(true); } if (!item.update()) { return failureResponse("Unable to write item changes into the database"); } if (cmd.invalidateCache()) { if (!store->invalidateItemCache(item)) { return failureResponse("Unable to invalidate item cache in the database"); } } // flags change notification went separately during command parsing // GID-only changes are ignored to prevent resources from updating their storage when no actual change happened if (cmd.notify() && !changes.isEmpty() && !onlyFlagsChanged && !onlyGIDChanged) { // Don't send FLAGS notification in itemChanged changes.remove(AKONADI_PARAM_FLAGS); store->notificationCollector()->itemChanged(item, changes); } if (!cmd.noResponse()) { Protocol::ModifyItemsResponse resp; resp.setId(item.id()); resp.setNewRevision(item.rev()); sendResponse(std::move(resp)); } } if (!transaction.commit()) { return failureResponse("Cannot commit transaction."); } // Always commit storage changes (deletion) after DB transaction storageTrx.commit(); datetime = modificationtime; } else { datetime = pimItems.first().datetime(); } Protocol::ModifyItemsResponse resp; resp.setModificationDateTime(datetime); return successResponse(std::move(resp)); } diff --git a/src/server/handler/store.h b/src/server/handler/itemmodifyhandler.h similarity index 95% rename from src/server/handler/store.h rename to src/server/handler/itemmodifyhandler.h index 6829f2845..58f3e62e2 100644 --- a/src/server/handler/store.h +++ b/src/server/handler/itemmodifyhandler.h @@ -1,88 +1,88 @@ /*************************************************************************** * Copyright (C) 2006 by Tobias Koenig * * Copyright (C) 2009 by Volker Krause * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * 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 Library 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 AKONADISTORE_H -#define AKONADISTORE_H +#ifndef AKONADI_ITEMMODIFYHANDLER_H_ +#define AKONADI_ITEMMODIFYHANDLER_H_ #include "handler.h" #include "entities.h" namespace Akonadi { namespace Server { /** @ingroup akonadi_server_handler Handler for the item modification command.

Semantics

Modifies the selected items. Item selection can happen within the usual three scopes: - based on a uid set relative to the currently selected collection - based on a uid set (UID) - based on a list of remote identifiers within the currently selected collection (RID) The following item properties can be modified: - the remote identifier (@c REMOTEID) - the remote revision (@c REMOTEREVISION) - the global identifier (@c GID) - resetting the dirty flag indication local changes not yet replicated to the backend (@c DIRTY) - adding/deleting/setting item flags (@c FLAGS) - setting the item size hint (@c SIZE) - changing item attributes - changing item payload parts If multiple items are selected only the following operations are valid: - adding flags - removing flags - settings flags The following operations are only allowed by resources: - resetting the dirty flag - invalidating the cache - modifying the remote identifier - modifying the remote revision Conflict detection: - only available when modifying a single item - requires the previous item revision to be provided (@c REV) */ -class Store : public Handler +class ItemModifyHandler: public Handler { public: - ~Store() override = default; + ~ItemModifyHandler() override = default; bool parseStream() override; private: bool replaceFlags(const PimItem::List &items, const QSet &flags, bool &flagsChanged); bool addFlags(const PimItem::List &items, const QSet &flags, bool &flagsChanged); bool deleteFlags(const PimItem::List &items, const QSet &flags, bool &flagsChanged); bool replaceTags(const PimItem::List &items, const Scope &tags, bool &tagsChanged); bool addTags(const PimItem::List &items, const Scope &tags, bool &tagsChanged); bool deleteTags(const PimItem::List &items, const Scope &tags, bool &tagsChanged); }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler/move.cpp b/src/server/handler/itemmovehandler.cpp similarity index 97% rename from src/server/handler/move.cpp rename to src/server/handler/itemmovehandler.cpp index 55d2aa4a9..e8a1e200d 100644 --- a/src/server/handler/move.cpp +++ b/src/server/handler/itemmovehandler.cpp @@ -1,169 +1,169 @@ /* Copyright (c) 2009 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "move.h" +#include "itemmovehandler.h" #include "connection.h" #include "handlerhelper.h" #include "cachecleaner.h" #include "storage/datastore.h" #include "storage/itemretriever.h" #include "storage/itemqueryhelper.h" #include "storage/selectquerybuilder.h" #include "storage/transaction.h" #include "storage/collectionqueryhelper.h" #include "akonadiserver_debug.h" using namespace Akonadi; using namespace Akonadi::Server; -void Move::itemsRetrieved(const QList &ids) +void ItemMoveHandler::itemsRetrieved(const QList &ids) { DataStore *store = connection()->storageBackend(); Transaction transaction(store, QStringLiteral("MOVE")); SelectQueryBuilder qb; qb.setForUpdate(); ItemQueryHelper::itemSetToQuery(ImapSet(ids), qb); qb.addValueCondition(PimItem::collectionIdFullColumnName(), Query::NotEquals, mDestination.id()); if (!qb.exec()) { failureResponse("Unable to execute query"); return; } const QVector items = qb.result(); if (items.isEmpty()) { return; } const QDateTime mtime = QDateTime::currentDateTimeUtc(); // Split the list by source collection QMap toMove; QMap sources; ImapSet toMoveIds; Q_FOREACH (/*sic!*/ PimItem item, items) { //krazy:exclude=foreach if (!item.isValid()) { failureResponse("Invalid item in result set!?"); return; } const Collection source = item.collection(); if (!source.isValid()) { failureResponse("Item without collection found!?"); return; } if (!sources.contains(source.id())) { sources.insert(source.id(), source); } Q_ASSERT(item.collectionId() != mDestination.id()); item.setCollectionId(mDestination.id()); item.setAtime(mtime); item.setDatetime(mtime); // if the resource moved itself, we assume it did so because the change happenned in the backend if (connection()->context()->resource().id() != mDestination.resourceId()) { item.setDirty(true); } if (!item.update()) { failureResponse("Unable to update item"); return; } toMove.insertMulti(source.id(), item); toMoveIds.add(QVector{ item.id() }); } if (!transaction.commit()) { failureResponse("Unable to commit transaction."); return; } // Emit notification for each source collection separately Collection source; PimItem::List itemsToMove; for (auto it = toMove.cbegin(), end = toMove.cend(); it != end; ++it) { if (source.id() != it.key()) { if (!itemsToMove.isEmpty()) { store->notificationCollector()->itemsMoved(itemsToMove, source, mDestination); } source = sources.value(it.key()); itemsToMove.clear(); } itemsToMove.push_back(*it); } if (!itemsToMove.isEmpty()) { store->notificationCollector()->itemsMoved(itemsToMove, source, mDestination); } // Batch-reset RID // The item should have an empty RID in the destination collection to avoid // RID conflicts with existing items (see T3904 in Phab). // We do it after emitting notification so that the FetchHelper can still // retrieve the RID QueryBuilder qb2(PimItem::tableName(), QueryBuilder::Update); qb2.setColumnValue(PimItem::remoteIdColumn(), QString()); ItemQueryHelper::itemSetToQuery(toMoveIds, connection()->context(), qb2); if (!qb2.exec()) { failureResponse("Unable to update RID"); return; } } -bool Move::parseStream() +bool ItemMoveHandler::parseStream() { const auto &cmd = Protocol::cmdCast(m_command); mDestination = HandlerHelper::collectionFromScope(cmd.destination(), connection()); if (mDestination.isVirtual()) { return failureResponse("Moving items into virtual collection is not allowed"); } if (!mDestination.isValid()) { return failureResponse("Invalid destination collection"); } connection()->context()->setScopeContext(cmd.itemsContext()); if (cmd.items().scope() == Scope::Rid) { if (!connection()->context()->collection().isValid()) { return failureResponse("RID move requires valid source collection"); } } CacheCleanerInhibitor inhibitor; // make sure all the items we want to move are in the cache ItemRetriever retriever(connection()); retriever.setScope(cmd.items()); retriever.setRetrieveFullPayload(true); QObject::connect(&retriever, &ItemRetriever::itemsRetrieved, [this](const QList &ids) { itemsRetrieved(ids); }); if (!retriever.exec()) { return failureResponse(retriever.lastError()); } return successResponse(); } diff --git a/src/server/handler/move.h b/src/server/handler/itemmovehandler.h similarity index 90% rename from src/server/handler/move.h rename to src/server/handler/itemmovehandler.h index 4c52989b5..2ffba5421 100644 --- a/src/server/handler/move.h +++ b/src/server/handler/itemmovehandler.h @@ -1,59 +1,59 @@ /* Copyright (c) 2009 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef AKONADI_MOVE_H -#define AKONADI_MOVE_H +#ifndef AKONADI_ITEMMOVEHANDLER_H_ +#define AKONADI_ITEMMOVEHANDLER_H_ #include "handler.h" namespace Akonadi { namespace Server { /** @ingroup akonadi_server_handler Handler for the item move command.

Semantics

Moves the selected items. Item selection can happen within the usual three scopes: - based on a uid set relative to the currently selected collection - based on a global uid set (UID) - based on a list of remote identifiers within the currently selected collection (RID) Destination is a collection id. */ -class Move : public Handler +class ItemMoveHandler: public Handler { public: - ~Move() override = default; + ~ItemMoveHandler() override = default; bool parseStream() override; private: void itemsRetrieved(const QList &ids); Collection mDestination; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler/login.cpp b/src/server/handler/loginhandler.cpp similarity index 96% rename from src/server/handler/login.cpp rename to src/server/handler/loginhandler.cpp index ad4e7f279..b80e43492 100644 --- a/src/server/handler/login.cpp +++ b/src/server/handler/loginhandler.cpp @@ -1,39 +1,39 @@ /*************************************************************************** * Copyright (C) 2006 by Till Adam * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * 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 Library 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 "login.h" +#include "loginhandler.h" #include "connection.h" using namespace Akonadi; using namespace Akonadi::Server; -bool Login::parseStream() +bool LoginHandler::parseStream() { const auto &cmd = Protocol::cmdCast(m_command); if (cmd.sessionId().isEmpty()) { return failureResponse(QStringLiteral("Missing session identifier")); } connection()->setSessionId(cmd.sessionId()); connection()->setState(Server::Authenticated); return successResponse(); } diff --git a/src/server/handler/login.h b/src/server/handler/loginhandler.h similarity index 92% rename from src/server/handler/login.h rename to src/server/handler/loginhandler.h index 9d7e8be32..bd4006dfd 100644 --- a/src/server/handler/login.h +++ b/src/server/handler/loginhandler.h @@ -1,45 +1,45 @@ /*************************************************************************** * Copyright (C) 2006 by Till Adam * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * 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 Library 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 AKONADILOGIN_H -#define AKONADILOGIN_H +#ifndef AKONADI_LOGINHANDLER_H_ +#define AKONADI_LOGINHANDLER_H_ #include "handler.h" namespace Akonadi { namespace Server { /** @ingroup akonadi_server_handler Handler for the login command. */ -class Login : public Handler +class LoginHandler: public Handler { public: - ~Login() override = default; + ~LoginHandler() override = default; bool parseStream() override; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler/logout.cpp b/src/server/handler/logouthandler.cpp similarity index 96% rename from src/server/handler/logout.cpp rename to src/server/handler/logouthandler.cpp index 07a065ab8..d7592278e 100644 --- a/src/server/handler/logout.cpp +++ b/src/server/handler/logouthandler.cpp @@ -1,31 +1,31 @@ /*************************************************************************** * Copyright (C) 2006 by Till Adam * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * 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 Library 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 "logout.h" +#include "logouthandler.h" using namespace Akonadi; using namespace Akonadi::Server; -bool Logout::parseStream() +bool LogoutHandler::parseStream() { sendResponse(); connection()->setState(LoggingOut); return true; } diff --git a/src/server/handler/logout.h b/src/server/handler/logouthandler.h similarity index 91% rename from src/server/handler/logout.h rename to src/server/handler/logouthandler.h index 02fe630ac..45ff468fa 100644 --- a/src/server/handler/logout.h +++ b/src/server/handler/logouthandler.h @@ -1,46 +1,46 @@ /*************************************************************************** * Copyright (C) 2006 by Till Adam * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * 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 Library 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 AKONADILOGOUT_H -#define AKONADILOGOUT_H +#ifndef AKONADI_LOGOUTHANDLER_H_ +#define AKONADI_LOGOUTHANDLER_H_ #include "handler.h" namespace Akonadi { namespace Server { /** @ingroup akonadi_server_handler Handler for the logout command. */ -class Logout : public Handler +class LogoutHandler: public Handler { public: - ~Logout() override = default; + ~LogoutHandler() override = default; bool parseStream() override; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler/relationfetch.cpp b/src/server/handler/relationfetchhandler.cpp similarity index 98% rename from src/server/handler/relationfetch.cpp rename to src/server/handler/relationfetchhandler.cpp index bbeb5d10a..d0c825623 100644 --- a/src/server/handler/relationfetch.cpp +++ b/src/server/handler/relationfetchhandler.cpp @@ -1,84 +1,84 @@ /*************************************************************************** * Copyright (C) 2014 by Christian Mollekopf * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * 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 Library 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 "relationfetch.h" +#include "relationfetchhandler.h" #include "handlerhelper.h" #include "connection.h" #include "storage/selectquerybuilder.h" #include using namespace Akonadi; using namespace Akonadi::Server; -bool RelationFetch::parseStream() +bool RelationFetchHandler::parseStream() { const auto &cmd = Protocol::cmdCast(m_command); SelectQueryBuilder relationQuery; if (cmd.side() > 0) { Query::Condition c; c.setSubQueryMode(Query::Or); c.addValueCondition(Relation::leftIdFullColumnName(), Query::Equals, cmd.side()); c.addValueCondition(Relation::rightIdFullColumnName(), Query::Equals, cmd.side()); relationQuery.addCondition(c); } else { if (cmd.left() > 0) { relationQuery.addValueCondition(Relation::leftIdFullColumnName(), Query::Equals, cmd.left()); } if (cmd.right() > 0) { relationQuery.addValueCondition(Relation::rightIdFullColumnName(), Query::Equals, cmd.right()); } } if (!cmd.types().isEmpty()) { relationQuery.addJoin(QueryBuilder::InnerJoin, RelationType::tableName(), Relation::typeIdFullColumnName(), RelationType::idFullColumnName()); QStringList types; types.reserve(cmd.types().size()); Q_FOREACH (const QByteArray &type, cmd.types()) { types << QString::fromUtf8(type); } relationQuery.addValueCondition(RelationType::nameFullColumnName(), Query::In, types); } if (!cmd.resource().isEmpty()) { Resource res = Resource::retrieveByName(cmd.resource()); if (!res.isValid()) { return failureResponse("Invalid resource"); } Query::Condition condition; condition.setSubQueryMode(Query::Or); condition.addColumnCondition(PimItem::idFullColumnName(), Query::Equals, Relation::leftIdFullColumnName()); condition.addColumnCondition(PimItem::idFullColumnName(), Query::Equals, Relation::rightIdFullColumnName()); relationQuery.addJoin(QueryBuilder::InnerJoin, PimItem::tableName(), condition); relationQuery.addJoin(QueryBuilder::InnerJoin, Collection::tableName(), PimItem::collectionIdFullColumnName(), Collection::idFullColumnName()); relationQuery.addValueCondition(Collection::resourceIdFullColumnName(), Query::Equals, res.id()); relationQuery.addGroupColumns(QStringList() << Relation::leftIdFullColumnName() << Relation::rightIdFullColumnName() << Relation::typeIdFullColumnName()); } if (!relationQuery.exec()) { return failureResponse("Failed to query for existing relation"); } const Relation::List existingRelations = relationQuery.result(); for (const Relation &relation : existingRelations) { sendResponse(HandlerHelper::fetchRelationsResponse(relation)); } return successResponse(); } diff --git a/src/server/handler/relationfetch.h b/src/server/handler/relationfetchhandler.h similarity index 90% rename from src/server/handler/relationfetch.h rename to src/server/handler/relationfetchhandler.h index 7936b0c7c..e2cb5e7a8 100644 --- a/src/server/handler/relationfetch.h +++ b/src/server/handler/relationfetchhandler.h @@ -1,46 +1,46 @@ /*************************************************************************** * Copyright (C) 2014 by Christian Mollekopf * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * 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 Library 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 AKONADIFETCHRELATION_H -#define AKONADIFETCHRELATION_H +#ifndef AKONADI_FETCHRELATIONHANDLER_H_ +#define AKONADI_FETCHRELATIONHANDLER_H_ #include "handler.h" namespace Akonadi { namespace Server { /** @ingroup akonadi_server_handler Handler for the RELATIONFETCH command. */ -class RelationFetch : public Handler +class RelationFetchHandler: public Handler { public: - ~RelationFetch() override = default; + ~RelationFetchHandler() override = default; bool parseStream() override; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler/relationstore.cpp b/src/server/handler/relationmodifyhandler.cpp similarity index 96% rename from src/server/handler/relationstore.cpp rename to src/server/handler/relationmodifyhandler.cpp index abc889ac3..c73bc17b8 100644 --- a/src/server/handler/relationstore.cpp +++ b/src/server/handler/relationmodifyhandler.cpp @@ -1,118 +1,118 @@ /* Copyright (c) 2014 Christian Mollekopf This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "relationstore.h" +#include "relationmodifyhandler.h" #include "connection.h" #include "storage/datastore.h" #include "storage/querybuilder.h" #include "storage/selectquerybuilder.h" using namespace Akonadi; using namespace Akonadi::Server; -Relation RelationStore::fetchRelation(qint64 leftId, qint64 rightId, qint64 typeId) +Relation RelationModifyHandler::fetchRelation(qint64 leftId, qint64 rightId, qint64 typeId) { SelectQueryBuilder relationQuery; relationQuery.addValueCondition(Relation::leftIdFullColumnName(), Query::Equals, leftId); relationQuery.addValueCondition(Relation::rightIdFullColumnName(), Query::Equals, rightId); relationQuery.addValueCondition(Relation::typeIdFullColumnName(), Query::Equals, typeId); if (!relationQuery.exec()) { throw HandlerException("Failed to query for existing relation"); } const Relation::List existingRelations = relationQuery.result(); if (!existingRelations.isEmpty()) { if (existingRelations.size() == 1) { return existingRelations.at(0); } else { throw HandlerException("Matched more than 1 relation"); } } return Relation(); } -bool RelationStore::parseStream() +bool RelationModifyHandler::parseStream() { const auto &cmd = Protocol::cmdCast(m_command); if (cmd.type().isEmpty()) { return failureResponse("Relation type not specified"); } if (cmd.left() < 0 || cmd.right() < 0) { return failureResponse("Invalid relation specified"); } if (!cmd.remoteId().isEmpty() && !connection()->context()->resource().isValid()) { return failureResponse("RemoteID can only be set by Resources"); } const QString typeName = QString::fromUtf8(cmd.type()); const RelationType relationType = RelationType::retrieveByNameOrCreate(typeName); if (!relationType.isValid()) { return failureResponse(QStringLiteral("Unable to create relation type '") % typeName % QStringLiteral("'")); } Relation existingRelation = fetchRelation(cmd.left(), cmd.right(), relationType.id()); if (existingRelation.isValid()) { existingRelation.setRemoteId(QLatin1String(cmd.remoteId())); if (!existingRelation.update()) { return failureResponse("Failed to update relation"); } } // Can't use insert(), does not work here (no "id" column) QueryBuilder inQb(Relation::tableName(), QueryBuilder::Insert); inQb.setIdentificationColumn(QString()); // omit "RETURING xyz" with PSQL inQb.setColumnValue(Relation::leftIdColumn(), cmd.left()); inQb.setColumnValue(Relation::rightIdColumn(), cmd.right()); inQb.setColumnValue(Relation::typeIdColumn(), relationType.id()); if (!inQb.exec()) { throw HandlerException("Failed to store relation"); } Relation insertedRelation = fetchRelation(cmd.left(), cmd.right(), relationType.id()); // Get all PIM items that are part of the relation SelectQueryBuilder itemsQuery; itemsQuery.setSubQueryMode(Query::Or); itemsQuery.addValueCondition(PimItem::idColumn(), Query::Equals, cmd.left()); itemsQuery.addValueCondition(PimItem::idColumn(), Query::Equals, cmd.right()); if (!itemsQuery.exec()) { return failureResponse("Adding relation failed"); } const PimItem::List items = itemsQuery.result(); if (items.size() != 2) { return failureResponse("Couldn't find items for relation"); } /* if (items[0].collection().resourceId() != items[1].collection().resourceId()) { throw HandlerException("Relations can only be created for items within the same resource"); } */ auto collector = storageBackend()->notificationCollector(); collector->relationAdded(insertedRelation); collector->itemsRelationsChanged(items, {insertedRelation}, {}); return successResponse(); } diff --git a/src/server/handler/relationstore.h b/src/server/handler/relationmodifyhandler.h similarity index 83% rename from src/server/handler/relationstore.h rename to src/server/handler/relationmodifyhandler.h index 630012d24..ceb9fa0a2 100644 --- a/src/server/handler/relationstore.h +++ b/src/server/handler/relationmodifyhandler.h @@ -1,46 +1,46 @@ /* Copyright (c) 2014 Christian Mollekop This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef AKONADI_RELATIONSTORE_H -#define AKONADI_RELATIONSTORE_H +#ifndef AKONADI_RELATIONMODIFYHANDLER_H_ +#define AKONADI_RELATIONMODIFYHANDLER_H_ #include "handler.h" namespace Akonadi { namespace Server { class Relation; -class RelationStore : public Handler +class RelationModifyHandler: public Handler { public: - ~RelationStore() override = default; + ~RelationModifyHandler() override = default; bool parseStream() override; private: Relation fetchRelation(qint64 leftId, qint64 rightId, qint64 typeId); }; } // namespace Server } // namespace Akonadi -#endif // AKONADI_RELATIONSTORE_H +#endif // AKONADI_RELATIONMODIFYHANDLER_H_ diff --git a/src/server/handler/relationremove.cpp b/src/server/handler/relationremovehandler.cpp similarity index 97% rename from src/server/handler/relationremove.cpp rename to src/server/handler/relationremovehandler.cpp index 1c31f6f42..ccdb0da65 100644 --- a/src/server/handler/relationremove.cpp +++ b/src/server/handler/relationremovehandler.cpp @@ -1,88 +1,88 @@ /* Copyright (c) 2014 Christian Mollekopf This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "relationremove.h" +#include "relationremovehandler.h" #include "connection.h" #include "storage/querybuilder.h" #include "storage/selectquerybuilder.h" #include "storage/queryhelper.h" #include "storage/datastore.h" using namespace Akonadi; using namespace Akonadi::Server; -bool RelationRemove::parseStream() +bool RelationRemoveHandler::parseStream() { const auto &cmd = Protocol::cmdCast(m_command); if (cmd.left() < 0 || cmd.right() < 0) { return failureResponse("Invalid relation id's provided"); } RelationType relType; if (!cmd.type().isEmpty()) { relType = RelationType::retrieveByName(QString::fromUtf8(cmd.type())); if (!relType.isValid()) { return failureResponse("Failed to load relation type"); } } SelectQueryBuilder relationQuery; relationQuery.addValueCondition(Relation::leftIdFullColumnName(), Query::Equals, cmd.left()); relationQuery.addValueCondition(Relation::rightIdFullColumnName(), Query::Equals, cmd.right()); if (relType.isValid()) { relationQuery.addValueCondition(Relation::typeIdFullColumnName(), Query::Equals, relType.id()); } if (!relationQuery.exec()) { return failureResponse("Failed to obtain relations"); } const Relation::List relations = relationQuery.result(); for (const Relation &relation : relations) { storageBackend()->notificationCollector()->relationRemoved(relation); } // Get all PIM items that are part of the relation SelectQueryBuilder itemsQuery; itemsQuery.setSubQueryMode(Query::Or); itemsQuery.addValueCondition(PimItem::idColumn(), Query::Equals, cmd.left()); itemsQuery.addValueCondition(PimItem::idColumn(), Query::Equals, cmd.right()); if (!itemsQuery.exec()) { throw failureResponse("Removing relation failed"); } const PimItem::List items = itemsQuery.result(); if (!items.isEmpty()) { storageBackend()->notificationCollector()->itemsRelationsChanged(items, Relation::List(), relations); } QueryBuilder qb(Relation::tableName(), QueryBuilder::Delete); qb.addValueCondition(Relation::leftIdFullColumnName(), Query::Equals, cmd.left()); qb.addValueCondition(Relation::rightIdFullColumnName(), Query::Equals, cmd.right()); if (relType.isValid()) { qb.addValueCondition(Relation::typeIdFullColumnName(), Query::Equals, relType.id()); } if (!qb.exec()) { return failureResponse("Failed to remove relations"); } return successResponse(); } diff --git a/src/server/handler/relationremove.h b/src/server/handler/relationremovehandler.h similarity index 82% rename from src/server/handler/relationremove.h rename to src/server/handler/relationremovehandler.h index 7baaa08c4..ecca6f873 100644 --- a/src/server/handler/relationremove.h +++ b/src/server/handler/relationremovehandler.h @@ -1,41 +1,41 @@ /* Copyright (c) 2014 Christian Mollekopf This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef AKONADI_RELATIONREMOVE_H -#define AKONADI_RELATIONREMOVE_H +#ifndef AKONADI_RELATIONREMOVEHANDLER_H_ +#define AKONADI_RELATIONREMOVEHANDLER_H_ #include "handler.h" namespace Akonadi { namespace Server { -class RelationRemove : public Handler +class RelationRemoveHandler: public Handler { public: - ~RelationRemove() override = default; + ~RelationRemoveHandler() override = default; bool parseStream() override; }; } // namespace Server } // namespace Akonadi -#endif // AKONADI_RELATIONREMOVE_H +#endif // AKONADI_RELATIONREMOVEHANDLER_H_ diff --git a/src/server/handler/resourceselect.cpp b/src/server/handler/resourceselecthandler.cpp similarity index 95% rename from src/server/handler/resourceselect.cpp rename to src/server/handler/resourceselecthandler.cpp index a7790fb9e..f28720dbf 100644 --- a/src/server/handler/resourceselect.cpp +++ b/src/server/handler/resourceselecthandler.cpp @@ -1,44 +1,44 @@ /* Copyright (c) 2009 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "resourceselect.h" +#include "resourceselecthandler.h" #include "connection.h" using namespace Akonadi; using namespace Akonadi::Server; -bool ResourceSelect::parseStream() +bool ResourceSelectHandler::parseStream() { const auto &cmd = Protocol::cmdCast(m_command); if (cmd.resourceId().isEmpty()) { connection()->context()->setResource(Resource()); return successResponse(); } const Resource res = Resource::retrieveByName(cmd.resourceId()); if (!res.isValid()) { return failureResponse(cmd.resourceId() % QStringLiteral(" is not a valid resource identifier")); } connection()->context()->setResource(res); return successResponse(); } diff --git a/src/server/handler/resourceselect.h b/src/server/handler/resourceselecthandler.h similarity index 88% rename from src/server/handler/resourceselect.h rename to src/server/handler/resourceselecthandler.h index 6c75de881..df3d153dd 100644 --- a/src/server/handler/resourceselect.h +++ b/src/server/handler/resourceselecthandler.h @@ -1,51 +1,51 @@ /* Copyright (c) 2009 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef AKONADI_RESOURCESELECT_H -#define AKONADI_RESOURCESELECT_H +#ifndef AKONADI_RESOURCESELECTHANDLER_H_ +#define AKONADI_RESOURCESELECTHANDLER_H_ #include "handler.h" namespace Akonadi { namespace Server { /** @ingroup akonadi_server_handler Handler for the resource selection command.

Semantics

Limits the scope of remote id based operations. Remote ids of collections are only guaranteed to be unique per resource, so this command should be issued before running any RID based collection commands. */ -class ResourceSelect : public Handler +class ResourceSelectHandler: public Handler { public: - ~ResourceSelect() override = default; + ~ResourceSelectHandler() override = default; bool parseStream() override; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler/searchpersistent.cpp b/src/server/handler/searchcreatehandler.cpp similarity index 97% rename from src/server/handler/searchpersistent.cpp rename to src/server/handler/searchcreatehandler.cpp index 2fd7c3d06..ac23fff47 100644 --- a/src/server/handler/searchpersistent.cpp +++ b/src/server/handler/searchcreatehandler.cpp @@ -1,88 +1,88 @@ /*************************************************************************** * Copyright (C) 2006 by Tobias Koenig * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * 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 Library 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 "searchpersistent.h" +#include "searchcreatehandler.h" #include "connection.h" #include "handlerhelper.h" #include "storage/datastore.h" #include "storage/entity.h" #include "storage/transaction.h" #include "search/searchmanager.h" #include "akonadiserver_debug.h" using namespace Akonadi; using namespace Akonadi::Server; -bool SearchPersistent::parseStream() +bool SearchCreateHandler::parseStream() { const auto &cmd = Protocol::cmdCast(m_command); if (cmd.name().isEmpty()) { return failureResponse("No name specified"); } if (cmd.query().isEmpty()) { return failureResponse("No query specified"); } DataStore *db = connection()->storageBackend(); Transaction transaction(db, QStringLiteral("SEARCH PERSISTENT")); QStringList queryAttributes; if (cmd.remote()) { queryAttributes << QStringLiteral(AKONADI_PARAM_REMOTE); } if (cmd.recursive()) { queryAttributes << QStringLiteral(AKONADI_PARAM_RECURSIVE); } QStringList queryCollections; QVector queryColIds = cmd.queryCollections(); std::sort(queryColIds.begin(), queryColIds.end()); queryCollections.reserve(queryColIds.size()); for (qint64 col : qAsConst(queryColIds)) { queryCollections.append(QString::number(col)); } Collection col; col.setQueryString(cmd.query()); col.setQueryAttributes(queryAttributes.join(QLatin1Char(' '))); col.setQueryCollections(queryCollections.join(QLatin1Char(' '))); col.setParentId(1); // search root col.setResourceId(1); // search resource col.setName(cmd.name()); col.setIsVirtual(true); const QStringList lstMimeTypes = cmd.mimeTypes(); if (!db->appendCollection(col, lstMimeTypes, {{"AccessRights", "luD"}})) { return failureResponse("Unable to create persistent search"); } if (!transaction.commit()) { return failureResponse("Unable to commit transaction"); } SearchManager::instance()->updateSearch(col); sendResponse(HandlerHelper::fetchCollectionsResponse(col)); return successResponse(); } diff --git a/src/server/handler/searchpersistent.h b/src/server/handler/searchcreatehandler.h similarity index 90% rename from src/server/handler/searchpersistent.h rename to src/server/handler/searchcreatehandler.h index 104d34a34..1b61088cb 100644 --- a/src/server/handler/searchpersistent.h +++ b/src/server/handler/searchcreatehandler.h @@ -1,46 +1,46 @@ /*************************************************************************** * Copyright (C) 2006 by Tobias Koenig * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * 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 Library 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 AKONADISEARCHPERSISTENT_H -#define AKONADISEARCHPERSISTENT_H +#ifndef AKONADI_SEARCHCREATEHANDLER_H_ +#define AKONADI_SEARCHCREATEHANDLER_H_ #include "handler.h" namespace Akonadi { namespace Server { /** @ingroup akonadi_server_handler Handler for the search_store search_delete commands. */ -class SearchPersistent : public Handler +class SearchCreateHandler: public Handler { public: - ~SearchPersistent() override = default; + ~SearchCreateHandler() override = default; bool parseStream() override; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler/search.cpp b/src/server/handler/searchhandler.cpp similarity index 93% rename from src/server/handler/search.cpp rename to src/server/handler/searchhandler.cpp index 0a6c36b53..b3c7a25a2 100644 --- a/src/server/handler/search.cpp +++ b/src/server/handler/searchhandler.cpp @@ -1,103 +1,103 @@ /*************************************************************************** * Copyright (C) 2009 by Tobias Koenig * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * 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 Library 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 "search.h" +#include "searchhandler.h" #include "connection.h" -#include "fetchhelper.h" +#include "itemfetchhelper.h" #include "handlerhelper.h" #include "searchhelper.h" #include "search/searchrequest.h" #include "search/searchmanager.h" #include "akonadiserver_search_debug.h" using namespace Akonadi; using namespace Akonadi::Server; -bool Search::parseStream() +bool SearchHandler::parseStream() { const auto &cmd = Protocol::cmdCast(m_command); if (cmd.query().isEmpty()) { return failureResponse("No query specified"); } QVector collectionIds; bool recursive = cmd.recursive(); if (cmd.collections().isEmpty() || cmd.collections() == QVector { 0ll }) { collectionIds << 0; recursive = true; } QVector collections = collectionIds; if (recursive) { collections += SearchHelper::matchSubcollectionsByMimeType(collectionIds, cmd.mimeTypes()); } qCDebug(AKONADISERVER_SEARCH_LOG) << "SEARCH:"; qCDebug(AKONADISERVER_SEARCH_LOG) << "\tQuery:" << cmd.query(); qCDebug(AKONADISERVER_SEARCH_LOG) << "\tMimeTypes:" << cmd.mimeTypes(); qCDebug(AKONADISERVER_SEARCH_LOG) << "\tCollections:" << collections; qCDebug(AKONADISERVER_SEARCH_LOG) << "\tRemote:" << cmd.remote(); qCDebug(AKONADISERVER_SEARCH_LOG) << "\tRecursive" << recursive; if (collections.isEmpty()) { return successResponse(); } mItemFetchScope = cmd.itemFetchScope(); mTagFetchScope = cmd.tagFetchScope(); SearchRequest request(connection()->sessionId()); request.setCollections(collections); request.setMimeTypes(cmd.mimeTypes()); request.setQuery(cmd.query()); request.setRemoteSearch(cmd.remote()); QObject::connect(&request, &SearchRequest::resultsAvailable, [this](const QSet &results) { processResults(results); }); request.exec(); //qCDebug(AKONADISERVER_SEARCH_LOG) << "\tResult:" << uids; qCDebug(AKONADISERVER_SEARCH_LOG) << "\tResult:" << mAllResults.count() << "matches"; return successResponse(); } -void Search::processResults(const QSet &results) +void SearchHandler::processResults(const QSet &results) { QSet newResults = results; newResults.subtract(mAllResults); mAllResults.unite(newResults); if (newResults.isEmpty()) { return; } ImapSet imapSet; imapSet.add(newResults); Scope scope; scope.setUidSet(imapSet); - FetchHelper fetchHelper(connection(), scope, mItemFetchScope, mTagFetchScope); + ItemFetchHelper fetchHelper(connection(), scope, mItemFetchScope, mTagFetchScope); fetchHelper.fetchItems(); } diff --git a/src/server/handler/search.h b/src/server/handler/searchhandler.h similarity index 92% rename from src/server/handler/search.h rename to src/server/handler/searchhandler.h index fc6655f42..e02a2ed20 100644 --- a/src/server/handler/search.h +++ b/src/server/handler/searchhandler.h @@ -1,55 +1,55 @@ /*************************************************************************** * Copyright (C) 2009 by Tobias Koenig * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * 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 Library 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 AKONADISEARCH_H -#define AKONADISEARCH_H +#ifndef AKONADI_SEARCHHANDLER_H_ +#define AKONADI_SEARCHHANDLER_H_ #include "handler.h" #include namespace Akonadi { namespace Server { /** @ingroup akonadi_server_handler Handler for the search commands. */ -class Search : public Handler +class SearchHandler : public Handler { public: - ~Search() override = default; + ~SearchHandler() override = default; bool parseStream() override; private: void processResults(const QSet &results); Protocol::ItemFetchScope mItemFetchScope; Protocol::TagFetchScope mTagFetchScope; QSet mAllResults; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler/searchresult.cpp b/src/server/handler/searchresulthandler.cpp similarity index 94% rename from src/server/handler/searchresult.cpp rename to src/server/handler/searchresulthandler.cpp index 496a177dc..d653ce399 100644 --- a/src/server/handler/searchresult.cpp +++ b/src/server/handler/searchresulthandler.cpp @@ -1,75 +1,75 @@ /* * Copyright (C) 2013 Daniel Vrátil * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ -#include "searchresult.h" +#include "searchresulthandler.h" #include "connection.h" #include "storage/querybuilder.h" #include "storage/itemqueryhelper.h" #include "search/searchtaskmanager.h" #include #include using namespace Akonadi; using namespace Akonadi::Server; -bool SearchResult::parseStream() +bool SearchResultHandler::parseStream() { const auto &cmd = Protocol::cmdCast(m_command); if (!checkScopeConstraints(cmd.result(), Scope::Uid | Scope::Rid)) { return fail(cmd.searchId(), QStringLiteral("Only UID or RID scopes are allowed in SEARECH_RESULT")); } QSet ids; if (cmd.result().scope() == Scope::Rid && !cmd.result().isEmpty()) { QueryBuilder qb(PimItem::tableName()); qb.addColumn(PimItem::idFullColumnName()); ItemQueryHelper::remoteIdToQuery(cmd.result().ridSet(), connection()->context(), qb); qb.addValueCondition(PimItem::collectionIdFullColumnName(), Query::Equals, cmd.collectionId()); if (!qb.exec()) { return fail(cmd.searchId(), QStringLiteral("Failed to convert RID to UID")); } QSqlQuery query = qb.query(); while (query.next()) { ids << query.value(0).toLongLong(); } query.finish(); } else if (cmd.result().scope() == Scope::Uid && !cmd.result().isEmpty()) { const ImapSet result = cmd.result().uidSet(); const ImapInterval::List lstInterval = result.intervals(); for (const ImapInterval &interval : lstInterval) { for (qint64 i = interval.begin(), end = interval.end(); i <= end; ++i) { ids.insert(i); } } } SearchTaskManager::instance()->pushResults(cmd.searchId(), ids, connection()); return successResponse(); } -bool SearchResult::fail(const QByteArray &searchId, const QString &error) +bool SearchResultHandler::fail(const QByteArray &searchId, const QString &error) { SearchTaskManager::instance()->pushResults(searchId, QSet(), connection()); return failureResponse(error); } diff --git a/src/server/handler/searchresult.h b/src/server/handler/searchresulthandler.h similarity index 84% rename from src/server/handler/searchresult.h rename to src/server/handler/searchresulthandler.h index 9a3023037..70195f262 100644 --- a/src/server/handler/searchresult.h +++ b/src/server/handler/searchresulthandler.h @@ -1,49 +1,49 @@ /* * Copyright (C) 2013 Daniel Vrátil * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ -#ifndef AKONADI_SEARCHRESULT_H -#define AKONADI_SEARCHRESULT_H +#ifndef AKONADI_SEARCHRESULTHANDLER_H_ +#define AKONADI_SEARCHRESULTHANDLER_H_ #include "handler.h" namespace Akonadi { namespace Server { /** @ingroup akonadi_server_handler Handler for the search_result command */ -class SearchResult : public Handler +class SearchResultHandler: public Handler { public: - ~SearchResult() override = default; + ~SearchResultHandler() override = default; bool parseStream() override; private: bool fail(const QByteArray &searchId, const QString &error); }; } // namespace Server } // namespace Akonadi -#endif // AKONADI_SEARCHRESULT_H +#endif // AKONADI_SEARCHRESULTHANDLER_H_ diff --git a/src/server/handler/tagappend.cpp b/src/server/handler/tagcreatehandler.cpp similarity index 98% rename from src/server/handler/tagappend.cpp rename to src/server/handler/tagcreatehandler.cpp index 972c21c1d..b01f1b254 100644 --- a/src/server/handler/tagappend.cpp +++ b/src/server/handler/tagcreatehandler.cpp @@ -1,144 +1,144 @@ /* Copyright (c) 2014 Daniel Vrátil This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "tagappend.h" +#include "tagcreatehandler.h" #include "tagfetchhelper.h" #include "connection.h" #include "storage/datastore.h" #include "storage/querybuilder.h" #include "storage/countquerybuilder.h" #include "storage/transaction.h" #include #include using namespace Akonadi; using namespace Akonadi::Server; -bool TagAppend::parseStream() +bool TagCreateHandler::parseStream() { const auto &cmd = Protocol::cmdCast(m_command); if (!cmd.remoteId().isEmpty() && !connection()->context()->resource().isValid()) { return failureResponse(QStringLiteral("Only resources can create tags with remote ID")); } Transaction trx(storageBackend(), QStringLiteral("TAGAPPEND")); TagType tagType; if (!cmd.type().isEmpty()) { const QString typeName = QString::fromUtf8(cmd.type()); tagType = TagType::retrieveByNameOrCreate(typeName); if (!tagType.isValid()) { return failureResponse(QStringLiteral("Unable to create tagtype '") % typeName % QStringLiteral("'")); } } qint64 tagId = -1; const QString gid = QString::fromUtf8(cmd.gid()); if (cmd.merge()) { QueryBuilder qb(Tag::tableName()); qb.addColumn(Tag::idColumn()); qb.addValueCondition(Tag::gidColumn(), Query::Equals, gid); if (!qb.exec()) { return failureResponse("Unable to list tags"); } if (qb.query().next()) { tagId = qb.query().value(0).toLongLong(); } qb.query().finish(); } if (tagId < 0) { Tag insertedTag; insertedTag.setGid(gid); if (cmd.parentId() >= 0) { insertedTag.setParentId(cmd.parentId()); } if (tagType.isValid()) { insertedTag.setTypeId(tagType.id()); } if (!insertedTag.insert(&tagId)) { return failureResponse("Failed to store tag"); } const Protocol::Attributes attrs = cmd.attributes(); for (auto iter = attrs.cbegin(), end = attrs.cend(); iter != end; ++iter) { TagAttribute attribute; attribute.setTagId(tagId); attribute.setType(iter.key()); attribute.setValue(iter.value()); if (!attribute.insert()) { return failureResponse("Failed to store tag attribute"); } } storageBackend()->notificationCollector()->tagAdded(insertedTag); } if (!cmd.remoteId().isEmpty()) { const qint64 resourceId = connection()->context()->resource().id(); CountQueryBuilder qb(TagRemoteIdResourceRelation::tableName()); qb.addValueCondition(TagRemoteIdResourceRelation::tagIdColumn(), Query::Equals, tagId); qb.addValueCondition(TagRemoteIdResourceRelation::resourceIdColumn(), Query::Equals, resourceId); if (!qb.exec()) { return failureResponse("Failed to query for existing TagRemoteIdResourceRelation entries"); } const bool exists = (qb.result() > 0); //If the relation is already existing simply update it (can happen if a resource simply creates the tag again while enabling merge) bool ret = false; if (exists) { //Simply using update() doesn't work since TagRemoteIdResourceRelation only takes the tagId for identification of the column QueryBuilder qb(TagRemoteIdResourceRelation::tableName(), QueryBuilder::Update); qb.addValueCondition(TagRemoteIdResourceRelation::tagIdColumn(), Query::Equals, tagId); qb.addValueCondition(TagRemoteIdResourceRelation::resourceIdColumn(), Query::Equals, resourceId); qb.setColumnValue(TagRemoteIdResourceRelation::remoteIdColumn(), QString::fromUtf8(cmd.remoteId())); ret = qb.exec(); } else { TagRemoteIdResourceRelation rel; rel.setTagId(tagId); rel.setResourceId(resourceId); rel.setRemoteId(QString::fromUtf8(cmd.remoteId())); ret = rel.insert(); } if (!ret) { return failureResponse("Failed to store tag remote ID"); } } trx.commit(); Scope scope; ImapSet set; set.add(QVector() << tagId); scope.setUidSet(set); Protocol::TagFetchScope fetchScope; fetchScope.setFetchRemoteID(true); fetchScope.setFetchAllAttributes(true); TagFetchHelper helper(connection(), scope, fetchScope); if (!helper.fetchTags()) { return failureResponse("Failed to fetch the new tag"); } return successResponse(); } diff --git a/src/server/handler/tagappend.h b/src/server/handler/tagcreatehandler.h similarity index 86% rename from src/server/handler/tagappend.h rename to src/server/handler/tagcreatehandler.h index e91454b0f..f4f82e97d 100644 --- a/src/server/handler/tagappend.h +++ b/src/server/handler/tagcreatehandler.h @@ -1,41 +1,41 @@ /* Copyright (c) 2014 Daniel Vrátil This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef AKONADI_TAGAPPEND_H -#define AKONADI_TAGAPPEND_H +#ifndef AKONADI_TAGCREATEHANDLER_H_ +#define AKONADI_TAGCREATEHANDLER_H_ #include "handler.h" namespace Akonadi { namespace Server { -class TagAppend : public Handler +class TagCreateHandler: public Handler { public: - ~TagAppend() override = default; + ~TagCreateHandler() override = default; bool parseStream() override; }; } // namespace Server } // namespace Akonadi #endif // AKONADI_TAGAPPEND_H diff --git a/src/server/handler/tagremove.cpp b/src/server/handler/tagdeletehandler.cpp similarity index 96% rename from src/server/handler/tagremove.cpp rename to src/server/handler/tagdeletehandler.cpp index 37f7c7298..b39193172 100644 --- a/src/server/handler/tagremove.cpp +++ b/src/server/handler/tagdeletehandler.cpp @@ -1,53 +1,53 @@ /* Copyright (c) 2014 Daniel Vrátil This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "tagremove.h" +#include "tagdeletehandler.h" #include "storage/selectquerybuilder.h" #include "storage/queryhelper.h" #include "storage/datastore.h" #include #include using namespace Akonadi; using namespace Akonadi::Server; -bool TagRemove::parseStream() +bool TagDeleteHandler::parseStream() { const auto &cmd = Protocol::cmdCast(m_command); if (!checkScopeConstraints(cmd.tag(), Scope::Uid)) { return failureResponse(QStringLiteral("Only UID-based TAGREMOVE is supported")); } SelectQueryBuilder tagQuery; QueryHelper::setToQuery(cmd.tag().uidSet(), Tag::idFullColumnName(), tagQuery); if (!tagQuery.exec()) { return failureResponse(QStringLiteral("Failed to obtain tags")); } const Tag::List tags = tagQuery.result(); if (!storageBackend()->removeTags(tags)) { return failureResponse(QStringLiteral("Failed to remove tags")); } return successResponse(); } diff --git a/src/server/handler/tagremove.h b/src/server/handler/tagdeletehandler.h similarity index 83% rename from src/server/handler/tagremove.h rename to src/server/handler/tagdeletehandler.h index 1a498be4b..9494017bd 100644 --- a/src/server/handler/tagremove.h +++ b/src/server/handler/tagdeletehandler.h @@ -1,41 +1,41 @@ /* Copyright (c) 2014 Daniel Vrátil This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef AKONADI_TAGREMOVE_H -#define AKONADI_TAGREMOVE_H +#ifndef AKONADI_TAGDELETEHANDLER_H_ +#define AKONADI_TAGDELETEHANDLER_H_ #include "handler.h" namespace Akonadi { namespace Server { -class TagRemove : public Handler +class TagDeleteHandler: public Handler { public: - ~TagRemove() override = default; + ~TagDeleteHandler() override = default; bool parseStream() override; }; } // namespace Server } // namespace Akonadi -#endif // AKONADI_TAGREMOVE_H +#endif // AKONADI_TAGDELETEHANDLER_H_ diff --git a/src/server/handler/tagfetch.cpp b/src/server/handler/tagfetchhandler.cpp similarity index 96% rename from src/server/handler/tagfetch.cpp rename to src/server/handler/tagfetchhandler.cpp index 7681ab641..5b393e756 100644 --- a/src/server/handler/tagfetch.cpp +++ b/src/server/handler/tagfetchhandler.cpp @@ -1,41 +1,41 @@ /*************************************************************************** * Copyright (C) 2014 by Daniel Vrátil * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * 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 Library 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 "tagfetch.h" +#include "tagfetchhandler.h" #include "connection.h" #include "tagfetchhelper.h" using namespace Akonadi; using namespace Akonadi::Server; -bool TagFetch::parseStream() +bool TagFetchHandler::parseStream() { const auto &cmd = Protocol::cmdCast(m_command); if (!checkScopeConstraints(cmd.scope(), Scope::Uid)) { return failureResponse("Only UID-based TAGFETCH is supported"); } TagFetchHelper helper(connection(), cmd.scope(), cmd.fetchScope()); if (!helper.fetchTags()) { return failureResponse("Failed to fetch tags"); } return successResponse(); } diff --git a/src/server/handler/tagfetch.h b/src/server/handler/tagfetchhandler.h similarity index 91% rename from src/server/handler/tagfetch.h rename to src/server/handler/tagfetchhandler.h index 8c367f82f..aa691b0c0 100644 --- a/src/server/handler/tagfetch.h +++ b/src/server/handler/tagfetchhandler.h @@ -1,46 +1,46 @@ /*************************************************************************** * Copyright (C) 2014 by Daniel Vrátil * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * 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 Library 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 AKONADIFETCHTAG_H -#define AKONADIFETCHTAG_H +#ifndef AKONADI_TAGFETCHHANDLER_H_ +#define AKONADI_TAGFETCHHANDLER_H_ #include "handler.h" namespace Akonadi { namespace Server { /** @ingroup akonadi_server_handler Handler for the FETCHTAG command. */ -class TagFetch : public Handler +class TagFetchHandler: public Handler { public: - ~TagFetch() override = default; + ~TagFetchHandler() override = default; bool parseStream() override; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler/tagstore.cpp b/src/server/handler/tagmodifyhandler.cpp similarity index 98% rename from src/server/handler/tagstore.cpp rename to src/server/handler/tagmodifyhandler.cpp index d5643ef91..2a78e208c 100644 --- a/src/server/handler/tagstore.cpp +++ b/src/server/handler/tagmodifyhandler.cpp @@ -1,162 +1,162 @@ /* Copyright (c) 2014 Daniel Vrátil This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "tagstore.h" +#include "tagmodifyhandler.h" #include "tagfetchhelper.h" #include "connection.h" #include "storage/datastore.h" #include "storage/querybuilder.h" #include using namespace Akonadi; using namespace Akonadi::Server; -bool TagStore::parseStream() +bool TagModifyHandler::parseStream() { const auto &cmd = Protocol::cmdCast(m_command); Tag changedTag = Tag::retrieveById(cmd.tagId()); if (!changedTag.isValid()) { return failureResponse("No such tag"); } QSet changes; // Retrieve all tag's attributes const TagAttribute::List attributes = TagAttribute::retrieveFiltered(TagAttribute::tagIdFullColumnName(), cmd.tagId()); QMap attributesMap; for (const TagAttribute &attribute : attributes) { attributesMap.insert(attribute.type(), attribute); } if (cmd.modifiedParts() & Protocol::ModifyTagCommand::ParentId) { if (cmd.parentId() != changedTag.parentId()) { changedTag.setParentId(cmd.parentId()); changes << AKONADI_PARAM_PARENT; } } if (cmd.modifiedParts() & Protocol::ModifyTagCommand::Type) { TagType type = TagType::retrieveById(changedTag.typeId()); const QString newTypeName = QString::fromUtf8(cmd.type()); if (newTypeName != type.name()) { const TagType newType = TagType::retrieveByNameOrCreate(newTypeName); if (!newType.isValid()) { return failureResponse("Failed to create new tag type"); } changedTag.setTagType(newType); changes << AKONADI_PARAM_MIMETYPE; } } bool tagRemoved = false; if (cmd.modifiedParts() & Protocol::ModifyTagCommand::RemoteId) { if (!connection()->context()->resource().isValid()) { return failureResponse("Only resources can change tag remote ID"); } //Simply using remove() doesn't work since we need two arguments QueryBuilder qb(TagRemoteIdResourceRelation::tableName(), QueryBuilder::Delete); qb.addValueCondition(TagRemoteIdResourceRelation::tagIdColumn(), Query::Equals, cmd.tagId()); qb.addValueCondition(TagRemoteIdResourceRelation::resourceIdColumn(), Query::Equals, connection()->context()->resource().id()); qb.exec(); if (!cmd.remoteId().isEmpty()) { TagRemoteIdResourceRelation remoteIdRelation; remoteIdRelation.setRemoteId(QString::fromUtf8(cmd.remoteId())); remoteIdRelation.setResourceId(connection()->context()->resource().id()); remoteIdRelation.setTag(changedTag); if (!remoteIdRelation.insert()) { return failureResponse("Failed to insert remotedid resource relation"); } } else { const int tagRidsCount = TagRemoteIdResourceRelation::count(TagRemoteIdResourceRelation::tagIdColumn(), changedTag.id()); // We just removed the last RID of the tag, which means that no other // resource owns this tag, so we have to remove it to simulate tag // removal if (tagRidsCount == 0) { if (!storageBackend()->removeTags(Tag::List() << changedTag)) { return failureResponse("Failed to remove tag"); } tagRemoved = true; } } // Do not notify about remoteid changes, otherwise we bounce back and forth // between resources recording it's change and updating the remote id. } if (cmd.modifiedParts() & Protocol::ModifyTagCommand::RemovedAttributes) { Q_FOREACH (const QByteArray &attrName, cmd.removedAttributes()) { TagAttribute attribute = attributesMap.value(attrName); TagAttribute::remove(attribute.id()); changes << attrName; } } if (cmd.modifiedParts() & Protocol::ModifyTagCommand::Attributes) { const QMap attrs = cmd.attributes(); for (auto iter = attrs.cbegin(), end = attrs.cend(); iter != end; ++iter) { if (attributesMap.contains(iter.key())) { TagAttribute attribute = attributesMap.value(iter.key()); attribute.setValue(iter.value()); if (!attribute.update()) { return failureResponse("Failed to update attribute"); } } else { TagAttribute attribute; attribute.setTagId(cmd.tagId()); attribute.setType(iter.key()); attribute.setValue(iter.value()); if (!attribute.insert()) { return failureResponse("Failed to insert attribute"); } } changes << iter.key(); } } if (!tagRemoved) { if (!changedTag.update()) { return failureResponse("Failed to store changes"); } if (!changes.isEmpty()) { storageBackend()->notificationCollector()->tagChanged(changedTag); } ImapSet set; set.add(QVector() << cmd.tagId()); Protocol::TagFetchScope fetchScope; fetchScope.setFetchRemoteID(true); fetchScope.setFetchAllAttributes(true); Scope scope; scope.setUidSet(set); TagFetchHelper helper(connection(), scope, fetchScope); if (!helper.fetchTags()) { return failureResponse("Failed to fetch response"); } } else { successResponse(); } return successResponse(); } diff --git a/src/server/handler/tagstore.h b/src/server/handler/tagmodifyhandler.h similarity index 83% rename from src/server/handler/tagstore.h rename to src/server/handler/tagmodifyhandler.h index 9c590f185..42ad74f26 100644 --- a/src/server/handler/tagstore.h +++ b/src/server/handler/tagmodifyhandler.h @@ -1,41 +1,41 @@ /* Copyright (c) 2014 Daniel Vrátil This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef AKONADI_TAGSTORE_H -#define AKONADI_TAGSTORE_H +#ifndef AKONADI_TAGMODIFYHANDLER_H_ +#define AKONADI_TAGMODIFYHANDLER_H_ #include "handler.h" namespace Akonadi { namespace Server { -class TagStore : public Handler +class TagModifyHandler: public Handler { public: - ~TagStore() override = default; + ~TagModifyHandler() override = default; bool parseStream() override; }; } // namespace Server } // namespace Akonadi -#endif // AKONADI_TAGSTORE_H +#endif // AKONADI_TAGMODIFYHANDLER_H_ diff --git a/src/server/handler/transaction.cpp b/src/server/handler/transactionhandler.cpp similarity index 98% rename from src/server/handler/transaction.cpp rename to src/server/handler/transactionhandler.cpp index 6736d81ac..bd6ca5106 100644 --- a/src/server/handler/transaction.cpp +++ b/src/server/handler/transactionhandler.cpp @@ -1,60 +1,60 @@ /* Copyright (c) 2006 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "transaction.h" +#include "transactionhandler.h" #include "connection.h" #include "storage/datastore.h" using namespace Akonadi; using namespace Akonadi::Server; bool TransactionHandler::parseStream() { const auto &cmd = Protocol::cmdCast(m_command); DataStore *store = connection()->storageBackend(); switch (cmd.mode()) { case Protocol::TransactionCommand::Invalid: return failureResponse("Invalid operation"); case Protocol::TransactionCommand::Begin: if (!store->beginTransaction(QStringLiteral("CLIENT TRANSACTION"))) { return failureResponse("Unable to begin transaction."); } break; case Protocol::TransactionCommand::Rollback: if (!store->inTransaction()) { return failureResponse("There is no transaction in progress."); } if (!store->rollbackTransaction()) { return failureResponse("Unable to roll back transaction."); } break; case Protocol::TransactionCommand::Commit: if (!store->inTransaction()) { return failureResponse("There is no transaction in progress."); } if (!store->commitTransaction()) { return failureResponse("Unable to commit transaction."); } break; } return successResponse(); } diff --git a/src/server/handler/transaction.h b/src/server/handler/transactionhandler.h similarity index 94% rename from src/server/handler/transaction.h rename to src/server/handler/transactionhandler.h index c4a5a487d..6a9a20120 100644 --- a/src/server/handler/transaction.h +++ b/src/server/handler/transactionhandler.h @@ -1,46 +1,46 @@ /* Copyright (c) 2006 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef AKONADI_TRANSACTION_HANDLER_H -#define AKONADI_TRANSACTION_HANDLER_H +#ifndef AKONADI_TRANSACTIONHANDLER_H_ +#define AKONADI_TRANSACTIONHANDLER_H_ #include "handler.h" namespace Akonadi { namespace Server { /** @ingroup akonadi_server_handler Handler for transaction commands (BEGIN, COMMIT, ROLLBACK). */ class TransactionHandler : public Handler { public: ~TransactionHandler() override = default; bool parseStream() override; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/notificationmanager.cpp b/src/server/notificationmanager.cpp index 2e9d3b17f..4b713835c 100644 --- a/src/server/notificationmanager.cpp +++ b/src/server/notificationmanager.cpp @@ -1,228 +1,227 @@ /* Copyright (c) 2006 - 2007 Volker Krause Copyright (c) 2010 Michael Jansen This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "notificationmanager.h" #include "notificationsubscriber.h" #include "storage/notificationcollector.h" #include "tracer.h" #include "akonadiserver_debug.h" #include "aggregatedfetchscope.h" #include "storage/collectionstatistics.h" -#include "handler/fetchhelper.h" #include "handlerhelper.h" #include #include #include #include #include #include #include #include using namespace Akonadi; using namespace Akonadi::Server; NotificationManager::NotificationManager() : AkThread(QStringLiteral("NotificationManager")) , mTimer(nullptr) , mNotifyThreadPool(nullptr) , mDebugNotifications(0) { } NotificationManager::~NotificationManager() { quitThread(); } void NotificationManager::init() { AkThread::init(); const QString serverConfigFile = StandardDirs::serverConfigFile(StandardDirs::ReadWrite); QSettings settings(serverConfigFile, QSettings::IniFormat); mTimer = new QTimer(this); mTimer->setInterval(settings.value(QStringLiteral("NotificationManager/Interval"), 50).toInt()); mTimer->setSingleShot(true); connect(mTimer, &QTimer::timeout, this, &NotificationManager::emitPendingNotifications); mNotifyThreadPool = new QThreadPool(this); mNotifyThreadPool->setMaxThreadCount(5); mCollectionFetchScope = new AggregatedCollectionFetchScope(); mItemFetchScope = new AggregatedItemFetchScope(); mTagFetchScope = new AggregatedTagFetchScope(); } void NotificationManager::quit() { mQuitting = true; if (mEventLoop) { mEventLoop->quit(); return; } mTimer->stop(); delete mTimer; mNotifyThreadPool->clear(); mNotifyThreadPool->waitForDone(); delete mNotifyThreadPool; qDeleteAll(mSubscribers); delete mCollectionFetchScope; delete mItemFetchScope; delete mTagFetchScope; AkThread::quit(); } void NotificationManager::registerConnection(quintptr socketDescriptor) { Q_ASSERT(thread() == QThread::currentThread()); NotificationSubscriber *subscriber = new NotificationSubscriber(this, socketDescriptor); qCInfo(AKONADISERVER_LOG) << "New notification connection (registered as" << subscriber << ")"; connect(subscriber, &NotificationSubscriber::notificationDebuggingChanged, this, [this](bool enabled) { if (enabled) { ++mDebugNotifications; } else { --mDebugNotifications; } Q_ASSERT(mDebugNotifications >= 0); Q_ASSERT(mDebugNotifications <= mSubscribers.count()); }); mSubscribers.push_back(subscriber); } void NotificationManager::forgetSubscriber(NotificationSubscriber *subscriber) { Q_ASSERT(QThread::currentThread() == thread()); mSubscribers.removeAll(subscriber); } void NotificationManager::slotNotify(const Protocol::ChangeNotificationList &msgs) { Q_ASSERT(QThread::currentThread() == thread()); for (const auto &msg : msgs) { switch (msg->type()) { case Protocol::Command::CollectionChangeNotification: Protocol::CollectionChangeNotification::appendAndCompress(mNotifications, msg); continue; case Protocol::Command::ItemChangeNotification: case Protocol::Command::TagChangeNotification: case Protocol::Command::RelationChangeNotification: case Protocol::Command::SubscriptionChangeNotification: case Protocol::Command::DebugChangeNotification: mNotifications.push_back(msg); continue; default: Q_ASSERT_X(false, "slotNotify", "Invalid notification type!"); continue; } } if (!mTimer->isActive()) { mTimer->start(); } } class NotifyRunnable : public QRunnable { public: explicit NotifyRunnable(NotificationSubscriber *subscriber, const Protocol::ChangeNotificationList ¬ifications) : mSubscriber(subscriber) , mNotifications(notifications) { } ~NotifyRunnable() { } void run() override { for (const auto &ntf : qAsConst(mNotifications)) { if (mSubscriber) { mSubscriber->notify(ntf); } else { break; } } } private: QPointer mSubscriber; Protocol::ChangeNotificationList mNotifications; }; void NotificationManager::emitPendingNotifications() { Q_ASSERT(QThread::currentThread() == thread()); if (mNotifications.isEmpty()) { return; } if (mDebugNotifications == 0) { for (NotificationSubscriber *subscriber : qAsConst(mSubscribers)) { if (subscriber) { mNotifyThreadPool->start(new NotifyRunnable(subscriber, mNotifications)); } } } else { // When debugging notification we have to use a non-threaded approach // so that we can work with return value of notify() for (const auto ¬ification : qAsConst(mNotifications)) { QVector listeners; for (NotificationSubscriber *subscriber : qAsConst(mSubscribers)) { if (subscriber && subscriber->notify(notification)) { listeners.push_back(subscriber->subscriber()); } } emitDebugNotification(notification, listeners); } } mNotifications.clear(); } void NotificationManager::emitDebugNotification(const Protocol::ChangeNotificationPtr &ntf, const QVector &listeners) { auto debugNtf = Protocol::DebugChangeNotificationPtr::create(); debugNtf->setNotification(ntf); debugNtf->setListeners(listeners); debugNtf->setTimestamp(QDateTime::currentMSecsSinceEpoch()); for (NotificationSubscriber *subscriber : qAsConst(mSubscribers)) { if (subscriber) { mNotifyThreadPool->start(new NotifyRunnable(subscriber, { debugNtf })); } } } diff --git a/src/server/storage/notificationcollector.cpp b/src/server/storage/notificationcollector.cpp index 341cc8faf..a32595e8c 100644 --- a/src/server/storage/notificationcollector.cpp +++ b/src/server/storage/notificationcollector.cpp @@ -1,624 +1,623 @@ /* Copyright (c) 2006 - 2007 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "notificationcollector.h" #include "storage/datastore.h" #include "storage/entity.h" #include "storage/collectionstatistics.h" #include "handlerhelper.h" #include "cachecleaner.h" #include "intervalcheck.h" #include "search/searchmanager.h" #include "akonadi.h" -#include "handler/search.h" #include "notificationmanager.h" #include "aggregatedfetchscope.h" #include "selectquerybuilder.h" -#include "handler/fetchhelper.h" +#include "handler/itemfetchhelper.h" #include "connection.h" #include "akonadiserver_debug.h" #include using namespace Akonadi; using namespace Akonadi::Server; NotificationCollector::NotificationCollector(DataStore *db) : mDb(db) { QObject::connect(db, &DataStore::transactionCommitted, [this]() { if (!mIgnoreTransactions) { dispatchNotifications(); } }); QObject::connect(db, &DataStore::transactionRolledBack, [this]() { if (!mIgnoreTransactions) { clear(); } }); } void NotificationCollector::itemAdded(const PimItem &item, bool seen, const Collection &collection, const QByteArray &resource) { SearchManager::instance()->scheduleSearchUpdate(); CollectionStatistics::self()->itemAdded(collection, item.size(), seen); itemNotification(Protocol::ItemChangeNotification::Add, item, collection, Collection(), resource); } void NotificationCollector::itemChanged(const PimItem &item, const QSet &changedParts, const Collection &collection, const QByteArray &resource) { SearchManager::instance()->scheduleSearchUpdate(); itemNotification(Protocol::ItemChangeNotification::Modify, item, collection, Collection(), resource, changedParts); } void NotificationCollector::itemsFlagsChanged(const PimItem::List &items, const QSet &addedFlags, const QSet &removedFlags, const Collection &collection, const QByteArray &resource) { int seenCount = (addedFlags.contains(AKONADI_FLAG_SEEN) || addedFlags.contains(AKONADI_FLAG_IGNORED) ? items.count() : 0); seenCount -= (removedFlags.contains(AKONADI_FLAG_SEEN) || removedFlags.contains(AKONADI_FLAG_IGNORED) ? items.count() : 0); CollectionStatistics::self()->itemsSeenChanged(collection, seenCount); itemNotification(Protocol::ItemChangeNotification::ModifyFlags, items, collection, Collection(), resource, QSet(), addedFlags, removedFlags); } void NotificationCollector::itemsTagsChanged(const PimItem::List &items, const QSet &addedTags, const QSet &removedTags, const Collection &collection, const QByteArray &resource) { itemNotification(Protocol::ItemChangeNotification::ModifyTags, items, collection, Collection(), resource, QSet(), QSet(), QSet(), addedTags, removedTags); } void NotificationCollector::itemsRelationsChanged(const PimItem::List &items, const Relation::List &addedRelations, const Relation::List &removedRelations, const Collection &collection, const QByteArray &resource) { itemNotification(Protocol::ItemChangeNotification::ModifyRelations, items, collection, Collection(), resource, QSet(), QSet(), QSet(), QSet(), QSet(), addedRelations, removedRelations); } void NotificationCollector::itemsMoved(const PimItem::List &items, const Collection &collectionSrc, const Collection &collectionDest, const QByteArray &sourceResource) { SearchManager::instance()->scheduleSearchUpdate(); itemNotification(Protocol::ItemChangeNotification::Move, items, collectionSrc, collectionDest, sourceResource); } void NotificationCollector::itemsRemoved(const PimItem::List &items, const Collection &collection, const QByteArray &resource) { itemNotification(Protocol::ItemChangeNotification::Remove, items, collection, Collection(), resource); } void NotificationCollector::itemsLinked(const PimItem::List &items, const Collection &collection) { itemNotification(Protocol::ItemChangeNotification::Link, items, collection, Collection(), QByteArray()); } void NotificationCollector::itemsUnlinked(const PimItem::List &items, const Collection &collection) { itemNotification(Protocol::ItemChangeNotification::Unlink, items, collection, Collection(), QByteArray()); } void NotificationCollector::collectionAdded(const Collection &collection, const QByteArray &resource) { if (auto cleaner = AkonadiServer::instance()->cacheCleaner()) { cleaner->collectionAdded(collection.id()); } if (auto checker = AkonadiServer::instance()->intervalChecker()) { checker->collectionAdded(collection.id()); } collectionNotification(Protocol::CollectionChangeNotification::Add, collection, collection.parentId(), -1, resource); } void NotificationCollector::collectionChanged(const Collection &collection, const QList &changes, const QByteArray &resource) { if (auto cleaner = AkonadiServer::instance()->cacheCleaner()) { cleaner->collectionChanged(collection.id()); } if (auto checker = AkonadiServer::instance()->intervalChecker()) { checker->collectionChanged(collection.id()); } if (changes.contains(AKONADI_PARAM_ENABLED) || changes.contains(AKONADI_PARAM_REFERENCED)) { CollectionStatistics::self()->invalidateCollection(collection); } collectionNotification(Protocol::CollectionChangeNotification::Modify, collection, collection.parentId(), -1, resource, changes.toSet()); } void NotificationCollector::collectionMoved(const Collection &collection, const Collection &source, const QByteArray &resource, const QByteArray &destResource) { if (auto cleaner = AkonadiServer::instance()->cacheCleaner()) { cleaner->collectionChanged(collection.id()); } if (auto checker = AkonadiServer::instance()->intervalChecker()) { checker->collectionChanged(collection.id()); } collectionNotification(Protocol::CollectionChangeNotification::Move, collection, source.id(), collection.parentId(), resource, QSet(), destResource); } void NotificationCollector::collectionRemoved(const Collection &collection, const QByteArray &resource) { if (auto cleaner = AkonadiServer::instance()->cacheCleaner()) { cleaner->collectionRemoved(collection.id()); } if (auto checker = AkonadiServer::instance()->intervalChecker()) { checker->collectionRemoved(collection.id()); } CollectionStatistics::self()->invalidateCollection(collection); collectionNotification(Protocol::CollectionChangeNotification::Remove, collection, collection.parentId(), -1, resource); } void NotificationCollector::collectionSubscribed(const Collection &collection, const QByteArray &resource) { if (auto cleaner = AkonadiServer::instance()->cacheCleaner()) { cleaner->collectionAdded(collection.id()); } if (auto checker = AkonadiServer::instance()->intervalChecker()) { checker->collectionAdded(collection.id()); } collectionNotification(Protocol::CollectionChangeNotification::Subscribe, collection, collection.parentId(), -1, resource, QSet()); } void NotificationCollector::collectionUnsubscribed(const Collection &collection, const QByteArray &resource) { if (auto cleaner = AkonadiServer::instance()->cacheCleaner()) { cleaner->collectionRemoved(collection.id()); } if (auto checker = AkonadiServer::instance()->intervalChecker()) { checker->collectionRemoved(collection.id()); } CollectionStatistics::self()->invalidateCollection(collection); collectionNotification(Protocol::CollectionChangeNotification::Unsubscribe, collection, collection.parentId(), -1, resource, QSet()); } void NotificationCollector::tagAdded(const Tag &tag) { tagNotification(Protocol::TagChangeNotification::Add, tag); } void NotificationCollector::tagChanged(const Tag &tag) { tagNotification(Protocol::TagChangeNotification::Modify, tag); } void NotificationCollector::tagRemoved(const Tag &tag, const QByteArray &resource, const QString &remoteId) { tagNotification(Protocol::TagChangeNotification::Remove, tag, resource, remoteId); } void NotificationCollector::relationAdded(const Relation &relation) { relationNotification(Protocol::RelationChangeNotification::Add, relation); } void NotificationCollector::relationRemoved(const Relation &relation) { relationNotification(Protocol::RelationChangeNotification::Remove, relation); } void NotificationCollector::clear() { mNotifications.clear(); } void NotificationCollector::setConnection(Connection *connection) { mConnection = connection; } void NotificationCollector::itemNotification(Protocol::ItemChangeNotification::Operation op, const PimItem &item, const Collection &collection, const Collection &collectionDest, const QByteArray &resource, const QSet &parts) { PimItem::List items; items << item; itemNotification(op, items, collection, collectionDest, resource, parts); } void NotificationCollector::itemNotification(Protocol::ItemChangeNotification::Operation op, const PimItem::List &items, const Collection &collection, const Collection &collectionDest, const QByteArray &resource, const QSet &parts, const QSet &addedFlags, const QSet &removedFlags, const QSet &addedTags, const QSet &removedTags, const Relation::List &addedRelations, const Relation::List &removedRelations) { QMap > vCollections; if ((op == Protocol::ItemChangeNotification::Modify) || (op == Protocol::ItemChangeNotification::ModifyFlags) || (op == Protocol::ItemChangeNotification::ModifyTags) || (op == Protocol::ItemChangeNotification::ModifyRelations)) { vCollections = DataStore::self()->virtualCollections(items); } auto msg = Protocol::ItemChangeNotificationPtr::create(); if (mConnection) { msg->setSessionId(mConnection->sessionId()); } msg->setOperation(op); msg->setItemParts(parts); msg->setAddedFlags(addedFlags); msg->setRemovedFlags(removedFlags); msg->setAddedTags(addedTags); msg->setRemovedTags(removedTags); if (!addedRelations.isEmpty()) { QSet rels; Q_FOREACH (const Relation &rel, addedRelations) { rels.insert(Protocol::ItemChangeNotification::Relation(rel.leftId(), rel.rightId(), rel.relationType().name())); } msg->setAddedRelations(rels); } if (!removedRelations.isEmpty()) { QSet rels; Q_FOREACH (const Relation &rel, removedRelations) { rels.insert(Protocol::ItemChangeNotification::Relation(rel.leftId(), rel.rightId(), rel.relationType().name())); } msg->setRemovedRelations(rels); } if (collectionDest.isValid()) { QByteArray destResourceName; destResourceName = collectionDest.resource().name().toLatin1(); msg->setDestinationResource(destResourceName); } msg->setParentDestCollection(collectionDest.id()); QVector ntfItems; Q_FOREACH (const PimItem &item, items) { Protocol::FetchItemsResponse i; i.setId(item.id()); i.setRemoteId(item.remoteId()); i.setRemoteRevision(item.remoteRevision()); i.setMimeType(item.mimeType().name()); ntfItems.push_back(std::move(i)); } /* Notify all virtual collections the items are linked to. */ QHash virtItems; for (const auto &ntfItem : ntfItems) { virtItems.insert(ntfItem.id(), std::move(ntfItem)); } auto iter = vCollections.constBegin(), endIter = vCollections.constEnd(); for (; iter != endIter; ++iter) { auto copy = Protocol::ItemChangeNotificationPtr::create(*msg); QVector items; items.reserve(iter->size()); for (const auto &item : qAsConst(*iter)) { items.append(virtItems.value(item.id())); } copy->setItems(items); copy->setParentCollection(iter.key()); copy->setResource(resource); CollectionStatistics::self()->invalidateCollection(Collection::retrieveById(iter.key())); dispatchNotification(copy); } msg->setItems(ntfItems); Collection col; if (!collection.isValid()) { msg->setParentCollection(items.first().collection().id()); col = items.first().collection(); } else { msg->setParentCollection(collection.id()); col = collection; } QByteArray res = resource; if (res.isEmpty()) { if (col.resourceId() <= 0) { col = Collection::retrieveById(col.id()); } res = col.resource().name().toLatin1(); } msg->setResource(res); // Add and ModifyFlags are handled incrementally // (see itemAdded() and itemsFlagsChanged()) if (msg->operation() != Protocol::ItemChangeNotification::Add && msg->operation() != Protocol::ItemChangeNotification::ModifyFlags) { CollectionStatistics::self()->invalidateCollection(col); } dispatchNotification(msg); } void NotificationCollector::collectionNotification(Protocol::CollectionChangeNotification::Operation op, const Collection &collection, Collection::Id source, Collection::Id destination, const QByteArray &resource, const QSet &changes, const QByteArray &destResource) { auto msg = Protocol::CollectionChangeNotificationPtr::create(); msg->setOperation(op); if (mConnection) { msg->setSessionId(mConnection->sessionId()); } msg->setParentCollection(source); msg->setParentDestCollection(destination); msg->setDestinationResource(destResource); msg->setChangedParts(changes); auto msgCollection = HandlerHelper::fetchCollectionsResponse(collection); if (auto mgr = AkonadiServer::instance()->notificationManager()) { auto fetchScope = mgr->collectionFetchScope(); // Make sure we have all the data if (!fetchScope->fetchIdOnly() && msgCollection.name().isEmpty()) { const auto col = Collection::retrieveById(msgCollection.id()); const auto mts = col.mimeTypes(); QStringList mimeTypes; mimeTypes.reserve(mts.size()); for (const auto &mt : mts) { mimeTypes.push_back(mt.name()); } msgCollection = HandlerHelper::fetchCollectionsResponse(col, {}, false, 0, {}, {}, false, mimeTypes); } // Get up-to-date statistics if (fetchScope->fetchStatistics()) { Collection col; col.setId(msgCollection.id()); const auto stats = CollectionStatistics::self()->statistics(col); msgCollection.setStatistics(Protocol::FetchCollectionStatsResponse(stats.count, stats.count - stats.read, stats.size)); } // Get attributes const auto requestedAttrs = fetchScope->attributes(); auto msgColAttrs = msgCollection.attributes(); // TODO: This assumes that we have either none or all attributes in msgCollection if (msgColAttrs.isEmpty() && !requestedAttrs.isEmpty()) { SelectQueryBuilder qb; qb.addColumn(CollectionAttribute::typeFullColumnName()); qb.addColumn(CollectionAttribute::valueFullColumnName()); qb.addValueCondition(CollectionAttribute::collectionIdFullColumnName(), Query::Equals, msgCollection.id()); Query::Condition cond(Query::Or); for (const auto &attr : requestedAttrs) { cond.addValueCondition(CollectionAttribute::typeFullColumnName(), Query::Equals, attr); } qb.addCondition(cond); if (!qb.exec()) { qCWarning(AKONADISERVER_LOG) << "NotificationCollector failed to query attributes for Collection" << collection.name() << "(ID" << collection.id() << ")"; } const auto attrs = qb.result(); for (const auto &attr : attrs) { msgColAttrs.insert(attr.type(), attr.value()); } msgCollection.setAttributes(msgColAttrs); } } msg->setCollection(std::move(msgCollection)); if (!collection.enabled()) { msg->addMetadata("DISABLED"); } QByteArray res = resource; if (res.isEmpty()) { res = collection.resource().name().toLatin1(); } msg->setResource(res); dispatchNotification(msg); } void NotificationCollector::tagNotification(Protocol::TagChangeNotification::Operation op, const Tag &tag, const QByteArray &resource, const QString &remoteId) { auto msg = Protocol::TagChangeNotificationPtr::create(); msg->setOperation(op); if (mConnection) { msg->setSessionId(mConnection->sessionId()); } msg->setResource(resource); Protocol::FetchTagsResponse msgTag; msgTag.setId(tag.id()); msgTag.setRemoteId(remoteId.toUtf8()); if (auto mgr = AkonadiServer::instance()->notificationManager()) { auto fetchScope = mgr->tagFetchScope(); if (!fetchScope->fetchIdOnly() && msgTag.gid().isEmpty()) { msgTag = HandlerHelper::fetchTagsResponse(Tag::retrieveById(msgTag.id()), fetchScope->toFetchScope(), mConnection); } const auto requestedAttrs = fetchScope->attributes(); auto msgTagAttrs = msgTag.attributes(); if (msgTagAttrs.isEmpty() && !requestedAttrs.isEmpty()) { SelectQueryBuilder qb; qb.addColumn(TagAttribute::typeFullColumnName()); qb.addColumn(TagAttribute::valueFullColumnName()); qb.addValueCondition(TagAttribute::tagIdFullColumnName(), Query::Equals, msgTag.id()); Query::Condition cond(Query::Or); for (const auto &attr : requestedAttrs) { cond.addValueCondition(TagAttribute::typeFullColumnName(), Query::Equals, attr); } qb.addCondition(cond); if (!qb.exec()) { qCWarning(AKONADISERVER_LOG) << "NotificationCollection failed to query attributes for Tag" << tag.id(); } const auto attrs = qb.result(); for (const auto &attr : attrs) { msgTagAttrs.insert(attr.type(), attr.value()); } msgTag.setAttributes(msgTagAttrs); } } msg->setTag(std::move(msgTag)); dispatchNotification(msg); } void NotificationCollector::relationNotification(Protocol::RelationChangeNotification::Operation op, const Relation &relation) { auto msg = Protocol::RelationChangeNotificationPtr::create(); msg->setOperation(op); if (mConnection) { msg->setSessionId(mConnection->sessionId()); } msg->setRelation(HandlerHelper::fetchRelationsResponse(relation)); dispatchNotification(msg); } void NotificationCollector::completeNotification(const Protocol::ChangeNotificationPtr &changeMsg) { if (changeMsg->type() == Protocol::Command::ItemChangeNotification) { const auto msg = changeMsg.staticCast(); const auto mgr = AkonadiServer::instance()->notificationManager(); if (mgr && msg->operation() != Protocol::ItemChangeNotification::Remove) { if (mDb->inTransaction()) { qCWarning(AKONADISERVER_LOG) << "NotificationCollector requested FetchHelper from within a transaction." << "Aborting since this would deadlock!"; return; } auto fetchScope = mgr->itemFetchScope(); // NOTE: Checking and retrieving missing elements for each Item manually // here would require a complex code (and I'm too lazy), so instead we simply // feed the Items to FetchHelper and retrieve them all with the setup from // the aggregated fetch scope. The worst case is that we re-fetch everything // we already have, but that's stil better than the pre-ntf-payload situation QVector ids; const auto items = msg->items(); ids.reserve(items.size()); bool allHaveRID = true; for (const auto &item : items) { ids.push_back(item.id()); allHaveRID &= !item.remoteId().isEmpty(); } // FetchHelper may trigger ItemRetriever, which needs RemoteID. If we // dont have one (maybe because the Resource has not stored it yet, // we emit a notification without it and leave it up to the Monitor // to retrieve the Item on demand - we should have a RID stored in // Akonadi by then. if (mConnection && (allHaveRID || msg->operation() != Protocol::ItemChangeNotification::Add)) { // Prevent transactions inside FetchHelper to recursively call our slot QScopedValueRollback ignoreTransactions(mIgnoreTransactions); mIgnoreTransactions = true; CommandContext context; auto itemFetchScope = fetchScope->toFetchScope(); auto tagFetchScope = mgr->tagFetchScope()->toFetchScope(); itemFetchScope.setFetch(Protocol::ItemFetchScope::CacheOnly); - FetchHelper helper(mConnection, &context, Scope(ids), itemFetchScope, tagFetchScope); + ItemFetchHelper helper(mConnection, &context, Scope(ids), itemFetchScope, tagFetchScope); // The Item was just changed, which means the atime was // updated, no need to do it again a couple milliseconds later. helper.disableATimeUpdates(); QVector fetchedItems; auto callback = [&fetchedItems](Protocol::FetchItemsResponse &&cmd) { fetchedItems.push_back(std::move(cmd)); }; if (helper.fetchItems(std::move(callback))) { msg->setItems(fetchedItems); } else { qCWarning(AKONADISERVER_LOG) << "NotificationCollector railed to retrieve Items for notification!"; } } else { QVector fetchedItems; for (const auto &item : items) { Protocol::FetchItemsResponse resp; resp.setId(item.id()); resp.setRevision(item.revision()); resp.setMimeType(item.mimeType()); resp.setParentId(item.parentId()); resp.setGid(item.gid()); resp.setSize(item.size()); resp.setMTime(item.mTime()); resp.setFlags(item.flags()); fetchedItems.push_back(std::move(resp)); } msg->setItems(fetchedItems); msg->setMustRetrieve(true); } } } } void NotificationCollector::dispatchNotification(const Protocol::ChangeNotificationPtr &msg) { if (!mDb || mDb->inTransaction()) { if (msg->type() == Protocol::Command::CollectionChangeNotification) { Protocol::CollectionChangeNotification::appendAndCompress(mNotifications, msg); } else { mNotifications.append(msg); } } else { completeNotification(msg); notify({msg}); } } void NotificationCollector::dispatchNotifications() { if (!mNotifications.isEmpty()) { for (auto &ntf : mNotifications) { completeNotification(ntf); } notify(std::move(mNotifications)); clear(); } } void NotificationCollector::notify(Protocol::ChangeNotificationList msgs) { if (auto mgr = AkonadiServer::instance()->notificationManager()) { QMetaObject::invokeMethod(mgr, "slotNotify", Qt::QueuedConnection, Q_ARG(Akonadi::Protocol::ChangeNotificationList, msgs)); } }