diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,11 +1,13 @@ cmake_minimum_required(VERSION 3.0) -set(PIM_VERSION "5.7.3") +set(PIM_VERSION "5.9.1") project(Akonadi VERSION ${PIM_VERSION}) +set(CMAKE_CXX_STANDARD 14) + # ECM setup -set(KF5_VERSION "5.39.0") +set(KF5_VERSION "5.47.0") find_package(ECM ${KF5_VERSION} CONFIG REQUIRED) @@ -25,11 +27,12 @@ include(CheckSymbolExists) include(ECMCoverageOption) include(KDEPackageAppTemplates) +include(ECMMarkNonGuiExecutable) include(AkonadiMacros) -set(QT_REQUIRED_VERSION "5.8.0") +set(QT_REQUIRED_VERSION "5.9.0") set(AKONADI_VERSION ${PIM_VERSION}) ecm_setup_version(PROJECT @@ -82,8 +85,11 @@ if(BUILD_TESTING) set(AKONADI_TESTS_EXPORT AKONADICORE_EXPORT) + set(AKONADIWIDGET_TESTS_EXPORT AKONADIWIDGETS_EXPORT) + add_definitions(-DBUILD_TESTING) endif() configure_file(akonaditests_export.h.in "${CMAKE_CURRENT_BINARY_DIR}/akonaditests_export.h") +configure_file(akonadiwidgetstests_export.h.in "${CMAKE_CURRENT_BINARY_DIR}/akonadiwidgetstests_export.h") # Make sure the KF5Akonadi_DATA_DIR is absolute before passing it to KF5AkonadiConfig.cmake.in # otherwise build fails either on OSX CI, or for normal users @@ -103,7 +109,7 @@ set(BUILD_TOOLS TRUE) endif() -set(SMI_MIN_VERSION "1.0") +set(SMI_MIN_VERSION "1.3") find_package(SharedMimeInfo ${SMI_MIN_VERSION} REQUIRED) find_program(XSLTPROC_EXECUTABLE xsltproc) diff --git a/KF5AkonadiMacros.cmake b/KF5AkonadiMacros.cmake --- a/KF5AkonadiMacros.cmake +++ b/KF5AkonadiMacros.cmake @@ -69,12 +69,12 @@ endfunction() find_program(MYSQLD_EXECUTABLE mysqld /usr/sbin /usr/local/sbin /usr/libexec /usr/local/libexec /opt/mysql/libexec /usr/mysql/bin) - if (MYSQLD_EXECUTABLE) + if (MYSQLD_EXECUTABLE AND NOT WIN32) _defineTest(${_name} "MYSQL" ${CONFIG_BACKENDS}) endif() find_program(POSTGRES_EXECUTABLE postgres) - if (POSTGRES_EXECUTABLE) + if (POSTGRES_EXECUTABLE AND NOT WIN32) _defineTest(${_name} "PGSQL" ${CONFIG_BACKENDS}) endif() @@ -95,3 +95,34 @@ LINK_LIBRARIES "${link_libraries}" ) endfunction() + +function(kcfg_generate_dbus_interface _kcfg _name) + if (NOT XSLTPROC_EXECUTABLE) + message(FATAL_ERROR "xsltproc executable not found but needed by KCFG_GENERATE_DBUS_INTERFACE()") + endif() + + # When using this macro inside Akonadi, we need to refer to the file in the + # repo + if (Akonadi_SOURCE_DIR) + set(xsl_path ${Akonadi_SOURCE_DIR}/src/core/kcfg2dbus.xsl) + else() + set(xsl_path ${KF5Akonadi_DATA_DIR}/kcfg2dbus.xsl) + endif() + file(RELATIVE_PATH xsl_relpath ${CMAKE_CURRENT_BINARY_DIR} ${xsl_path}) + if (IS_ABSOLUTE ${_kcfg}) + file(RELATIVE_PATH kcfg_relpath ${CMAKE_CURRENT_BINARY_DIR} ${_kcfg}) + else() + file(RELATIVE_PATH kcfg_relpath ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/${_kcfg}) + endif() + add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${_name}.xml + COMMAND ${XSLTPROC_EXECUTABLE} + --output ${_name}.xml + --stringparam interfaceName ${_name} + ${xsl_relpath} + ${kcfg_relpath} + DEPENDS + ${xsl_path} + ${_kcfg} + ) +endfunction() diff --git a/akonadi.categories b/akonadi.categories --- a/akonadi.categories +++ b/akonadi.categories @@ -1,5 +1,6 @@ org.kde.pim.akonadiagentserver akonadi (Akonadi Agent Server) org.kde.pim.akonadiserver akonadi (Akonadi Server) +org.kde.pim.akonadiserver.search akonadi (Akonadi Server Search Functionality) org.kde.pim.akonadiagentbase akonadi (Akonadi AgentBase Library) org.kde.pim.akonadiwidgets akonadi (Akonadi Widget Library) org.kde.pim.akonadiprivate akonadi (Akonadi Private Library) diff --git a/akonadiwidgetstests_export.h.in b/akonadiwidgetstests_export.h.in new file mode 100644 --- /dev/null +++ b/akonadiwidgetstests_export.h.in @@ -0,0 +1,2 @@ +#include "akonadiwidgets_export.h" +#define AKONADIWIDGET_TESTS_EXPORT @AKONADIWIDGET_TESTS_EXPORT@ diff --git a/autotests/agentbase2/CMakeLists.txt b/autotests/agentbase2/CMakeLists.txt deleted file mode 100644 --- a/autotests/agentbase2/CMakeLists.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/autotests/akonadicontrol/CMakeLists.txt b/autotests/akonadicontrol/CMakeLists.txt --- a/autotests/akonadicontrol/CMakeLists.txt +++ b/autotests/akonadicontrol/CMakeLists.txt @@ -10,7 +10,7 @@ ) get_filename_component(_name ${_source} NAME_WE) add_executable(${_name} ${_test}) - add_test(AkonadiControl-${_name} ${_name}) + add_test(NAME AkonadiControl-${_name} COMMAND ${_name}) add_dependencies(${_name} akonadi_control) if (ENABLE_ASAN) set_tests_properties(AkonadiControl-${_name} PROPERTIES diff --git a/autotests/libs/CMakeLists.txt b/autotests/libs/CMakeLists.txt --- a/autotests/libs/CMakeLists.txt +++ b/autotests/libs/CMakeLists.txt @@ -1,4 +1,4 @@ -set(QT_REQUIRED_VERSION "5.8.0") +set(QT_REQUIRED_VERSION "5.9.0") find_package(Qt5 ${QT_REQUIRED_VERSION} CONFIG REQUIRED Test DBus) include(ECMAddTests) @@ -86,7 +86,7 @@ firstrunner.cpp ${CMAKE_BINARY_DIR}/src/core/akonadicore_debug.cpp ) -target_link_libraries( akonadi-firstrun Qt5::Test Qt5::Core KF5::AkonadiCore KF5::DBusAddons KF5::ConfigCore Qt5::Widgets) +target_link_libraries( akonadi-firstrun Qt5::Test Qt5::Core KF5::AkonadiCore KF5::AkonadiPrivate KF5::DBusAddons KF5::ConfigCore Qt5::Widgets) # qtestlib unit tests add_akonadi_test(imapparsertest.cpp) @@ -105,6 +105,7 @@ add_akonadi_test(newmailnotifierattributetest.cpp) add_akonadi_test(pop3resourceattributetest.cpp) add_akonadi_test_widgets(actionstatemanagertest.cpp) +add_akonadi_test_widgets(conflictresolvedialogtest.cpp) add_akonadi_test(tagmodeltest.cpp) add_akonadi_test(statisticsproxymodeltest.cpp) @@ -142,6 +143,7 @@ add_akonadi_isolated_test(SOURCE transactiontest.cpp) add_akonadi_isolated_test(SOURCE itemcopytest.cpp) add_akonadi_isolated_test(SOURCE itemmovetest.cpp) +add_akonadi_isolated_test(SOURCE collectioncreatetest.cpp) add_akonadi_isolated_test(SOURCE collectioncopytest.cpp) add_akonadi_isolated_test(SOURCE collectionmovetest.cpp) add_akonadi_isolated_test( @@ -174,7 +176,7 @@ add_akonadi_isolated_test( SOURCE itemsearchjobtest.cpp ADDITIONAL_SOURCES testsearchplugin/testsearchplugin.cpp) -add_akonadi_isolated_test(SOURCE tagtest.cpp) +add_akonadi_isolated_test(SOURCE tagtest.cpp ADDITIONAL_SOURCES ${CMAKE_BINARY_DIR}/src/core/akonadicore_debug.cpp) add_akonadi_isolated_test(SOURCE tagsynctest.cpp) add_akonadi_isolated_test(SOURCE relationtest.cpp) add_akonadi_isolated_test(SOURCE etmpopulationtest.cpp) diff --git a/autotests/libs/actionstatemanagertest.cpp b/autotests/libs/actionstatemanagertest.cpp --- a/autotests/libs/actionstatemanagertest.cpp +++ b/autotests/libs/actionstatemanagertest.cpp @@ -567,16 +567,22 @@ QFETCH(StateMap, stateMap); UnitActionStateManager manager(this); - manager.updateState(collections, Item::List()); + Collection::List favoriteCollections; + if (collections.contains(folderCollectionThree)) + favoriteCollections << folderCollectionThree; + manager.updateState(collections, favoriteCollections, Item::List()); QCOMPARE(stateMap.count(), mStateMap.count()); QHashIterator it(stateMap); while (it.hasNext()) { it.next(); //qDebug() << it.key(); QVERIFY(mStateMap.contains(it.key())); - QCOMPARE(it.value(), mStateMap.value(it.key())); + const bool expected = mStateMap.value(it.key()); + if (it.value() != expected) + qWarning() << "Wrong state for" << it.key(); + QCOMPARE(it.value(), expected); } } diff --git a/autotests/libs/cachepolicytest.h b/autotests/libs/cachepolicytest.h --- a/autotests/libs/cachepolicytest.h +++ b/autotests/libs/cachepolicytest.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2017 Laurent Montel + Copyright (C) 2017-2018 Laurent Montel 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 diff --git a/autotests/libs/cachepolicytest.cpp b/autotests/libs/cachepolicytest.cpp --- a/autotests/libs/cachepolicytest.cpp +++ b/autotests/libs/cachepolicytest.cpp @@ -1,5 +1,5 @@ /* - Copyright (c) 2017 Laurent Montel + Copyright (C) 2017-2018 Laurent Montel 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 diff --git a/autotests/libs/collectionattributetest.cpp b/autotests/libs/collectionattributetest.cpp --- a/autotests/libs/collectionattributetest.cpp +++ b/autotests/libs/collectionattributetest.cpp @@ -224,7 +224,7 @@ QByteArray result = attribute.serialized(); CollectionIdentificationAttribute parsed; parsed.deserialize(result); - qDebug() << parsed.identifier() << parsed.collectionNamespace() << result;; + qDebug() << parsed.identifier() << parsed.collectionNamespace() << result; QCOMPARE(parsed.identifier(), id); QCOMPARE(parsed.collectionNamespace(), ns); } diff --git a/autotests/libs/collectioncreatetest.cpp b/autotests/libs/collectioncreatetest.cpp new file mode 100644 --- /dev/null +++ b/autotests/libs/collectioncreatetest.cpp @@ -0,0 +1,67 @@ +/* + * Copyright 2017 Daniel Vrátil + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "test_utils.h" +#include "collectioncreatejob.h" +#include "collectionfetchjob.h" +#include "collectiondeletejob.h" +#include "entitydisplayattribute.h" + +using namespace Akonadi; + +class CollectionCreateTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void initTestCase() + { + AkonadiTest::checkTestIsIsolated(); + } + + void testCreateCollection() + { + auto monitor = getTestMonitor(); + QSignalSpy spy(monitor, &Monitor::collectionAdded); + + Collection col; + col.setName(QLatin1String("test_collection")); + col.setContentMimeTypes({ Collection::mimeType() }); + col.setParentCollection(Collection(collectionIdFromPath(QLatin1String("res1")))); + col.setRights(Collection::AllRights); + + CollectionCreateJob *cj = new CollectionCreateJob(col, this); + AKVERIFYEXEC(cj); + col = cj->collection(); + QVERIFY(col.isValid()); + + QTRY_COMPARE(spy.count(), 1); + auto ntfCol = spy.at(0).at(0).value(); + QCOMPARE(col, ntfCol); + + CollectionDeleteJob *dj = new CollectionDeleteJob(col, this); + AKVERIFYEXEC(dj); + } +}; + +QTEST_AKONADIMAIN(CollectionCreateTest) + +#include "collectioncreatetest.moc" diff --git a/autotests/libs/collectionsynctest.cpp b/autotests/libs/collectionsynctest.cpp --- a/autotests/libs/collectionsynctest.cpp +++ b/autotests/libs/collectionsynctest.cpp @@ -131,12 +131,12 @@ void cleanupBenchmark(const Collection::List &collections) { Collection::List baseCols; - Q_FOREACH (const Collection &col, collections) { + for (const Collection &col : collections) { if (col.remoteId().startsWith(QLatin1String("/baseCol")) || col.remoteId() == QLatin1String("/shared")) { baseCols << col; } } - Q_FOREACH (const Collection &col, baseCols) { + for (const Collection &col : baseCols) { CollectionDeleteJob *del = new CollectionDeleteJob(col); AKVERIFYEXEC(del); } diff --git a/autotests/libs/cachepolicytest.h b/autotests/libs/conflictresolvedialogtest.h copy from autotests/libs/cachepolicytest.h copy to autotests/libs/conflictresolvedialogtest.h --- a/autotests/libs/cachepolicytest.h +++ b/autotests/libs/conflictresolvedialogtest.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2017 Laurent Montel + Copyright (C) 2017-2018 Laurent Montel 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 @@ -17,19 +17,20 @@ 02110-1301, USA. */ -#ifndef CACHEPOLICYTEST_H -#define CACHEPOLICYTEST_H +#ifndef CONFLICTRESOLVEDIALOGTEST_H +#define CONFLICTRESOLVEDIALOGTEST_H #include -class CachePolicyTest : public QObject +class ConflictResolveDialogTest : public QObject { Q_OBJECT public: - explicit CachePolicyTest(QObject *parent = nullptr); - ~CachePolicyTest(); + explicit ConflictResolveDialogTest(QObject *parent = nullptr); + ~ConflictResolveDialogTest() = default; + private Q_SLOTS: - void shouldHaveDefaultValue(); + void shouldHaveDefaultValues(); }; -#endif // CACHEPOLICYTEST_H +#endif // CONFLICTRESOLVEDIALOGTEST_H diff --git a/autotests/libs/conflictresolvedialogtest.cpp b/autotests/libs/conflictresolvedialogtest.cpp new file mode 100644 --- /dev/null +++ b/autotests/libs/conflictresolvedialogtest.cpp @@ -0,0 +1,64 @@ +/* + Copyright (C) 2017-2018 Laurent Montel + + 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 "conflictresolvedialogtest.h" +#include "../src/widgets/conflictresolvedialog_p.h" + +#include +#include +#include +#include + +QTEST_MAIN(ConflictResolveDialogTest) + +ConflictResolveDialogTest::ConflictResolveDialogTest(QObject *parent) + : QObject(parent) +{ + +} + +void ConflictResolveDialogTest::shouldHaveDefaultValues() +{ + Akonadi::ConflictResolveDialog dlg; + + QVERIFY(!dlg.windowTitle().isEmpty()); + + QPushButton *takeLeftButton = dlg.findChild(QStringLiteral("takeLeftButton")); + QVERIFY(takeLeftButton); + QVERIFY(!takeLeftButton->text().isEmpty()); + + QPushButton *takeRightButton = dlg.findChild(QStringLiteral("takeRightButton")); + QVERIFY(takeRightButton); + QVERIFY(!takeRightButton->text().isEmpty()); + + QPushButton *keepBothButton = dlg.findChild(QStringLiteral("keepBothButton")); + QVERIFY(keepBothButton); + QVERIFY(!keepBothButton->text().isEmpty()); + QVERIFY(keepBothButton->isDefault()); + + QTextBrowser *mView = dlg.findChild(QStringLiteral("view")); + QVERIFY(mView); + QVERIFY(mView->toPlainText().isEmpty()); + + QLabel *docuLabel = dlg.findChild(QStringLiteral("doculabel")); + QVERIFY(docuLabel); + QVERIFY(!docuLabel->text().isEmpty()); + QVERIFY(docuLabel->wordWrap()); + QCOMPARE(docuLabel->contextMenuPolicy(), Qt::NoContextMenu); +} diff --git a/autotests/libs/fakeakonadiservercommand.h b/autotests/libs/fakeakonadiservercommand.h --- a/autotests/libs/fakeakonadiservercommand.h +++ b/autotests/libs/fakeakonadiservercommand.h @@ -129,7 +129,7 @@ { } - virtual ~FakeCollectionMovedCommand() + ~FakeCollectionMovedCommand() override { } @@ -152,7 +152,7 @@ { } - virtual ~FakeCollectionAddedCommand() + ~FakeCollectionAddedCommand() override { } @@ -174,7 +174,7 @@ { } - virtual ~FakeCollectionRemovedCommand() + ~FakeCollectionRemovedCommand() override { } @@ -202,7 +202,7 @@ { } - virtual ~FakeCollectionChangedCommand() + ~FakeCollectionChangedCommand() override { } @@ -226,7 +226,7 @@ { } - virtual ~FakeItemMovedCommand() + ~FakeItemMovedCommand() override { } @@ -249,7 +249,7 @@ { } - virtual ~FakeItemAddedCommand() + ~FakeItemAddedCommand() override { } @@ -271,7 +271,7 @@ { } - virtual ~FakeItemRemovedCommand() + ~FakeItemRemovedCommand() override { } @@ -294,7 +294,7 @@ { } - virtual ~FakeItemChangedCommand() + ~FakeItemChangedCommand() override { } @@ -316,7 +316,7 @@ { } - virtual ~FakeTagAddedCommand() + ~FakeTagAddedCommand() override { } @@ -338,7 +338,7 @@ { } - virtual ~FakeTagChangedCommand() + ~FakeTagChangedCommand() override { } @@ -361,7 +361,7 @@ { } - virtual ~FakeTagMovedCommand() + ~FakeTagMovedCommand() override { } @@ -384,7 +384,7 @@ { } - virtual ~FakeTagRemovedCommand() + ~FakeTagRemovedCommand() override { } @@ -419,7 +419,7 @@ m_parentTag = parentTag; } - virtual ~FakeJobResponse() + ~FakeJobResponse() override { } diff --git a/autotests/libs/fakeentitycache.h b/autotests/libs/fakeentitycache.h --- a/autotests/libs/fakeentitycache.h +++ b/autotests/libs/fakeentitycache.h @@ -126,22 +126,22 @@ Q_OBJECT public: - explicit FakeNotificationConnection(QObject *parent = nullptr) - : Connection(Connection::NotificationConnection, "testConn", parent) + explicit FakeNotificationConnection(Akonadi::CommandBuffer *buffer) + : Connection(Connection::NotificationConnection, "", buffer) + , mBuffer(buffer) {} virtual ~FakeNotificationConnection() {} void emitNotify(const Akonadi::Protocol::ChangeNotificationPtr &ntf) { - Q_EMIT commandReceived(3, ntf); + Akonadi::CommandBufferLocker locker(mBuffer); + mBuffer->enqueue(3, ntf); } - /* -Q_SIGNALS: - void notify(const Akonadi::Protocol::ChangeNotification &ntf); - */ +private: + Akonadi::CommandBuffer *mBuffer; }; class FakeMonitorDependeciesFactory : public Akonadi::ChangeNotificationDependenciesFactory @@ -155,8 +155,16 @@ { } - Akonadi::Connection *createNotificationConnection(Akonadi::Session *parent) override { - return new FakeNotificationConnection(parent); + Akonadi::Connection *createNotificationConnection(Akonadi::Session *parent, + Akonadi::CommandBuffer *buffer) override { + auto conn = new FakeNotificationConnection(buffer); + addConnection(parent, conn); + return conn; + } + + void destroyNotificationConnection(Akonadi::Session *parent, Akonadi::Connection *connection) override { + Q_UNUSED(parent); + delete connection; } Akonadi::CollectionCache *createCollectionCache(int maxCapacity, Akonadi::Session *session) override { diff --git a/autotests/libs/inspectablechangerecorder.cpp b/autotests/libs/inspectablechangerecorder.cpp --- a/autotests/libs/inspectablechangerecorder.cpp +++ b/autotests/libs/inspectablechangerecorder.cpp @@ -30,7 +30,7 @@ InspectableChangeRecorder::InspectableChangeRecorder(FakeMonitorDependeciesFactory *dependenciesFactory, QObject *parent) : ChangeRecorder(new Akonadi::ChangeRecorderPrivate(dependenciesFactory, this), parent) { - QTimer::singleShot(0, this, SLOT(doConnectToNotificationManager())); + QTimer::singleShot(0, this, &InspectableChangeRecorder::doConnectToNotificationManager); } void InspectableChangeRecorder::doConnectToNotificationManager() diff --git a/autotests/libs/itemmovetest.cpp b/autotests/libs/itemmovetest.cpp --- a/autotests/libs/itemmovetest.cpp +++ b/autotests/libs/itemmovetest.cpp @@ -81,7 +81,8 @@ Monitor monitor(&monitorSession); monitor.setObjectName(QStringLiteral("itemmovetest")); monitor.setCollectionMonitored(Collection::root()); - monitor.fetchCollection(true); monitor.itemFetchScope().setAncestorRetrieval(ItemFetchScope::Parent); + monitor.fetchCollection(true); + monitor.itemFetchScope().setAncestorRetrieval(ItemFetchScope::Parent); monitor.itemFetchScope().setFetchRemoteIdentification(true); QSignalSpy moveSpy(&monitor, &Monitor::itemsMoved); QSignalSpy readySpy(&monitor, &Monitor::monitorReady); diff --git a/autotests/libs/linktest.cpp b/autotests/libs/linktest.cpp --- a/autotests/libs/linktest.cpp +++ b/autotests/libs/linktest.cpp @@ -83,6 +83,7 @@ QList arg = lspy.takeFirst(); Item item = arg.at(0).value(); QCOMPARE(item.mimeType(), QString::fromLatin1("application/octet-stream")); + qDebug() << "TEST" << item.id() << item.loadedPayloadParts() << item.payloadData(); QVERIFY(item.hasPayload()); lspy.clear(); diff --git a/autotests/libs/monitornotificationtest.cpp b/autotests/libs/monitornotificationtest.cpp --- a/autotests/libs/monitornotificationtest.cpp +++ b/autotests/libs/monitornotificationtest.cpp @@ -41,6 +41,11 @@ m_fakeSession->setAsDefaultSession(); } + ~MonitorNotificationTest() + { + delete m_fakeSession; + } + private Q_SLOTS: void testSingleMessage(); void testFillPipeline(); @@ -107,7 +112,11 @@ auto msg = Protocol::CollectionChangeNotificationPtr::create(); msg->setParentCollection(parent.id()); msg->setOperation(Protocol::CollectionChangeNotification::Add); - msg->setId(added.id()); + msg->setCollection(Protocol::FetchCollectionsResponse(added.id())); + // With notification payloads most requests by-pass the pipeline as the + // notification already contains everything. To force pipelineing we set + // the internal metadata (normally set by ChangeRecorder) + msg->addMetadata("FETCH_COLLECTION"); QHash data; data.insert(parent.id(), parent); @@ -120,7 +129,7 @@ monitor->notificationConnection()->emitNotify(msg); - QCOMPARE(monitor->pipeline().size(), 1); + QTRY_COMPARE(monitor->pipeline().size(), 1); QVERIFY(monitor->pendingNotifications().isEmpty()); collectionCache->setData(data); @@ -174,7 +183,8 @@ auto msg = Protocol::CollectionChangeNotificationPtr::create(); msg->setParentCollection(parent.id()); msg->setOperation(Protocol::CollectionChangeNotification::Add); - msg->setId(added.id()); + msg->setCollection(Protocol::FetchCollectionsResponse(added.id())); + msg->addMetadata("FETCH_COLLECTION"); data.insert(parent.id(), parent); data.insert(added.id(), added); @@ -189,7 +199,7 @@ monitor->notificationConnection()->emitNotify(ntf); } - QCOMPARE(monitor->pipeline().size(), 5); + QTRY_COMPARE(monitor->pipeline().size(), 5); QCOMPARE(monitor->pendingNotifications().size(), 15); collectionCache->setData(data); @@ -247,7 +257,8 @@ auto msg = Protocol::CollectionChangeNotificationPtr::create(); msg->setParentCollection(i % 2 ? 2 : added.id() - 1); msg->setOperation(Protocol::CollectionChangeNotification::Add); - msg->setId(added.id()); + msg->setCollection(Protocol::FetchCollectionsResponse(added.id())); + msg->addMetadata("FETCH_COLLECTION"); list << msg; } @@ -268,15 +279,15 @@ collectionCache->emitDataAvailable(); - QVERIFY(monitor->pipeline().isEmpty()); + QTRY_VERIFY(monitor->pipeline().isEmpty()); QVERIFY(monitor->pendingNotifications().isEmpty()); Q_FOREACH (const Protocol::ChangeNotificationPtr &ntf, list) { monitor->notificationConnection()->emitNotify(ntf); } // Collection 6 is not notified, because Collection 5 has held up the pipeline - QCOMPARE(collectionAddedSpy.size(), 1); + QTRY_COMPARE(collectionAddedSpy.size(), 1); QCOMPARE((int)collectionAddedSpy.takeFirst().first().value().id(), 4); QCOMPARE(monitor->pipeline().size(), 3); QCOMPARE(monitor->pendingNotifications().size(), 0); diff --git a/autotests/libs/newmailnotifierattributetest.h b/autotests/libs/newmailnotifierattributetest.h --- a/autotests/libs/newmailnotifierattributetest.h +++ b/autotests/libs/newmailnotifierattributetest.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2014-2017 Montel Laurent + Copyright (c) 2014-2018 Montel Laurent This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as diff --git a/autotests/libs/newmailnotifierattributetest.cpp b/autotests/libs/newmailnotifierattributetest.cpp --- a/autotests/libs/newmailnotifierattributetest.cpp +++ b/autotests/libs/newmailnotifierattributetest.cpp @@ -1,5 +1,5 @@ /* - Copyright (c) 2014-2017 Montel Laurent + Copyright (c) 2014-2018 Montel Laurent This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as diff --git a/autotests/libs/pop3resourceattributetest.h b/autotests/libs/pop3resourceattributetest.h --- a/autotests/libs/pop3resourceattributetest.h +++ b/autotests/libs/pop3resourceattributetest.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2014-2017 Montel Laurent + Copyright (c) 2014-2018 Montel Laurent This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as diff --git a/autotests/libs/pop3resourceattributetest.cpp b/autotests/libs/pop3resourceattributetest.cpp --- a/autotests/libs/pop3resourceattributetest.cpp +++ b/autotests/libs/pop3resourceattributetest.cpp @@ -1,5 +1,5 @@ /* - Copyright (c) 2014-2017 Montel Laurent + Copyright (c) 2014-2018 Montel Laurent This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as diff --git a/autotests/libs/protocolhelpertest.cpp b/autotests/libs/protocolhelpertest.cpp --- a/autotests/libs/protocolhelpertest.cpp +++ b/autotests/libs/protocolhelpertest.cpp @@ -24,7 +24,7 @@ Q_DECLARE_METATYPE(Scope) Q_DECLARE_METATYPE(QVector) -Q_DECLARE_METATYPE(Protocol::FetchScope) +Q_DECLARE_METATYPE(Protocol::ItemFetchScope) class ProtocolHelperTest : public QObject { @@ -227,15 +227,15 @@ void testItemFetchScopeToProtocol_data() { QTest::addColumn("scope"); - QTest::addColumn("result"); + QTest::addColumn("result"); { - Protocol::FetchScope fs; - fs.setFetch(Protocol::FetchScope::Flags | - Protocol::FetchScope::Size | - Protocol::FetchScope::RemoteID | - Protocol::FetchScope::RemoteRevision | - Protocol::FetchScope::MTime); + Protocol::ItemFetchScope fs; + fs.setFetch(Protocol::ItemFetchScope::Flags | + Protocol::ItemFetchScope::Size | + Protocol::ItemFetchScope::RemoteID | + Protocol::ItemFetchScope::RemoteRevision | + Protocol::ItemFetchScope::MTime); QTest::newRow("empty") << ItemFetchScope() << fs; } @@ -246,35 +246,35 @@ scope.setAncestorRetrieval(Akonadi::ItemFetchScope::All); scope.setIgnoreRetrievalErrors(true); - Protocol::FetchScope fs; - fs.setFetch(Protocol::FetchScope::FullPayload | - Protocol::FetchScope::AllAttributes | - Protocol::FetchScope::Flags | - Protocol::FetchScope::Size | - Protocol::FetchScope::RemoteID | - Protocol::FetchScope::RemoteRevision | - Protocol::FetchScope::MTime | - Protocol::FetchScope::IgnoreErrors); - fs.setAncestorDepth(Protocol::FetchScope::AllAncestors); + Protocol::ItemFetchScope fs; + fs.setFetch(Protocol::ItemFetchScope::FullPayload | + Protocol::ItemFetchScope::AllAttributes | + Protocol::ItemFetchScope::Flags | + Protocol::ItemFetchScope::Size | + Protocol::ItemFetchScope::RemoteID | + Protocol::ItemFetchScope::RemoteRevision | + Protocol::ItemFetchScope::MTime | + Protocol::ItemFetchScope::IgnoreErrors); + fs.setAncestorDepth(Protocol::ItemFetchScope::AllAncestors); QTest::newRow("full") << scope << fs; } { ItemFetchScope scope; scope.setFetchModificationTime(false); scope.setFetchRemoteIdentification(false); - Protocol::FetchScope fs; - fs.setFetch(Protocol::FetchScope::Flags | - Protocol::FetchScope::Size); + Protocol::ItemFetchScope fs; + fs.setFetch(Protocol::ItemFetchScope::Flags | + Protocol::ItemFetchScope::Size); QTest::newRow("minimal") << scope << fs; } } void testItemFetchScopeToProtocol() { QFETCH(ItemFetchScope, scope); - QFETCH(Protocol::FetchScope, result); + QFETCH(Protocol::ItemFetchScope, result); QCOMPARE(ProtocolHelper::itemFetchScopeToProtocol(scope), result); } diff --git a/autotests/libs/tagselectwidgettest.h b/autotests/libs/tagselectwidgettest.h --- a/autotests/libs/tagselectwidgettest.h +++ b/autotests/libs/tagselectwidgettest.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2015-2017 Montel Laurent + Copyright (c) 2015-2018 Montel Laurent This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as diff --git a/autotests/libs/tagselectwidgettest.cpp b/autotests/libs/tagselectwidgettest.cpp --- a/autotests/libs/tagselectwidgettest.cpp +++ b/autotests/libs/tagselectwidgettest.cpp @@ -1,5 +1,5 @@ /* - Copyright (c) 2015-2017 Montel Laurent + Copyright (c) 2015-2018 Montel Laurent This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as diff --git a/autotests/libs/tagtest.cpp b/autotests/libs/tagtest.cpp --- a/autotests/libs/tagtest.cpp +++ b/autotests/libs/tagtest.cpp @@ -743,6 +743,7 @@ Akonadi::Monitor monitor; monitor.setTypeMonitored(Akonadi::Monitor::Tags); monitor.tagFetchScope().fetchAttribute(); + QTest::qWait(10); // give Monitor time to upload settings Akonadi::Tag createdTag; { diff --git a/autotests/libs/testresource/CMakeLists.txt b/autotests/libs/testresource/CMakeLists.txt --- a/autotests/libs/testresource/CMakeLists.txt +++ b/autotests/libs/testresource/CMakeLists.txt @@ -2,29 +2,13 @@ ${Boost_INCLUDE_DIR} ) +include(${CMAKE_SOURCE_DIR}/KF5AkonadiMacros.cmake) + kde_enable_exceptions() remove_definitions(-DTRANSLATION_DOMAIN=\"libakonadi5\") add_definitions(-DTRANSLATION_DOMAIN=\"akonadi_knut_resource\") -find_package(LibXslt) -set_package_properties(LibXslt PROPERTIES DESCRIPTION "xsltproc" URL "http://xmlsoft.org/XSLT/" TYPE REQUIRED PURPOSE "Needed to generate D-Bus interface specifications") - -find_program(XSLTPROC_EXECUTABLE xsltproc DOC "Path to the xsltproc executable") - -# generates a D-Bus interface description from a KConfigXT file -macro(kcfg_generate_dbus_interface _kcfg _name) - add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${_name}.xml - COMMAND ${XSLTPROC_EXECUTABLE} --stringparam interfaceName ${_name} - ${Akonadi_SOURCE_DIR}/src/core/kcfg2dbus.xsl - ${_kcfg} - > ${CMAKE_CURRENT_BINARY_DIR}/${_name}.xml - DEPENDS ${Akonadi_SOURCE_DIR}/src/core/kcfg2dbus.xsl - ${_kcfg} - ) -endmacro() - # Disabled for now, resourcetester remained in kdepim-runtime #add_subdirectory( tests ) diff --git a/autotests/libs/testresource/knutresource.h b/autotests/libs/testresource/knutresource.h --- a/autotests/libs/testresource/knutresource.h +++ b/autotests/libs/testresource/knutresource.h @@ -42,7 +42,7 @@ public: using Akonadi::AgentBase::ObserverV2::collectionChanged; // So we don't trigger -Woverloaded-virtual explicit KnutResource(const QString &id); - ~KnutResource(); + ~KnutResource() override; public Q_SLOTS: void configure(WId windowId) override; diff --git a/autotests/libs/testrunner/main.cpp b/autotests/libs/testrunner/main.cpp --- a/autotests/libs/testrunner/main.cpp +++ b/autotests/libs/testrunner/main.cpp @@ -124,7 +124,11 @@ if (parser.isSet(QStringLiteral("testenv"))) { sh.makeShellScript(parser.value(QStringLiteral("testenv"))); } else { +#ifdef Q_OS_WIN + sh.makeShellScript(setup->basePath() + QLatin1String("testenvironment.ps1")); +#else sh.makeShellScript(setup->basePath() + QLatin1String("testenvironment.sh")); +#endif } if (!parser.positionalArguments().isEmpty()) { diff --git a/autotests/libs/testrunner/setup.cpp b/autotests/libs/testrunner/setup.cpp --- a/autotests/libs/testrunner/setup.cpp +++ b/autotests/libs/testrunner/setup.cpp @@ -41,8 +41,8 @@ if (!mAkonadiDaemonProcess) { mAkonadiDaemonProcess = new KProcess(this); - connect(mAkonadiDaemonProcess, SIGNAL(finished(int)), - this, SLOT(slotAkonadiDaemonProcessFinished(int))); + connect(mAkonadiDaemonProcess, QOverload::of(&KProcess::finished), + this, &SetupTest::slotAkonadiDaemonProcessFinished); } mAkonadiDaemonProcess->setProgram(QStringLiteral("akonadi_control"), @@ -147,18 +147,29 @@ if (fi.isDir()) { if (fi.fileName() == QStringLiteral("akonadi")) { // namespace according to instance identifier - copyDirectory(fi.absoluteFilePath(), dst + QDir::separator() + QStringLiteral("akonadi") + QDir::separator() - + QStringLiteral("instance") + QDir::separator() + instanceId()); +#ifdef Q_OS_WIN + const bool isXdgConfig = src.contains(QLatin1String("/xdgconfig/")); + copyDirectory(fi.absoluteFilePath(), dst + QStringLiteral("/akonadi/") + (isXdgConfig ? QStringLiteral("config/") : QStringLiteral("data/")) + + QStringLiteral("instance/") + instanceId()); +#else + copyDirectory(fi.absoluteFilePath(), dst + QStringLiteral("/akonadi/instance/") + instanceId()); +#endif } else { - copyDirectory(fi.absoluteFilePath(), dst + QDir::separator() + fi.fileName()); + copyDirectory(fi.absoluteFilePath(), dst + QLatin1Char('/') + fi.fileName()); } } else { if (fi.fileName().startsWith(QStringLiteral("akonadi_")) && fi.fileName().endsWith(QStringLiteral("rc"))) { // namespace according to instance identifier const QString baseName = fi.fileName().left(fi.fileName().size() - 2); - QFile::copy(fi.absoluteFilePath(), dst + QDir::separator() + Akonadi::ServerManager::addNamespace(baseName) + QStringLiteral("rc")); + const QString dstPath = dst + QLatin1Char('/') + Akonadi::ServerManager::addNamespace(baseName) + QStringLiteral("rc"); + if (!QFile::copy(fi.absoluteFilePath(), dstPath)) { + qWarning() << "Failed to copy" << fi.absoluteFilePath() << "to" << dstPath; + } } else { - QFile::copy(fi.absoluteFilePath(), dst + QDir::separator() + fi.fileName()); + const QString dstPath = dst + QLatin1Char('/') + fi.fileName(); + if (!QFile::copy(fi.absoluteFilePath(), dstPath)) { + qWarning() << "Failed to copy" << fi.absoluteFilePath() << "to" << dstPath; + } } } } @@ -170,47 +181,62 @@ QDir::root().mkpath(dst); const auto entries = srcDir.entryInfoList(QDir::Dirs | QDir::Files | QDir::NoSymLinks | QDir::NoDotAndDotDot); for (const auto &fi : entries) { + const QString dstPath = dst + QLatin1Char('/') + fi.fileName(); if (fi.isDir()) { - copyDirectory(fi.absoluteFilePath(), dst + QDir::separator() + fi.fileName()); + copyDirectory(fi.absoluteFilePath(), dstPath); } else { - QFile::copy(fi.absoluteFilePath(), dst + QDir::separator() + fi.fileName()); + if (!QFile::copy(fi.absoluteFilePath(), dstPath)) { + qWarning() << "Failed to copy" << fi.absoluteFilePath() << "to" << dstPath; + } } } } void SetupTest::createTempEnvironment() { qDebug() << "Creating test environment in" << basePath(); + const Config *config = Config::instance(); +#ifdef Q_OS_WIN + // Always copy the generic xdgconfig dir + copyXdgDirectory(config->basePath() + QStringLiteral("/xdgconfig"), basePath()); + if (!config->xdgConfigHome().isEmpty()) { + copyXdgDirectory(config->xdgConfigHome(), basePath()); + } + copyXdgDirectory(config->xdgDataHome(), basePath()); + setEnvironmentVariable("XDG_DATA_HOME", basePath()); + setEnvironmentVariable("XDG_CONFIG_HOME", basePath()); + writeAkonadiserverrc(basePath() + QStringLiteral("/akonadi/config/instance/%1/akonadiserverrc").arg(instanceId())); +#else const QDir tmpDir(basePath()); const QString testRunnerDataDir = QStringLiteral("data"); const QString testRunnerConfigDir = QStringLiteral("config"); const QString testRunnerTmpDir = QStringLiteral("tmp"); - tmpDir.mkdir(testRunnerConfigDir); - tmpDir.mkdir(testRunnerDataDir); - tmpDir.mkdir(testRunnerTmpDir); + tmpDir.mkpath(testRunnerConfigDir); + tmpDir.mkpath(testRunnerDataDir); + tmpDir.mkpath(testRunnerTmpDir); - const Config *config = Config::instance(); // Always copy the generic xdgconfig dir copyXdgDirectory(config->basePath() + QStringLiteral("/xdgconfig"), basePath() + testRunnerConfigDir); if (!config->xdgConfigHome().isEmpty()) { copyXdgDirectory(config->xdgConfigHome(), basePath() + testRunnerConfigDir); } copyXdgDirectory(config->xdgDataHome(), basePath() + testRunnerDataDir); + setEnvironmentVariable("XDG_DATA_HOME", basePath() + testRunnerDataDir); + setEnvironmentVariable("XDG_CONFIG_HOME", basePath() + testRunnerConfigDir); + setEnvironmentVariable("TMPDIR", basePath() + testRunnerTmpDir); + writeAkonadiserverrc(basePath() + testRunnerConfigDir + QStringLiteral("/akonadi/instance/%1/akonadiserverrc").arg(instanceId())); +#endif + QString backend; if (Config::instance()->dbBackend() == QLatin1String("pgsql")) { backend = QStringLiteral("postgresql"); } else { backend = Config::instance()->dbBackend(); } - setEnvironmentVariable("XDG_DATA_HOME", basePath() + testRunnerDataDir); - setEnvironmentVariable("XDG_CONFIG_HOME", basePath() + testRunnerConfigDir); - setEnvironmentVariable("TMPDIR", basePath() + testRunnerTmpDir); setEnvironmentVariable("TESTRUNNER_DB_ENVIRONMENT", backend); - - writeAkonadiserverrc(basePath() + testRunnerConfigDir); } void SetupTest::writeAkonadiserverrc(const QString &path) @@ -227,8 +253,7 @@ return; } - QSettings settings(path + QStringLiteral("/akonadi/instance/%1/akonadiserverrc").arg(instanceId()), - QSettings::IniFormat); + QSettings settings(path, QSettings::IniFormat); settings.beginGroup(QStringLiteral("General")); settings.setValue(QStringLiteral("Driver"), backend); settings.endGroup(); @@ -243,7 +268,12 @@ void SetupTest::cleanTempEnvironment() { +#ifdef Q_OS_WIN + QDir(basePath() + QStringLiteral("akonadi/config/instance/") + instanceId()).removeRecursively(); + QDir(basePath() + QStringLiteral("akonadi/data/instance/") + instanceId()).removeRecursively(); +#else QDir(basePath()).removeRecursively(); +#endif } SetupTest::SetupTest() @@ -278,8 +308,8 @@ KConfigGroup migrationCfg(&migratorConfig, "Migration"); migrationCfg.writeEntry("Enabled", false); - connect(Akonadi::ServerManager::self(), SIGNAL(stateChanged(Akonadi::ServerManager::State)), - SLOT(serverStateChanged(Akonadi::ServerManager::State))); + connect(Akonadi::ServerManager::self(), &Akonadi::ServerManager::stateChanged, + this, &SetupTest::serverStateChanged); QDBusConnection::sessionBus().registerService(QStringLiteral("org.kde.Akonadi.Testrunner-") + QString::number(QCoreApplication::instance()->applicationPid())); QDBusConnection::sessionBus().registerObject(QStringLiteral("/"), this, QDBusConnection::ExportScriptableSlots); @@ -345,22 +375,29 @@ QString SetupTest::basePath() const { +#ifdef Q_OS_WIN + // On Windows we are forced to share the same data directory as production instances + // because there's no way to override QStandardPaths like we can on Unix. + // This means that on Windows we rely on Instances providing us the neccessary isolation + return QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); +#else QString sysTempDirPath = QDir::tempPath(); -#ifdef Q_OS_UNIX + #ifdef Q_OS_UNIX // QDir::tempPath() makes sure to use the fully sym-link exploded // absolute path to the temp dir. That is nice, but on OSX it makes // that path really long. MySQL chokes on this, for it's socket path, // so work around that sysTempDirPath = QStringLiteral("/tmp"); -#endif + #endif const QDir sysTempDir(sysTempDirPath); - const QString tempDir = QStringLiteral("akonadi_testrunner-%1") + const QString tempDir = QStringLiteral("/akonadi_testrunner-%1/") .arg(QCoreApplication::instance()->applicationPid()); if (!sysTempDir.exists(tempDir)) { sysTempDir.mkdir(tempDir); } - return sysTempDirPath + QDir::separator() + tempDir + QDir::separator(); + return sysTempDirPath + tempDir; +#endif } void SetupTest::slotAkonadiDaemonProcessFinished(int exitCode) diff --git a/autotests/libs/testrunner/shellscript.cpp b/autotests/libs/testrunner/shellscript.cpp --- a/autotests/libs/testrunner/shellscript.cpp +++ b/autotests/libs/testrunner/shellscript.cpp @@ -30,36 +30,53 @@ void ShellScript::writeEnvironmentVariables() { - foreach (const EnvVar &envvar, mEnvVars) { - mScript += QLatin1String("_old_") + QLatin1String(envvar.first) + QLatin1String("=$") + QLatin1String(envvar.first) + QLatin1String("\n"); - mScript.append(QLatin1String(envvar.first)); - mScript.append(QLatin1String("=\"")); - QString value = QString::fromLocal8Bit(envvar.second); - value = value.replace(QLatin1Char('"'), QLatin1String("\\\"")); - mScript.append(value); - mScript.append(QLatin1String("\"\n")); - - mScript.append(QLatin1String("export ")); - mScript.append(QLatin1String(envvar.first)); - mScript.append(QLatin1Char('\n')); + for (const auto &envvar : qAsConst(mEnvVars)) { +#ifdef Q_OS_WIN + const auto tmpl = QStringLiteral("$env:_old_%1=$env:%1\r\n" + "$env:%1=\"%2\"\r\n"); +#else + const auto tmpl = QStringLiteral("_old_%1=$%1\n" + "%1=\"%2\"\n" + "export %1\n"); +#endif + mScript += tmpl.arg(QString::fromLocal8Bit(envvar.first), + QString::fromLocal8Bit(envvar.second).replace(QLatin1Char('"'), QStringLiteral("\\\""))); } - mScript.append(QLatin1String("\n\n")); +#ifdef Q_OS_WIN + mScript += QStringLiteral("\r\n\r\n"); +#else + mScript += QStringLiteral("\n\n"); +#endif } void ShellScript::writeShutdownFunction() { - QString s = - QLatin1String("function shutdown-testenvironment()\n" - "{\n" - " qdbus org.kde.Akonadi.Testrunner-") + QString::number(QCoreApplication::instance()->applicationPid()) + QLatin1String(" / org.kde.Akonadi.Testrunner.shutdown\n"); - - foreach (const EnvVar &envvar, mEnvVars) { - s += QLatin1String(" ") + QLatin1String(envvar.first) + QLatin1String("=$_old_") + QLatin1String(envvar.first) + QLatin1String("\n"); - s += QLatin1String(" export ") + QLatin1String(envvar.first) + QLatin1String("\n"); +#ifdef Q_OS_WIN + const auto tmpl = QStringLiteral("Function shutdownTestEnvironment()\r\n" + "{\r\n" + " qdbus %1 %2 %3\r\n" + "%4" + "}\r\n\r\n"); + const auto restoreTmpl = QStringLiteral(" $env:%1=$env:_old_%1\r\n"); +#else + const auto tmpl = QStringLiteral("function shutdown-testenvironment()\n" + "{\n" + " qdbus %1 %2 %3\n" + "%4" + "}\n\n"); + const auto restoreTmpl = QStringLiteral(" %1=$_old_%1\n" + " export %1\n"); +#endif + QString restore; + for (const auto &envvar : qAsConst(mEnvVars)) { + restore += restoreTmpl.arg(QString::fromLocal8Bit(envvar.first)); } - s.append(QLatin1String("}\n\n")); - mScript.append(s); + + mScript += tmpl.arg(QStringLiteral("org.kde.Akonadi.Testrunner-%1").arg(qApp->applicationPid()), + QStringLiteral("/"), + QStringLiteral("org.kde.Akonadi.Testrunner.shutdown"), + restore); } void ShellScript::makeShellScript(const QString &fileName) @@ -71,7 +88,7 @@ writeEnvironmentVariables(); writeShutdownFunction(); - file.write(mScript.toLatin1().constData(), qstrlen(mScript.toLatin1().constData())); + file.write(mScript.toLatin1()); file.close(); } else { qCritical() << "Failed to write" << fileName; diff --git a/autotests/libs/testrunner/testrunner.cpp b/autotests/libs/testrunner/testrunner.cpp --- a/autotests/libs/testrunner/testrunner.cpp +++ b/autotests/libs/testrunner/testrunner.cpp @@ -39,8 +39,9 @@ qDebug() << mArguments; mProcess = new KProcess(this); mProcess->setProgram(mArguments); - connect(mProcess, SIGNAL(finished(int)), SLOT(processFinished(int))); - connect(mProcess, SIGNAL(error(QProcess::ProcessError)), SLOT(processError(QProcess::ProcessError))); + connect(mProcess, QOverload::of(&KProcess::finished), this, &TestRunner::processFinished); + connect(mProcess, QOverload::of(&KProcess::error), + this, &TestRunner::processError); // environment setup seems to have been done by setuptest globally already mProcess->start(); if (!mProcess->waitForStarted()) { diff --git a/autotests/private/akstandarddirstest.cpp b/autotests/private/akstandarddirstest.cpp --- a/autotests/private/akstandarddirstest.cpp +++ b/autotests/private/akstandarddirstest.cpp @@ -34,13 +34,13 @@ void testCondigFile() { akTestSetInstanceIdentifier(QString()); - QVERIFY(StandardDirs::agentConfigFile(XdgBaseDirs::ReadOnly).endsWith(QL1S("agentsrc"))); - QVERIFY(StandardDirs::agentConfigFile(XdgBaseDirs::ReadWrite).endsWith(QL1S("agentsrc"))); - QVERIFY(!StandardDirs::agentConfigFile(XdgBaseDirs::ReadWrite).endsWith(QL1S("foo/agentsrc"))); + QVERIFY(StandardDirs::agentsConfigFile(StandardDirs::ReadOnly).endsWith(QL1S("agentsrc"))); + QVERIFY(StandardDirs::agentsConfigFile(StandardDirs::ReadWrite).endsWith(QL1S("agentsrc"))); + QVERIFY(!StandardDirs::agentsConfigFile(StandardDirs::ReadWrite).endsWith(QL1S("foo/agentsrc"))); akTestSetInstanceIdentifier(QL1S("foo")); - QVERIFY(StandardDirs::agentConfigFile(XdgBaseDirs::ReadOnly).endsWith(QL1S("agentsrc"))); - QVERIFY(StandardDirs::agentConfigFile(XdgBaseDirs::ReadWrite).endsWith(QL1S("instance/foo/agentsrc"))); + QVERIFY(StandardDirs::agentsConfigFile(StandardDirs::ReadOnly).endsWith(QL1S("agentsrc"))); + QVERIFY(StandardDirs::agentsConfigFile(StandardDirs::ReadWrite).endsWith(QL1S("instance/foo/agentsrc"))); } void testSaveDir() diff --git a/autotests/private/notificationmessagetest.cpp b/autotests/private/notificationmessagetest.cpp --- a/autotests/private/notificationmessagetest.cpp +++ b/autotests/private/notificationmessagetest.cpp @@ -31,7 +31,9 @@ void NotificationMessageTest::testCompress() { ChangeNotificationList list; + FetchCollectionsResponse collection(1); CollectionChangeNotification msg; + msg.setCollection(std::move(collection)); msg.setOperation(CollectionChangeNotification::Add); QVERIFY(CollectionChangeNotification::appendAndCompress( @@ -53,7 +55,9 @@ void NotificationMessageTest::testCompress2() { ChangeNotificationList list; + FetchCollectionsResponse collection(1); CollectionChangeNotification msg; + msg.setCollection(std::move(collection)); msg.setOperation(CollectionChangeNotification::Modify); QVERIFY(CollectionChangeNotification::appendAndCompress( @@ -71,7 +75,9 @@ void NotificationMessageTest::testCompress3() { ChangeNotificationList list; + FetchCollectionsResponse collection(1); CollectionChangeNotification msg; + msg.setCollection(std::move(collection)); msg.setOperation(CollectionChangeNotification::Modify); QVERIFY(CollectionChangeNotification::appendAndCompress( @@ -86,7 +92,9 @@ void NotificationMessageTest::testPartModificationMerge() { ChangeNotificationList list; + FetchCollectionsResponse collection(1); CollectionChangeNotification msg; + msg.setCollection(std::move(collection)); msg.setOperation(CollectionChangeNotification::Modify); msg.setChangedParts(QSet() << "PART1"); diff --git a/autotests/private/protocoltest.cpp b/autotests/private/protocoltest.cpp --- a/autotests/private/protocoltest.cpp +++ b/autotests/private/protocoltest.cpp @@ -39,7 +39,7 @@ // If it wasn't you who broke it, please go find that person who was too // lazy to extend the test case and beat them with a stick. -- Dan - QCOMPARE(Akonadi::Protocol::version(), 58); + QCOMPARE(Akonadi::Protocol::version(), 59); } void ProtocolTest::testFactory_data() @@ -241,38 +241,38 @@ QFETCH(QVector, expectedParts); QFETCH(QVector, expectedPayloads); - FetchScope in; - for (int i = FetchScope::CacheOnly; i <= FetchScope::VirtReferences; i = i << 1) { - QVERIFY(!in.fetch(static_cast(i))); + ItemFetchScope in; + for (int i = ItemFetchScope::CacheOnly; i <= ItemFetchScope::VirtReferences; i = i << 1) { + QVERIFY(!in.fetch(static_cast(i))); } - QVERIFY(in.fetch(FetchScope::None)); + QVERIFY(in.fetch(ItemFetchScope::None)); in.setRequestedParts(requestedParts); in.setChangedSince(QDateTime(QDate(2015, 8, 10), QTime(23, 52, 20), Qt::UTC)); in.setTagFetchScope({ "TAGID" }); - in.setAncestorDepth(FetchScope::AllAncestors); - in.setFetch(FetchScope::CacheOnly); - in.setFetch(FetchScope::CheckCachedPayloadPartsOnly); - in.setFetch(FetchScope::FullPayload, fullPayload); - in.setFetch(FetchScope::AllAttributes); - in.setFetch(FetchScope::Size); - in.setFetch(FetchScope::MTime); - in.setFetch(FetchScope::RemoteRevision); - in.setFetch(FetchScope::IgnoreErrors); - in.setFetch(FetchScope::Flags); - in.setFetch(FetchScope::RemoteID); - in.setFetch(FetchScope::GID); - in.setFetch(FetchScope::Tags); - in.setFetch(FetchScope::Relations); - in.setFetch(FetchScope::VirtReferences); - - const FetchScope out = serializeAndDeserialize(in); + in.setAncestorDepth(ItemFetchScope::AllAncestors); + in.setFetch(ItemFetchScope::CacheOnly); + in.setFetch(ItemFetchScope::CheckCachedPayloadPartsOnly); + in.setFetch(ItemFetchScope::FullPayload, fullPayload); + in.setFetch(ItemFetchScope::AllAttributes); + in.setFetch(ItemFetchScope::Size); + in.setFetch(ItemFetchScope::MTime); + in.setFetch(ItemFetchScope::RemoteRevision); + in.setFetch(ItemFetchScope::IgnoreErrors); + in.setFetch(ItemFetchScope::Flags); + in.setFetch(ItemFetchScope::RemoteID); + in.setFetch(ItemFetchScope::GID); + in.setFetch(ItemFetchScope::Tags); + in.setFetch(ItemFetchScope::Relations); + in.setFetch(ItemFetchScope::VirtReferences); + + const ItemFetchScope out = serializeAndDeserialize(in); QCOMPARE(out.requestedParts(), expectedParts); QCOMPARE(out.requestedPayloads(), expectedPayloads); QCOMPARE(out.changedSince(), QDateTime(QDate(2015, 8, 10), QTime(23, 52, 20), Qt::UTC)); QCOMPARE(out.tagFetchScope(), QSet{ "TAGID" }); - QCOMPARE(out.ancestorDepth(), FetchScope::AllAncestors); - QCOMPARE(out.fetch(FetchScope::None), false); + QCOMPARE(out.ancestorDepth(), ItemFetchScope::AllAncestors); + QCOMPARE(out.fetch(ItemFetchScope::None), false); QCOMPARE(out.cacheOnly(), true); QCOMPARE(out.checkCachedPayloadPartsOnly(), true); QCOMPARE(out.fullPayload(), fullPayload); diff --git a/autotests/server/CMakeLists.txt b/autotests/server/CMakeLists.txt --- a/autotests/server/CMakeLists.txt +++ b/autotests/server/CMakeLists.txt @@ -9,19 +9,19 @@ ${CMAKE_BINARY_DIR}/src/server ${Akonadi_SOURCE_DIR}/src/server) -akonadi_generate_schema(${CMAKE_CURRENT_SOURCE_DIR}/dbtest_data/unittest_schema.xml UnitTestSchema unittestschema) - -set(AKONADI_DB_DATA ${CMAKE_CURRENT_SOURCE_DIR}/dbtest_data/dbdata.xml) - -add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/dbpopulator.cpp - COMMAND ${XSLTPROC_EXECUTABLE} - --output ${CMAKE_CURRENT_BINARY_DIR}/dbpopulator.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/dbpopulator.xsl - ${AKONADI_DB_DATA} - DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/dbpopulator.xsl - ${AKONADI_DB_DATA} +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 @@ -32,6 +32,7 @@ fakesearchmanager.cpp fakeitemretrievalmanager.cpp dbinitializer.cpp + inspectablenotificationcollector.cpp ${CMAKE_CURRENT_BINARY_DIR}/dbpopulator.cpp ) @@ -47,7 +48,7 @@ ) macro(add_server_test _source) - set(_test ${_source} ../../src/server/akonadiserver_debug.cpp) + 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}) diff --git a/autotests/server/akappendhandlertest.cpp b/autotests/server/akappendhandlertest.cpp --- a/autotests/server/akappendhandlertest.cpp +++ b/autotests/server/akappendhandlertest.cpp @@ -50,7 +50,7 @@ { // Effectively disable external payload parts, we have a dedicated unit-test // for that - const QString serverConfigFile = StandardDirs::serverConfigFile(XdgBaseDirs::ReadWrite); + const QString serverConfigFile = StandardDirs::serverConfigFile(StandardDirs::ReadWrite); QSettings settings(serverConfigFile, QSettings::IniFormat); settings.setValue(QStringLiteral("General/SizeThreshold"), std::numeric_limits::max()); @@ -76,7 +76,12 @@ void updateNotifcationEntity(Protocol::ItemChangeNotificationPtr &ntf, const PimItem &pimItem) { - ntf->setItems({ { pimItem.id(), pimItem.remoteId(), pimItem.remoteRevision(), pimItem.mimeType().name() } }); + 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 @@ -236,7 +241,12 @@ notification->setOperation(Protocol::ItemChangeNotification::Add); notification->setParentCollection(4); notification->setResource("akonadi_fake_resource_0"); - notification->setItems({ { -1, QLatin1String("TEST-1"), QLatin1String("1"), QLatin1String("application/octet-stream") } }); + 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() @@ -664,7 +674,7 @@ updatePimItem(pimItem, QStringLiteral("TEST-20"), 0); updateFlags(flags, {}); updateTags(tags, { { QLatin1String("PLAIN"), utf8String } }); - updateNotifcationEntity(notification, pimItem);; + updateNotifcationEntity(notification, pimItem); ++uidnext; { auto cmd = createCommand(pimItem, datetime, {}); diff --git a/autotests/server/collectionreferencetest.cpp b/autotests/server/collectionreferencetest.cpp --- a/autotests/server/collectionreferencetest.cpp +++ b/autotests/server/collectionreferencetest.cpp @@ -74,12 +74,15 @@ auto notificationTemplate = Protocol::CollectionChangeNotificationPtr::create(); notificationTemplate->setOperation(Protocol::CollectionChangeNotification::Modify); - notificationTemplate->setId(initializer.collection("col2").id()); - notificationTemplate->setRemoteId(QStringLiteral("col2")); - notificationTemplate->setRemoteRevision(QStringLiteral("")); 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); diff --git a/autotests/server/collectionstatisticstest.cpp b/autotests/server/collectionstatisticstest.cpp --- a/autotests/server/collectionstatisticstest.cpp +++ b/autotests/server/collectionstatisticstest.cpp @@ -59,6 +59,7 @@ { Q_OBJECT + DbInitializer *dbInitializer = nullptr; public: CollectionStatisticsTest() { @@ -71,25 +72,27 @@ qWarning() << "Server exception: " << e.what(); qFatal("Fake Akonadi Server failed to start up, aborting test"); } + + dbInitializer = new DbInitializer; } ~CollectionStatisticsTest() { + delete dbInitializer; FakeAkonadiServer::instance()->quit(); } private Q_SLOTS: void testPrefetch_data() { - DbInitializer initializer; - initializer.createResource("testresource"); - auto col1 = initializer.createCollection("col1"); - initializer.createItem("item1", col1); - initializer.createItem("item2", col1); - auto col2 = initializer.createCollection("col2"); + dbInitializer->createResource("testresource"); + auto col1 = dbInitializer->createCollection("col1"); + dbInitializer->createItem("item1", col1); + dbInitializer->createItem("item2", col1); + auto col2 = dbInitializer->createCollection("col2"); // empty - auto col3 = initializer.createCollection("col3"); - initializer.createItem("item3", col3); + auto col3 = dbInitializer->createCollection("col3"); + dbInitializer->createItem("item3", col3); QTest::addColumn("collection"); QTest::addColumn("calculationsCount"); @@ -120,12 +123,12 @@ void testCalculateStats() { - DbInitializer initializer; - initializer.createResource("testresource"); - auto col = initializer.createCollection("col1"); - initializer.createItem("item1", col); - initializer.createItem("item2", col); - initializer.createItem("item3", col); + dbInitializer->cleanup(); + dbInitializer->createResource("testresource"); + auto col = dbInitializer->createCollection("col1"); + dbInitializer->createItem("item1", col); + dbInitializer->createItem("item2", col); + dbInitializer->createItem("item3", col); IntrospectableCollectionStatistics cs(false); auto stats = cs.statistics(col); @@ -137,12 +140,12 @@ void testSeenChanged() { - DbInitializer initializer; - initializer.createResource("testresource"); - auto col = initializer.createCollection("col1"); - initializer.createItem("item1", col); - initializer.createItem("item2", col); - initializer.createItem("item3", col); + dbInitializer->cleanup(); + dbInitializer->createResource("testresource"); + auto col = dbInitializer->createCollection("col1"); + dbInitializer->createItem("item1", col); + dbInitializer->createItem("item2", col); + dbInitializer->createItem("item3", col); IntrospectableCollectionStatistics cs(false); auto stats = cs.statistics(col); @@ -166,12 +169,12 @@ QCOMPARE(stats.size, 0); } -void testItemAdded() -{ - DbInitializer initializer; - initializer.createResource("testresource"); - auto col = initializer.createCollection("col1"); - initializer.createItem("item1", col); + void testItemAdded() + { + dbInitializer->cleanup(); + dbInitializer->createResource("testresource"); + auto col = dbInitializer->createCollection("col1"); + dbInitializer->createItem("item1", col); IntrospectableCollectionStatistics cs(false); auto stats = cs.statistics(col); diff --git a/autotests/server/createhandlertest.cpp b/autotests/server/createhandlertest.cpp --- a/autotests/server/createhandlertest.cpp +++ b/autotests/server/createhandlertest.cpp @@ -88,10 +88,11 @@ << TestScenario::create(5, TestScenario::ServerCmd, resp) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateCollectionResponsePtr::create()); + Protocol::FetchCollectionsResponse collection(*resp); + collection.setMimeTypes({}); // CREATE ntf does not contain mimetypes and attrs + collection.setAttributes({}); auto notification = Protocol::CollectionChangeNotificationPtr::create(*notificationTemplate); - notification->setId(8); - notification->setRemoteId(QStringLiteral("")); - notification->setRemoteRevision(QStringLiteral("")); + notification->setCollection(std::move(collection)); QTest::newRow("create collection") << scenarios << notification; } @@ -123,10 +124,12 @@ << TestScenario::create(5, TestScenario::ServerCmd, resp) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateCollectionResponsePtr::create()); + Protocol::FetchCollectionsResponse collection(*resp); + collection.setMimeTypes({}); // CREATE ntf does not contain mimetypes and attrs + collection.setAttributes({}); auto notification = Protocol::CollectionChangeNotificationPtr::create(*notificationTemplate); - notification->setId(9); - notification->setRemoteId(QStringLiteral("")); - notification->setRemoteRevision(QStringLiteral("")); + notification->setCollection(std::move(collection)); + QTest::newRow("create collection with local override") << scenarios << notification; } @@ -152,12 +155,14 @@ << TestScenario::create(5, TestScenario::ServerCmd, resp) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateCollectionResponsePtr::create()); + Protocol::FetchCollectionsResponse collection(*resp); + collection.setMimeTypes({}); // CREATE ntf does not contain mimetypes and attrs + collection.setAttributes({}); auto notification = Protocol::CollectionChangeNotificationPtr::create(*notificationTemplate); notification->setSessionId("akonadi_fake_resource_0"); notification->setParentCollection(0); - notification->setId(10); - notification->setRemoteId(QStringLiteral("")); - notification->setRemoteRevision(QStringLiteral("")); + notification->setCollection(std::move(collection)); + QTest::newRow("create top-level collection") << scenarios << notification; } @@ -177,6 +182,11 @@ 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()); diff --git a/autotests/server/dbconfigtest.cpp b/autotests/server/dbconfigtest.cpp --- a/autotests/server/dbconfigtest.cpp +++ b/autotests/server/dbconfigtest.cpp @@ -25,6 +25,7 @@ #include #include +#include "config-akonadi.h" #define QL1S(x) QStringLiteral(x) @@ -39,13 +40,13 @@ { // doesn't work, DbConfig has an internal singleton-like cache... //QFETCH( QString, driverName ); - const QString driverName(QL1S("QMYSQL")); + const QString driverName(QL1S(AKONADI_DATABASE_BACKEND)); // isolated config file to not conflict with a running instance akTestSetInstanceIdentifier(QL1S("unit-test")); { - QSettings s(StandardDirs::serverConfigFile(XdgBaseDirs::WriteOnly)); + QSettings s(StandardDirs::serverConfigFile(StandardDirs::WriteOnly)); s.setValue(QL1S("General/Driver"), driverName); } diff --git a/autotests/server/dbinitializer.h b/autotests/server/dbinitializer.h --- a/autotests/server/dbinitializer.h +++ b/autotests/server/dbinitializer.h @@ -37,6 +37,7 @@ bool ancestors = false, bool mimetypes = true, const QStringList &ancestorFetchScope = QStringList()); + Akonadi::Protocol::FetchItemsResponsePtr fetchResponse(const Akonadi::Server::PimItem &item); Akonadi::Server::Collection collection(const char *name); void cleanup(); diff --git a/autotests/server/dbinitializer.cpp b/autotests/server/dbinitializer.cpp --- a/autotests/server/dbinitializer.cpp +++ b/autotests/server/dbinitializer.cpp @@ -179,6 +179,28 @@ return resp; } +Akonadi::Protocol::FetchItemsResponsePtr DbInitializer::fetchResponse(const PimItem &item) +{ + auto resp = Akonadi::Protocol::FetchItemsResponsePtr::create(); + resp->setId(item.id()); + resp->setRevision(item.rev()); + resp->setMimeType(item.mimeType().name()); + resp->setRemoteId(item.remoteId()); + resp->setParentId(item.collectionId()); + resp->setSize(item.size()); + resp->setMTime(item.datetime()); + resp->setRemoteRevision(item.remoteRevision()); + resp->setGid(item.gid()); + const auto flags = item.flags(); + QVector flagNames; + for (const auto &flag : flags) { + flagNames.push_back(flag.name().toUtf8()); + } + resp->setFlags(flagNames); + + return resp; +} + Collection DbInitializer::collection(const char *name) { return Collection::retrieveByName(QLatin1String(name)); diff --git a/autotests/server/dbpopulator.xsl b/autotests/server/dbpopulator.xsl --- a/autotests/server/dbpopulator.xsl +++ b/autotests/server/dbpopulator.xsl @@ -315,8 +315,36 @@ + + +/* + * This is an auto-generated file. + * Do not edit! All changes made to it will be lost. + */ + +#ifndef AKONADI_SERVER_DBPOPULATOR_H +#define AKONADI_SERVER_DBPOPULATOR_H + +namespace Akonadi { +namespace Server { +class DbPopulator +{ +public: + DbPopulator(); + ~DbPopulator(); + + bool run(); + +}; + +} +} +#endif + + + /* * This is an auto-generated file. * Do not edit! All changes made to it will be lost. @@ -394,6 +422,7 @@ qDebug() << "Database successfully populated"; return true; } + diff --git a/autotests/server/fakeakonadiserver.h b/autotests/server/fakeakonadiserver.h --- a/autotests/server/fakeakonadiserver.h +++ b/autotests/server/fakeakonadiserver.h @@ -34,7 +34,7 @@ namespace Akonadi { namespace Server { -class NotificationCollector; +class InspectableNotificationCollector; class FakeSearchManager; class FakeDataStore; class FakeConnection; @@ -84,7 +84,7 @@ public: static FakeAkonadiServer *instance(); - ~FakeAkonadiServer(); + ~FakeAkonadiServer() override; /* Reimpl */ bool init() override; @@ -123,7 +123,7 @@ QEventLoop *mServerLoop = nullptr; - NotificationCollector *mNtfCollector = nullptr; + InspectableNotificationCollector *mNtfCollector = nullptr; QSharedPointer mNotificationSpy; bool mPopulateDb; diff --git a/autotests/server/fakeakonadiserver.cpp b/autotests/server/fakeakonadiserver.cpp --- a/autotests/server/fakeakonadiserver.cpp +++ b/autotests/server/fakeakonadiserver.cpp @@ -23,14 +23,15 @@ #include "fakesearchmanager.h" #include "fakeclient.h" #include "fakeitemretrievalmanager.h" +#include "inspectablenotificationcollector.h" #include #include #include #include #include +#include -#include #include #include #include @@ -46,7 +47,7 @@ using namespace Akonadi; using namespace Akonadi::Server; -Q_DECLARE_METATYPE(Akonadi::Server::NotificationCollector*) +Q_DECLARE_METATYPE(Akonadi::Server::InspectableNotificationCollector*) TestScenario TestScenario::create(qint64 tag, TestScenario::Action action, const Protocol::CommandPtr &response) @@ -118,6 +119,8 @@ qputenv("KDEHOME", qPrintable(basePath() + QLatin1String("/kdehome"))); mClient = new FakeClient; + + FakeDataStore::registerFactory(); } FakeAkonadiServer::~FakeAkonadiServer() @@ -130,7 +133,8 @@ QString FakeAkonadiServer::basePath() { - return QStringLiteral("/tmp/akonadiserver-test-%1").arg(QCoreApplication::instance()->applicationPid()); + return QStandardPaths::writableLocation(QStandardPaths::TempLocation) + + QStringLiteral("/akonadiserver-test-%1").arg(QCoreApplication::instance()->applicationPid()); } QString FakeAkonadiServer::socketFile() @@ -185,7 +189,7 @@ qputenv("XDG_DATA_HOME", qPrintable(QString(basePath() + QLatin1String("/local")))); qputenv("XDG_CONFIG_HOME", qPrintable(QString(basePath() + QLatin1String("/config")))); qputenv("AKONADI_INSTANCE", qPrintable(instanceName())); - QSettings settings(StandardDirs::serverConfigFile(XdgBaseDirs::WriteOnly), QSettings::IniFormat); + QSettings settings(StandardDirs::serverConfigFile(StandardDirs::WriteOnly), QSettings::IniFormat); settings.beginGroup(QStringLiteral("General")); settings.setValue(QStringLiteral("Driver"), QLatin1String("QSQLITE3")); settings.endGroup(); @@ -250,8 +254,7 @@ mCmdServer->close(); } - const QCommandLineParser &args = AkApplicationBase::instance()->commandLineArguments(); - if (!args.isSet(QStringLiteral("no-cleanup"))) { + if (!qEnvironmentVariableIsSet("AKONADI_TEST_NOCLEANUP")) { bool ok = QDir(basePath()).removeRecursively(); qDebug() << "Cleaned up" << basePath() << "success=" << ok; } else { @@ -277,15 +280,15 @@ void FakeAkonadiServer::newCmdConnection(quintptr socketDescriptor) { mConnection = new FakeConnection(socketDescriptor); - // Delete collection in its own thread - mNtfCollector->deleteLater(); // Connection is it's own thread, so we have to make sure we get collector // from DataStore of the Connection's thread, not ours + NotificationCollector *collector = nullptr; QMetaObject::invokeMethod(mConnection, "notificationCollector", Qt::BlockingQueuedConnection, - Q_RETURN_ARG(Akonadi::Server::NotificationCollector*, mNtfCollector)); + Q_RETURN_ARG(Akonadi::Server::NotificationCollector*, collector)); + mNtfCollector = dynamic_cast(collector); Q_ASSERT(mNtfCollector); - mNotificationSpy.reset(new QSignalSpy(mNtfCollector, &Server::NotificationCollector::notify)); + mNotificationSpy.reset(new QSignalSpy(mNtfCollector, &Server::InspectableNotificationCollector::notifySignal)); Q_ASSERT(mNotificationSpy->isValid()); } diff --git a/autotests/server/fakeclient.h b/autotests/server/fakeclient.h --- a/autotests/server/fakeclient.h +++ b/autotests/server/fakeclient.h @@ -37,7 +37,7 @@ public: explicit FakeClient(QObject *parent = nullptr); - ~FakeClient(); + ~FakeClient() override; void setScenarios(const TestScenario::List &scenarios); @@ -47,7 +47,7 @@ void run() override; private Q_SLOTS: - void dataAvailable(); + bool dataAvailable(); void readServerPart(); void writeClientPart(); void connectionLost(); diff --git a/autotests/server/fakeclient.cpp b/autotests/server/fakeclient.cpp --- a/autotests/server/fakeclient.cpp +++ b/autotests/server/fakeclient.cpp @@ -20,33 +20,36 @@ #include "fakeclient.h" #include "fakeakonadiserver.h" +#include #include +#include #include #include #include -#define CLIENT_COMPARE(actual, expected)\ +#define CLIENT_COMPARE(actual, expected, ...)\ do {\ if (!QTest::qCompare(actual, expected, #actual, #expected, __FILE__, __LINE__)) {\ - quit();\ - return;\ + mSocket->disconnectFromServer();\ + return __VA_ARGS__;\ }\ } while (0) -#define CLIENT_VERIFY(statement)\ +#define CLIENT_VERIFY(statement, ...)\ do {\ if (!QTest::qVerify((statement), #statement, "", __FILE__, __LINE__)) {\ - quit();\ - return;\ + mSocket->disconnectFromServer();\ + return __VA_ARGS__;\ }\ } while (0) using namespace Akonadi; using namespace Akonadi::Server; FakeClient::FakeClient(QObject *parent) : QThread(parent) + , mMutex(QMutex::Recursive) { moveToThread(this); } @@ -66,14 +69,16 @@ return mScenarios.isEmpty(); } -void FakeClient::dataAvailable() +bool FakeClient::dataAvailable() { QMutexLocker locker(&mMutex); - CLIENT_VERIFY(!mScenarios.isEmpty()); + CLIENT_VERIFY(!mScenarios.isEmpty(), false); readServerPart(); writeClientPart(); + + return true; } void FakeClient::readServerPart() @@ -97,18 +102,27 @@ expectedStream >> expectedTag; const auto expectedCommand = Protocol::deserialize(expectedStream.device()); - - while ((size_t)mSocket->bytesAvailable() < sizeof(qint64)) { - if (!mSocket->waitForReadyRead(5000)) { - qDebug() << "Timeout while waiting for server response"; - qDebug() << "Expected response:" << Protocol::debugString(expectedCommand); - QVERIFY(false); + try { + while ((size_t)mSocket->bytesAvailable() < sizeof(qint64)) { + Protocol::DataStream::waitForData(mSocket, 5000); } + } catch (const ProtocolException &e) { + qDebug() << "ProtocolException:" << e.what(); + qDebug() << "Expected response:" << Protocol::debugString(expectedCommand); + CLIENT_VERIFY(false); } + mStream >> actualTag; CLIENT_COMPARE(actualTag, expectedTag); - const auto actualCommand = Protocol::deserialize(mStream.device()); + Protocol::CommandPtr actualCommand; + try { + actualCommand = Protocol::deserialize(mStream.device()); + } catch (const ProtocolException &e) { + qDebug() << "Protocol exception:" << e.what(); + qDebug() << "Expected response:" << Protocol::debugString(expectedCommand); + CLIENT_VERIFY(false); + } if (actualCommand->type() != expectedCommand->type()) { qDebug() << "Actual command: " << Protocol::debugString(actualCommand); @@ -131,7 +145,7 @@ || mScenarios.at(0).action ==TestScenario::Quit); } else { // Server replied and there's nothing else to send, then quit - quit(); + mSocket->disconnectFromServer(); } } @@ -143,6 +157,7 @@ if (rule.action == TestScenario::ClientCmd) { mSocket->write(rule.data); + CLIENT_VERIFY(mSocket->waitForBytesWritten()); } else { const int timeout = rule.data.toInt(); QTest::qWait(timeout); @@ -163,14 +178,44 @@ { mSocket = new QLocalSocket(); mSocket->connectToServer(FakeAkonadiServer::socketFile()); - connect(mSocket, &QIODevice::readyRead, this, &FakeClient::dataAvailable); connect(mSocket, &QLocalSocket::disconnected, this, &FakeClient::connectionLost); + connect(mSocket, QOverload::of(&QLocalSocket::error), + this, [this](QLocalSocket::LocalSocketError error) { + qWarning() << "Client socket error: " << mSocket->errorString(); + connectionLost(); + QVERIFY(false); + }); if (!mSocket->waitForConnected()) { qFatal("Failed to connect to FakeAkonadiServer"); + QVERIFY(false); + return; } mStream.setDevice(mSocket); - exec(); + Q_FOREVER { + if (mSocket->state() != QLocalSocket::ConnectedState) { + connectionLost(); + break; + } + + { + QEventLoop loop; + connect(mSocket, &QLocalSocket::readyRead, &loop, &QEventLoop::quit); + connect(mSocket, &QLocalSocket::disconnected, &loop, &QEventLoop::quit); + loop.exec(); + } + + while (mSocket->bytesAvailable() > 0) { + if (mSocket->state() != QLocalSocket::ConnectedState) { + connectionLost(); + break; + } + + if(!dataAvailable()) { + break; + } + } + } mStream.setDevice(nullptr); mSocket->close(); @@ -182,6 +227,4 @@ { // Otherwise this is an error on server-side, we expected more talking CLIENT_VERIFY(isScenarioDone()); - - quit(); } diff --git a/autotests/server/fakeconnection.h b/autotests/server/fakeconnection.h --- a/autotests/server/fakeconnection.h +++ b/autotests/server/fakeconnection.h @@ -34,9 +34,7 @@ public: explicit FakeConnection(quintptr socketDescriptor, QObject *parent = nullptr); explicit FakeConnection(QObject *parent = nullptr); - virtual ~FakeConnection(); - - DataStore *storageBackend() override; + ~FakeConnection() override; public Q_SLOTS: Akonadi::Server::NotificationCollector *notificationCollector(); diff --git a/autotests/server/fakeconnection.cpp b/autotests/server/fakeconnection.cpp --- a/autotests/server/fakeconnection.cpp +++ b/autotests/server/fakeconnection.cpp @@ -37,16 +37,6 @@ FakeConnection::~FakeConnection() { - -} - -DataStore *FakeConnection::storageBackend() -{ - if (!m_backend) { - m_backend = static_cast(FakeDataStore::self()); - } - - return m_backend; } NotificationCollector *FakeConnection::notificationCollector() diff --git a/autotests/server/fakedatastore.h b/autotests/server/fakedatastore.h --- a/autotests/server/fakedatastore.h +++ b/autotests/server/fakedatastore.h @@ -25,13 +25,15 @@ namespace Akonadi { namespace Server { +class FakeDataStoreFactory; class FakeDataStore : public DataStore { Q_OBJECT - + friend class FakeDataStoreFactory; public: - virtual ~FakeDataStore(); - static DataStore *self(); + ~FakeDataStore() override; + + static void registerFactory(); bool init() override; @@ -77,7 +79,9 @@ virtual bool invalidateItemCache(const PimItem &item) override; - virtual bool appendCollection(Collection &collection) override; + virtual bool appendCollection(Collection &collection, + const QStringList &mimeTypes, + const QMap &attributes) override; virtual bool cleanupCollection(Collection &collection) override; virtual bool cleanupCollection_slow(Collection &collection) override; @@ -107,16 +111,15 @@ virtual bool addCollectionAttribute(const Collection &col, const QByteArray &key, - const QByteArray &value) override; + const QByteArray &value, + bool silent = false) override; virtual bool removeCollectionAttribute(const Collection &col, const QByteArray &key) override; virtual bool beginTransaction(const QString &name = QString()) override; virtual bool rollbackTransaction() override; virtual bool commitTransaction() override; - virtual NotificationCollector *notificationCollector() override; - void setPopulateDb(bool populate); protected: diff --git a/autotests/server/fakedatastore.cpp b/autotests/server/fakedatastore.cpp --- a/autotests/server/fakedatastore.cpp +++ b/autotests/server/fakedatastore.cpp @@ -20,7 +20,7 @@ #include "fakedatastore.h" #include "dbpopulator.h" #include "storage/dbconfig.h" -#include "storage/notificationcollector.h" +#include "inspectablenotificationcollector.h" #include "akonadischema.h" #include "storage/dbinitializer.h" @@ -38,32 +38,37 @@ Q_DECLARE_METATYPE(MimeType) Q_DECLARE_METATYPE(QList) +namespace Akonadi { +namespace Server { + +class FakeDataStoreFactory : public DataStoreFactory +{ +public: + FakeDataStoreFactory() = default; + ~FakeDataStoreFactory() override = default; + DataStore * createStore() override + { + return new FakeDataStore(); + } +}; + +} +} + Akonadi::Server::FakeDataStore::FakeDataStore() : DataStore() , mPopulateDb(true) { - notificationCollector(); + mNotificationCollector = std::make_unique(this); } FakeDataStore::~FakeDataStore() { } -NotificationCollector *FakeDataStore::notificationCollector() +void FakeDataStore::registerFactory() { - if (!mNotificationCollector) { - mNotificationCollector = new NotificationCollector(this); - } - return mNotificationCollector; -} - -DataStore *FakeDataStore::self() -{ - if (!sInstances.hasLocalData()) { - sInstances.setLocalData(new FakeDataStore()); - } - - return sInstances.localData(); + sFactory.reset(new FakeDataStoreFactory); } bool FakeDataStore::init() @@ -184,11 +189,14 @@ return DataStore::invalidateItemCache(item); } -bool FakeDataStore::appendCollection(Collection &collection) +bool FakeDataStore::appendCollection(Collection &collection, + const QStringList &mimeTypes, + const QMap &attributes) { mChanges.insert(QStringLiteral("appendCollection"), - QVariantList() << QVariant::fromValue(collection)); - return DataStore::appendCollection(collection); + QVariantList() << QVariant::fromValue(collection) + << mimeTypes << QVariant::fromValue(attributes)); + return DataStore::appendCollection(collection, mimeTypes, attributes); } bool FakeDataStore::cleanupCollection(Collection &collection) @@ -273,12 +281,13 @@ bool FakeDataStore::addCollectionAttribute(const Collection &col, const QByteArray &key, - const QByteArray &value) + const QByteArray &value, + bool silent) { mChanges.insert(QStringLiteral("addCollectionAttribute"), QVariantList() << QVariant::fromValue(col) - << key << value); - return DataStore::addCollectionAttribute(col, key, value); + << key << value << silent); + return DataStore::addCollectionAttribute(col, key, value, silent); } bool FakeDataStore::removeCollectionAttribute(const Collection &col, diff --git a/autotests/server/fakeitemretrievalmanager.cpp b/autotests/server/fakeitemretrievalmanager.cpp --- a/autotests/server/fakeitemretrievalmanager.cpp +++ b/autotests/server/fakeitemretrievalmanager.cpp @@ -37,7 +37,11 @@ void FakeItemRetrievalManager::requestItemDelivery(ItemRetrievalRequest *request) { +#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) + QMetaObject::invokeMethod(this, [this, request] { Q_EMIT requestFinished(request); }, Qt::QueuedConnection); +#else QMetaObject::invokeMethod(this, "requestFinished", Qt::QueuedConnection, Q_ARG(ItemRetrievalRequest*, request)); +#endif } diff --git a/autotests/server/fakesearchmanager.h b/autotests/server/fakesearchmanager.h --- a/autotests/server/fakesearchmanager.h +++ b/autotests/server/fakesearchmanager.h @@ -34,7 +34,7 @@ public: explicit FakeSearchManager(QObject *parent = nullptr); - virtual ~FakeSearchManager(); + ~FakeSearchManager() override; void registerInstance(const QString &id) override; void unregisterInstance(const QString &id) override; diff --git a/autotests/server/fetchhandlertest.cpp b/autotests/server/fetchhandlertest.cpp --- a/autotests/server/fetchhandlertest.cpp +++ b/autotests/server/fetchhandlertest.cpp @@ -63,8 +63,8 @@ Protocol::FetchItemsCommandPtr createCommand(const Scope &scope, const Protocol::ScopeContext &ctx = Protocol::ScopeContext()) { auto cmd = Protocol::FetchItemsCommandPtr::create(scope, ctx); - cmd->fetchScope().setFetch(Protocol::FetchScope::IgnoreErrors); - return cmd;; + cmd->fetchScope().setFetch(Protocol::ItemFetchScope::IgnoreErrors); + return cmd; } Protocol::FetchItemsResponsePtr createResponse(const PimItem &item) diff --git a/autotests/server/handlertest.cpp b/autotests/server/handlertest.cpp --- a/autotests/server/handlertest.cpp +++ b/autotests/server/handlertest.cpp @@ -23,21 +23,43 @@ #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" using namespace Akonadi; using namespace Akonadi::Server; -#define MAKE_CMD_ROW( command, class ) QTest::newRow(#command) << command << "Akonadi::Server::" #class; +#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"); + QTest::addColumn("className"); } void addAuthCommands() @@ -86,10 +108,10 @@ void testFindAuthenticatedCommand() { QFETCH(Protocol::Command::Type, command); - QFETCH(QString, className); + QFETCH(QByteArray, className); QScopedPointer handler(Handler::findHandlerForCommandAuthenticated(command)); QVERIFY(!handler.isNull()); - QCOMPARE(handler->metaObject()->className(), className.toLatin1().constData()); + QCOMPARE(QByteArray(typeid(*handler.data()).name()), className); } void testFindAuthenticatedCommandNegative_data() @@ -103,7 +125,7 @@ void testFindAuthenticatedCommandNegative() { QFETCH(Protocol::Command::Type, command); - QFETCH(QString, className); + QFETCH(QByteArray, className); QScopedPointer handler(Handler::findHandlerForCommandAuthenticated(command)); QVERIFY(handler.isNull()); @@ -118,11 +140,11 @@ void testFindNonAutenticatedCommand() { QFETCH(Protocol::Command::Type, command); - QFETCH(QString, className); + QFETCH(QByteArray, className); QScopedPointer handler(Handler::findHandlerForCommandNonAuthenticated(command)); QVERIFY(!handler.isNull()); - QCOMPARE(handler->metaObject()->className(), className.toLatin1().constData()); + QCOMPARE(QByteArray(typeid(*handler.data()).name()), className); } void testFindNonAutenticatedCommandNegative_data() @@ -136,7 +158,7 @@ void testFindNonAutenticatedCommandNegative() { QFETCH(Protocol::Command::Type, command); - QFETCH(QString, className); + QFETCH(QByteArray, className); QScopedPointer handler(Handler::findHandlerForCommandNonAuthenticated(command)); QVERIFY(handler.isNull()); @@ -151,11 +173,11 @@ void testFindAlwaysCommand() { QFETCH(Protocol::Command::Type, command); - QFETCH(QString, className); + QFETCH(QByteArray, className); QScopedPointer handler(Handler::findHandlerForCommandAlwaysAllowed(command)); QVERIFY(!handler.isNull()); - QCOMPARE(handler->metaObject()->className(), className.toLatin1().constData()); + QCOMPARE(QByteArray(typeid(*handler.data()).name()), className); } void testFindAlwaysCommandNegative_data() @@ -169,7 +191,7 @@ void testFindAlwaysCommandNegative() { QFETCH(Protocol::Command::Type, command); - QFETCH(QString, className); + QFETCH(QByteArray, className); QScopedPointer handler(Handler::findHandlerForCommandAlwaysAllowed(command)); QVERIFY(handler.isNull()); diff --git a/src/server/handler/searchresult.h b/autotests/server/inspectablenotificationcollector.h copy from src/server/handler/searchresult.h copy to autotests/server/inspectablenotificationcollector.h --- a/src/server/handler/searchresult.h +++ b/autotests/server/inspectablenotificationcollector.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Daniel Vrátil + * Copyright (C) 2018 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 @@ -17,32 +17,29 @@ * */ -#ifndef AKONADI_SEARCHRESULT_H -#define AKONADI_SEARCHRESULT_H +#ifndef AKONADI_INSPECTABLENOTIFICATIONCOLLECTOR_H_ +#define AKONADI_INSPECTABLENOTIFICATIONCOLLECTOR_H_ -#include "handler.h" +#include "storage/notificationcollector.h" -namespace Akonadi -{ -namespace Server -{ +namespace Akonadi { +namespace Server { -/** - @ingroup akonadi_server_handler - - Handler for the search_result command -*/ -class SearchResult : public Handler +class DataStore; +class InspectableNotificationCollector : public QObject, public NotificationCollector { Q_OBJECT public: - bool parseStream() override; + InspectableNotificationCollector(DataStore *store); + ~InspectableNotificationCollector() override = default; + + void notify(Protocol::ChangeNotificationList ntfs) override; -private: - bool fail(const QByteArray &searchId, const QString &error); +Q_SIGNALS: + void notifySignal(const Akonadi::Protocol::ChangeNotificationList &msgs); }; } // namespace Server } // namespace Akonadi -#endif // AKONADI_SEARCHRESULT_H +#endif diff --git a/autotests/server/dbpopulator.h b/autotests/server/inspectablenotificationcollector.cpp rename from autotests/server/dbpopulator.h rename to autotests/server/inspectablenotificationcollector.cpp --- a/autotests/server/dbpopulator.h +++ b/autotests/server/inspectablenotificationcollector.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 Daniel Vrátil + * Copyright (C) 2018 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 @@ -17,24 +17,18 @@ * */ -#ifndef AKONADI_SERVER_DBPOPULATOR_H -#define AKONADI_SERVER_DBPOPULATOR_H +#include "inspectablenotificationcollector.h" +using namespace Akonadi; +using namespace Akonadi::Server; -namespace Akonadi { -namespace Server { - -class DbPopulator +InspectableNotificationCollector::InspectableNotificationCollector(DataStore *store) + : NotificationCollector(store) { -public: - DbPopulator(); - ~DbPopulator(); - - bool run(); - -}; - -} } -#endif // AKONADI_SERVER_DBPOPULATOR_H +void InspectableNotificationCollector::notify(Protocol::ChangeNotificationList ntfs) +{ + Q_EMIT notifySignal(ntfs); + NotificationCollector::notify(ntfs); +} diff --git a/autotests/server/linkhandlertest.cpp b/autotests/server/linkhandlertest.cpp --- a/autotests/server/linkhandlertest.cpp +++ b/autotests/server/linkhandlertest.cpp @@ -62,6 +62,16 @@ 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() { @@ -79,9 +89,9 @@ auto notification = Protocol::ItemChangeNotificationPtr::create(); notification->setOperation(Protocol::ItemChangeNotification::Link); notification->setItems({ - { 1, QLatin1String("A"), QString(), QLatin1String("application/octet-stream") }, - { 2, QLatin1String("B"), QString(), QLatin1String("application/octet-stream") }, - { 3, QLatin1String("C"), QString(), QLatin1String("application/octet-stream") } }); + 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()); @@ -93,7 +103,7 @@ QTest::newRow("normal") << scenarios << notification << false; notification = Protocol::ItemChangeNotificationPtr::create(*notification); - notification->setItems({ { 4, QLatin1String("D"), QString(), QLatin1String("application/octet-stream") } }); + 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)) @@ -162,9 +172,9 @@ Q_FOREACH (const auto &entity, notification->items()) { if (expectFail) { - QVERIFY(!Collection::relatesToPimItem(notification->parentCollection(), entity.id)); + QVERIFY(!Collection::relatesToPimItem(notification->parentCollection(), entity.id())); } else { - QVERIFY(Collection::relatesToPimItem(notification->parentCollection(), entity.id)); + QVERIFY(Collection::relatesToPimItem(notification->parentCollection(), entity.id())); } } } @@ -185,9 +195,9 @@ auto notification = Protocol::ItemChangeNotificationPtr::create(); notification->setOperation(Protocol::ItemChangeNotification::Unlink); notification->setItems({ - { 1, QLatin1String("A"), QString(), QLatin1String("application/octet-stream") }, - { 2, QLatin1String("B"), QString(), QLatin1String("application/octet-stream") }, - { 3, QLatin1String("C"), QString(), QLatin1String("application/octet-stream") } }); + 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()); @@ -198,7 +208,7 @@ QTest::newRow("normal") << scenarios << notification << false; notification = Protocol::ItemChangeNotificationPtr::create(*notification); - notification->setItems({ { 4, QLatin1String("D"), QString(), QLatin1String("application/octet-stream") } }); + 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)) @@ -267,9 +277,9 @@ Q_FOREACH (const auto &entity, notification->items()) { if (expectFail) { - QVERIFY(Collection::relatesToPimItem(notification->parentCollection(), entity.id)); + QVERIFY(Collection::relatesToPimItem(notification->parentCollection(), entity.id())); } else { - QVERIFY(!Collection::relatesToPimItem(notification->parentCollection(), entity.id)); + QVERIFY(!Collection::relatesToPimItem(notification->parentCollection(), entity.id())); } } } diff --git a/autotests/server/modifyhandlertest.cpp b/autotests/server/modifyhandlertest.cpp --- a/autotests/server/modifyhandlertest.cpp +++ b/autotests/server/modifyhandlertest.cpp @@ -62,13 +62,18 @@ auto notificationTemplate = Protocol::CollectionChangeNotificationPtr::create(); notificationTemplate->setOperation(Protocol::CollectionChangeNotification::Modify); - notificationTemplate->setId(5); - notificationTemplate->setRemoteId(QStringLiteral("ColD")); - notificationTemplate->setRemoteRevision(QStringLiteral("")); 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")); @@ -80,7 +85,7 @@ 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")); } { @@ -92,10 +97,14 @@ << 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(std::move(collection)); auto unsubscribeNotification = Protocol::CollectionChangeNotificationPtr::create(*notificationTemplate); unsubscribeNotification->setOperation(Protocol::CollectionChangeNotification::Unsubscribe); + unsubscribeNotification->setCollection(collection); QTest::newRow("disable collection") << scenarios << Protocol::ChangeNotificationList{ notification, unsubscribeNotification} << QVariant::fromValue(false); } @@ -110,8 +119,10 @@ 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); } @@ -127,10 +138,17 @@ << 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); } @@ -166,11 +184,11 @@ QCOMPARE(*recvNtf, *expNtf); const auto notification = receivedNotifications.at(i).staticCast(); if (notification->changedParts().contains("NAME")) { - Collection col = Collection::retrieveById(notification->id()); + 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->id()); + 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; diff --git a/autotests/server/movehandlertest.cpp b/autotests/server/movehandlertest.cpp --- a/autotests/server/movehandlertest.cpp +++ b/autotests/server/movehandlertest.cpp @@ -53,6 +53,16 @@ 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() { @@ -80,7 +90,7 @@ << TestScenario::create(5, TestScenario::ServerCmd, Protocol::MoveItemsResponsePtr::create()); auto notification = Protocol::ItemChangeNotificationPtr::create(*notificationTemplate); - notification->setItems({ { 1, QStringLiteral("A"), QString(), QStringLiteral("application/octet-stream") } }); + notification->setItems({ fetchResponse(1, QStringLiteral("A"), QString(), QStringLiteral("application/octet-stream")) }); QTest::newRow("move item") << scenarios << Protocol::ChangeNotificationList{ notification } << QVariant::fromValue(destCol.id()); @@ -96,8 +106,8 @@ auto notification = Protocol::ItemChangeNotificationPtr::create(*notificationTemplate); notification->setItems({ - { 3, QStringLiteral("C"), QString(), QStringLiteral("application/octet-stream") }, - { 2, QStringLiteral("B"), QString(), QStringLiteral("application/octet-stream") } }); + 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()); @@ -132,7 +142,7 @@ QCOMPARE(notification->parentDestCollection(), newValue.toInt()); Q_FOREACH (const auto &ntfItem, notification->items()) { - const PimItem item = PimItem::retrieveById(ntfItem.id); + const PimItem item = PimItem::retrieveById(ntfItem.id()); QCOMPARE(item.collectionId(), newValue.toInt()); } } diff --git a/autotests/server/notificationmanagertest.cpp b/autotests/server/notificationmanagertest.cpp --- a/autotests/server/notificationmanagertest.cpp +++ b/autotests/server/notificationmanagertest.cpp @@ -104,6 +104,17 @@ typedef QList NSList; + + Protocol::FetchItemsResponse itemResponse(qint64 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 testSourceFilter_data() { @@ -135,7 +146,7 @@ << false; itemMsg = Protocol::ItemChangeNotificationPtr::create(*itemMsg); - itemMsg->setItems({ { 1, QString(), QString(), QStringLiteral("message/rfc822") } }); + itemMsg->setItems({ itemResponse(1, QString(), QString(), QStringLiteral("message/rfc822")) }); QTest::newRow("monitorAll vs notification with one item") << true << EmptyList(Entity::Id) @@ -181,8 +192,10 @@ // Simulate adding a new resource auto colMsg = Protocol::CollectionChangeNotificationPtr::create(); colMsg->setOperation(Protocol::CollectionChangeNotification::Add); - colMsg->setId(1); - colMsg->setRemoteId(QStringLiteral("imap://user@some.domain/")); + Protocol::FetchCollectionsResponse col; + col.setId(1); + col.setRemoteId(QStringLiteral("imap://user@some.domain/")); + colMsg->setCollection(std::move(col)); colMsg->setParentCollection(0); colMsg->setSessionId("akonadi_imap_resource_0"); colMsg->setResource("akonadi_imap_resource_0"); @@ -203,7 +216,7 @@ itemMsg->setParentCollection(1); itemMsg->setParentDestCollection(2); itemMsg->setSessionId("kmail"); - itemMsg->setItems({ { 10, QStringLiteral("123"), QStringLiteral("1"), QStringLiteral("message/rfc822") } }); + itemMsg->setItems({ itemResponse(10, QStringLiteral("123"), QStringLiteral("1"), QStringLiteral("message/rfc822")) }); QTest::newRow("inter-resource move, source source") << false << EmptyList(Entity::Id) @@ -242,8 +255,8 @@ itemMsg->setParentDestCollection(2); itemMsg->setSessionId("kmail"); itemMsg->setItems({ - { 10, QStringLiteral("123"), QStringLiteral("1"), QStringLiteral("message/rfc822") }, - { 11, QStringLiteral("456"), QStringLiteral("1"), QStringLiteral("message/rfc822") } }); + itemResponse(10, QStringLiteral("123"), QStringLiteral("1"), QStringLiteral("message/rfc822")), + itemResponse(11, QStringLiteral("456"), QStringLiteral("1"), QStringLiteral("message/rfc822")) }); QTest::newRow("intra-resource move, owning resource") << false << EmptyList(Entity::Id) @@ -274,7 +287,7 @@ itemMsg->setSessionId("randomSession"); itemMsg->setResource("randomResource"); itemMsg->setParentCollection(1); - itemMsg->setItems({ { 10, QString(), QString(), QStringLiteral("message/rfc822") } }); + itemMsg->setItems({ itemResponse(10, QString(), QString(), QStringLiteral("message/rfc822")) }); QTest::newRow("new mail for mailfilter or maildispatcher") << false << List(Entity::Id, 0) @@ -289,8 +302,12 @@ tagMsg->setOperation(Protocol::TagChangeNotification::Remove); tagMsg->setSessionId("randomSession"); tagMsg->setResource("akonadi_random_resource_0"); - tagMsg->setId(1); - tagMsg->setRemoteId(QStringLiteral("TAG")); + { + Protocol::FetchTagsResponse tagMsgTag; + tagMsgTag.setId(1); + tagMsgTag.setRemoteId("TAG"); + tagMsg->setTag(std::move(tagMsgTag)); + } QTest::newRow("Tag removal - resource notification - matching resource source") << false << EmptyList(Entity::Id) @@ -314,8 +331,12 @@ tagMsg = Protocol::TagChangeNotificationPtr::create(); tagMsg->setOperation(Protocol::TagChangeNotification::Remove); tagMsg->setSessionId("randomSession"); - tagMsg->setId(1); - tagMsg->setRemoteId(QStringLiteral("TAG")); + { + Protocol::FetchTagsResponse tagMsgTag; + tagMsgTag.setId(1); + tagMsgTag.setRemoteId("TAG"); + tagMsg->setTag(std::move(tagMsgTag)); + } QTest::newRow("Tag removal - client notification - client source") << false << EmptyList(Entity::Id) diff --git a/autotests/server/partstreamertest.cpp b/autotests/server/partstreamertest.cpp --- a/autotests/server/partstreamertest.cpp +++ b/autotests/server/partstreamertest.cpp @@ -50,7 +50,7 @@ PartStreamerTest() { // Set a very small treshold for easier testing - const QString serverConfigFile = StandardDirs::serverConfigFile(XdgBaseDirs::ReadWrite); + const QString serverConfigFile = StandardDirs::serverConfigFile(StandardDirs::ReadWrite); QSettings settings(serverConfigFile, QSettings::IniFormat); settings.setValue(QStringLiteral("General/SizeThreshold"), 5); diff --git a/autotests/server/relationhandlertest.cpp b/autotests/server/relationhandlertest.cpp --- a/autotests/server/relationhandlertest.cpp +++ b/autotests/server/relationhandlertest.cpp @@ -87,17 +87,23 @@ QScopedPointer initializer; - Protocol::RelationChangeNotificationPtr relationNotification(const Protocol::RelationChangeNotificationPtr ¬ificationTemplate, + Protocol::RelationChangeNotificationPtr relationNotification(Protocol::RelationChangeNotification::Operation op, const PimItem &item1, const PimItem &item2, const QString &rid, const QString &type = QStringLiteral("type")) { - auto notification = Protocol::RelationChangeNotificationPtr::create(*notificationTemplate); - notification->setLeftItem(item1.id()); - notification->setRightItem(item2.id()); - notification->setRemoteId(rid); - notification->setType(type); + auto notification = Protocol::RelationChangeNotificationPtr::create(); + notification->setOperation(op); + notification->setSessionId(FakeAkonadiServer::instanceName().toLatin1()); + Protocol::FetchRelationsResponse relation; + relation.setLeft(item1.id()); + relation.setLeftMimeType(item1.mimeType().name().toLatin1()); + relation.setRight(item2.id()); + relation.setRightMimeType(item2.mimeType().name().toLatin1()); + relation.setRemoteId(rid.toLatin1()); + relation.setType(type.toLatin1()); + notification->setRelation(std::move(relation)); return notification; } @@ -114,10 +120,6 @@ QTest::addColumn("expectedRelations"); QTest::addColumn("expectedNotifications"); - auto notificationTemplate = Protocol::RelationChangeNotificationPtr::create(); - notificationTemplate->setOperation(Protocol::RelationChangeNotification::Add); - notificationTemplate->setSessionId(FakeAkonadiServer::instanceName().toLatin1()); - { TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario() @@ -136,12 +138,10 @@ itemNotification->setSessionId(FakeAkonadiServer::instanceName().toLatin1()); itemNotification->setResource("testresource"); itemNotification->setParentCollection(col1.id()); - itemNotification->setItems({ - { item1.id(), item1.remoteId(), QString(), item1.mimeType().name() }, - { item2.id(), item2.remoteId(), QString(), item2.mimeType().name() } }); + itemNotification->setItems({ *initializer->fetchResponse(item1), *initializer->fetchResponse(item2) }); itemNotification->setAddedRelations({ Protocol::ItemChangeNotification::Relation(item1.id(), item2.id(), QStringLiteral("type")) }); - const auto notification = relationNotification(notificationTemplate, item1, item2, rel.remoteId()); + const auto notification = relationNotification(Protocol::RelationChangeNotification::Add, item1, item2, rel.remoteId()); QTest::newRow("uid create relation") << scenarios << (Relation::List() << rel) << (Protocol::ChangeNotificationList() << notification << itemNotification); } @@ -198,10 +198,6 @@ rel2.setRelationType(RelationType::retrieveByName(QStringLiteral("type2"))); QVERIFY(rel2.insert()); - auto notificationTemplate = Protocol::RelationChangeNotificationPtr::create(); - notificationTemplate->setOperation(Protocol::RelationChangeNotification::Remove); - notificationTemplate->setSessionId(FakeAkonadiServer::instanceName().toLatin1()); - QTest::addColumn("scenarios"); QTest::addColumn("expectedRelations"); QTest::addColumn("expectedNotifications"); @@ -216,12 +212,10 @@ itemNotification->setSessionId(FakeAkonadiServer::instanceName().toLatin1()); itemNotification->setResource("testresource"); itemNotification->setParentCollection(col1.id()); - itemNotification->setItems({ - { item1.id(), item1.remoteId(), QString(), item1.mimeType().name() }, - { item2.id(), item2.remoteId(), QString(), item2.mimeType().name() } }); + itemNotification->setItems({ *initializer->fetchResponse(item1), *initializer->fetchResponse(item2) }); itemNotification->setRemovedRelations({ Protocol::ItemChangeNotification::Relation(item1.id(), item2.id(), QStringLiteral("type")) }); - const auto notification = relationNotification(notificationTemplate, item1, item2, rel.remoteId()); + const auto notification = relationNotification(Protocol::RelationChangeNotification::Remove, item1, item2, rel.remoteId()); QTest::newRow("uid remove relation") << scenarios << (Relation::List() << rel2) << (Protocol::ChangeNotificationList() << notification << itemNotification); } @@ -237,12 +231,10 @@ itemNotification->setSessionId(FakeAkonadiServer::instanceName().toLatin1()); itemNotification->setResource("testresource"); itemNotification->setParentCollection(col1.id()); - itemNotification->setItems({ - { item1.id(), item1.remoteId(), QString(), item1.mimeType().name() }, - { item2.id(), item2.remoteId(), QString(), item2.mimeType().name() } }); + itemNotification->setItems({ *initializer->fetchResponse(item1), *initializer->fetchResponse(item2) }); itemNotification->setRemovedRelations({ Protocol::ItemChangeNotification::Relation(item1.id(), item2.id(), QStringLiteral("type2")) }); - const auto notification = relationNotification(notificationTemplate, item1, item2, rel.remoteId(), QStringLiteral("type2")); + const auto notification = relationNotification(Protocol::RelationChangeNotification::Remove, item1, item2, rel.remoteId(), QStringLiteral("type2")); QTest::newRow("uid remove relation without type") << scenarios << Relation::List() << (Protocol::ChangeNotificationList() << notification << itemNotification); } diff --git a/autotests/server/taghandlertest.cpp b/autotests/server/taghandlertest.cpp --- a/autotests/server/taghandlertest.cpp +++ b/autotests/server/taghandlertest.cpp @@ -83,7 +83,7 @@ } Protocol::FetchTagsResponsePtr createResponse(const Tag &tag, const QByteArray &remoteId = QByteArray(), - const Protocol::Attributes &attrs = Protocol::Attributes()) + const Protocol::Attributes &attrs = Protocol::Attributes()) { auto resp = Protocol::FetchTagsResponsePtr::create(tag.id()); resp->setGid(tag.gid().toUtf8()); @@ -145,7 +145,7 @@ auto notification = Protocol::TagChangeNotificationPtr::create(); notification->setOperation(Protocol::TagChangeNotification::Add); notification->setSessionId(FakeAkonadiServer::instanceName().toLatin1()); - notification->setId(1); + notification->setTag(Protocol::FetchTagsResponse(1)); QTest::newRow("uid create relation") << scenarios << QVector{ { tag, { attribute } } } @@ -184,7 +184,7 @@ auto notification = Protocol::TagChangeNotificationPtr::create(); notification->setOperation(Protocol::TagChangeNotification::Add); notification->setSessionId(FakeAkonadiServer::instanceName().toLatin1()); - notification->setId(2); + notification->setTag(Protocol::FetchTagsResponse(2)); QTest::newRow("create child tag") << scenarios << QVector{ { tag, { attribute } } } @@ -207,7 +207,7 @@ QCOMPARE(receivedNotifications.size(), expectedNotifications.count()); for (int i = 0; i < expectedNotifications.size(); i++) { QCOMPARE(*receivedNotifications.at(i), *expectedNotifications.at(i)); - ids << Protocol::cmdCast(receivedNotifications.at(i)).id(); + ids << Protocol::cmdCast(receivedNotifications.at(i)).tag().id(); } SelectQueryBuilder qb; @@ -278,7 +278,7 @@ auto notification = Protocol::TagChangeNotificationPtr::create(); notification->setOperation(Protocol::TagChangeNotification::Modify); notification->setSessionId(FakeAkonadiServer::instanceName().toLatin1()); - notification->setId(tag.id()); + notification->setTag(Protocol::FetchTagsResponse(tag.id())); QTest::newRow("uid store name") << scenarios << (Tag::List() << tag) << (Protocol::ChangeNotificationList() << notification); } @@ -345,15 +345,19 @@ auto itemUntaggedNtf = Protocol::ItemChangeNotificationPtr::create(); itemUntaggedNtf->setOperation(Protocol::ItemChangeNotification::ModifyTags); itemUntaggedNtf->setSessionId(FakeAkonadiServer::instanceName().toLatin1()); - itemUntaggedNtf->setItems({ { pimItem.id(), pimItem.remoteId(), QString(), pimItem.mimeType().name() } }); + itemUntaggedNtf->setItems({ *initializer->fetchResponse(pimItem) }); itemUntaggedNtf->setResource(res2.name().toLatin1()); itemUntaggedNtf->setParentCollection(col.id()); itemUntaggedNtf->setRemovedTags(QSet() << tag.id()); auto tagRemoveNtf = Protocol::TagChangeNotificationPtr::create(); tagRemoveNtf->setOperation(Protocol::TagChangeNotification::Remove); tagRemoveNtf->setSessionId(FakeAkonadiServer::instanceName().toLatin1()); - tagRemoveNtf->setId(tag.id()); + Protocol::FetchTagsResponse ntfTag; + ntfTag.setId(tag.id()); + ntfTag.setGid("gid"); + ntfTag.setType("PLAIN"); + tagRemoveNtf->setTag(std::move(ntfTag)); QTest::newRow("uid store unset last rid") << scenarios << Tag::List() << (Protocol::ChangeNotificationList() << itemUntaggedNtf << tagRemoveNtf); } @@ -372,6 +376,8 @@ QCOMPARE(receivedNotifications.size(), expectedNotifications.count()); for (int i = 0; i < receivedNotifications.size(); i++) { + qDebug() << Protocol::debugString(receivedNotifications.at(i)); + qDebug() << Protocol::debugString(expectedNotifications.at(i)); QCOMPARE(*receivedNotifications.at(i), *expectedNotifications.at(i)); } @@ -423,17 +429,21 @@ ntf->setSessionId(FakeAkonadiServer::instanceName().toLatin1()); auto res1Ntf = Protocol::TagChangeNotificationPtr::create(*ntf); - res1Ntf->setId(tag.id()); - res1Ntf->setRemoteId(rel1.remoteId()); + Protocol::FetchTagsResponse res1NtfTag; + res1NtfTag.setId(tag.id()); + res1NtfTag.setRemoteId(rel1.remoteId().toLatin1()); + res1Ntf->setTag(std::move(res1NtfTag)); res1Ntf->setResource(res1.name().toLatin1()); auto res2Ntf = Protocol::TagChangeNotificationPtr::create(*ntf); - res2Ntf->setId(tag.id()); - res2Ntf->setRemoteId(rel2.remoteId()); + Protocol::FetchTagsResponse res2NtfTag; + res2NtfTag.setId(tag.id()); + res2NtfTag.setRemoteId(rel2.remoteId().toLatin1()); + res2Ntf->setTag(std::move(res2NtfTag)); res2Ntf->setResource(res2.name().toLatin1()); auto clientNtf = Protocol::TagChangeNotificationPtr::create(*ntf); - clientNtf->setId(tag.id()); + clientNtf->setTag(Protocol::FetchTagsResponse(tag.id())); QTest::newRow("uid remove") << scenarios << Tag::List() << (Protocol::ChangeNotificationList() << res1Ntf << res2Ntf << clientNtf); } diff --git a/cmake/modules/AkonadiMacros.cmake b/cmake/modules/AkonadiMacros.cmake --- a/cmake/modules/AkonadiMacros.cmake +++ b/cmake/modules/AkonadiMacros.cmake @@ -21,27 +21,87 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# Internal server macros +function(akonadi_run_xsltproc) + if (NOT XSLTPROC_EXECUTABLE) + message(FATAL_ERROR "xsltproc executable not found but needed by AKONADI_RUN_XSLTPROC()") + endif() + + set(options ) + set(oneValueArgs XSL XML CLASSNAME BASENAME) + set(multiValueArgs ) + cmake_parse_arguments(XSLT "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + if (NOT XSLT_XSL) + message(FATAL_ERROR "Required argument XSL missing in AKONADI_RUN_XSLTPROC() call") + endif() + if (NOT XSLT_XML) + message(FATAL_ERROR "Required argument XML missing in AKONADI_RUN_XSLTPROC() call") + endif() + if (NOT XSLT_BASENAME) + message(FATAL_ERROR "Required argument BASENAME missing in AKONADI_RUN_XSLTPROC() call") + endif() + + # Workaround xsltproc struggling with spaces in filepaths on Windows + file(RELATIVE_PATH xsl_relpath ${CMAKE_CURRENT_BINARY_DIR} ${XSLT_XSL}) + file(RELATIVE_PATH xml_relpath ${CMAKE_CURRENT_BINARY_DIR} ${XSLT_XML}) + + set(extra_params ) + if (XSLT_CLASSNAME) + set(extra_params --stringparam className ${XSLT_CLASSNAME}) + endif() + + add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${XSLT_BASENAME}.h + ${CMAKE_CURRENT_BINARY_DIR}/${XSLT_BASENAME}.cpp + COMMAND ${XSLTPROC_EXECUTABLE} + --output ${XSLT_BASENAME}.h + --stringparam code header + --stringparam fileName ${XSLT_BASENAME} + ${extra_params} + ${xsl_relpath} + ${xml_relpath} + COMMAND ${XSLTPROC_EXECUTABLE} + --output ${XSLT_BASENAME}.cpp + --stringparam code source + --stringparam fileName ${XSLT_BASENAME} + ${extra_params} + ${xsl_relpath} + ${xml_relpath} + DEPENDS ${XSLT_XSL} + ${XSLT_XML} + ) + + set_property(SOURCE + ${CMAKE_CURRENT_BINARY_DIR}/${XSLT_BASENAME}.cpp + ${CMAKE_CURRENT_BINARY_DIR}/${XSLT_BASENAME}.h + PROPERTY SKIP_AUTOMOC TRUE + ) +endfunction() + macro(akonadi_generate_schema _schemaXml _className _fileBaseName) -add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${_fileBaseName}.h - ${CMAKE_CURRENT_BINARY_DIR}/${_fileBaseName}.cpp - COMMAND ${XSLTPROC_EXECUTABLE} - --output ${CMAKE_CURRENT_BINARY_DIR}/${_fileBaseName}.h - --stringparam code header - --stringparam className ${_className} - --stringparam fileName ${_fileBaseName} - ${Akonadi_SOURCE_DIR}/src/server/storage/schema.xsl - ${_schemaXml} - COMMAND ${XSLTPROC_EXECUTABLE} - --output ${CMAKE_CURRENT_BINARY_DIR}/${_fileBaseName}.cpp - --stringparam code source - --stringparam className ${_className} - --stringparam fileName ${_fileBaseName} - ${Akonadi_SOURCE_DIR}/src/server/storage/schema.xsl - ${_schemaXml} - DEPENDS ${Akonadi_SOURCE_DIR}/src/server/storage/schema.xsl - ${Akonadi_SOURCE_DIR}/src/server/storage/schema-header.xsl - ${Akonadi_SOURCE_DIR}/src/server/storage/schema-source.xsl - ${_schemaXml} -) + if (NOT XSLTPROC_EXECUTABLE) + message(FATAL_ERROR "xsltproc executable not found but needed by AKONADI_GENERATE_SCHEMA()") + endif() + + akonadi_run_xsltproc( + XSL ${Akonadi_SOURCE_DIR}/src/server/storage/schema.xsl + XML ${_schemaXml} + CLASSNAME ${_className} + BASENAME ${_fileBaseName} + ) endmacro() + +function(akonadi_add_xmllint_test) + if (NOT XMLLINT_EXECUTABLE) + message(FATAL_ERROR "xmllint executable not found but needed by AKONADI_ADD_XMLLINT_SCHEMA()") + endif() + + set(options ) + set(oneValueArgs XML XSD) + set(multiValueArgs ) + cmake_parse_arguments(TEST "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + file(RELATIVE_PATH xsd_relpath ${CMAKE_CURRENT_BINARY_DIR} ${TEST_XSD}) + file(RELATIVE_PATH xml_relpath ${CMAKE_CURRENT_BINARY_DIR} ${TEST_XML}) + add_test(${TEST_UNPARSED_ARGUMENTS} ${XMLLINT_EXECUTABLE} --noout --schema ${xsd_relpath} ${xml_relpath}) +endfunction() diff --git a/cmake/modules/FindXsltproc.cmake b/cmake/modules/FindXsltproc.cmake deleted file mode 100644 --- a/cmake/modules/FindXsltproc.cmake +++ /dev/null @@ -1,49 +0,0 @@ -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions -# are met: -# -# 1. Redistributions of source code must retain the copyright -# notice, this list of conditions and the following disclaimer. -# 2. Redistributions in binary form must reproduce the copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# 3. The name of the author may not be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR -# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, -# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# -# Find xsltproc executable and provide a macro to generate D-Bus interfaces. -# -# The following variables are defined : -# XSLTPROC_EXECUTABLE - path to the xsltproc executable -# Xsltproc_FOUND - true if the program was found -# -find_program(XSLTPROC_EXECUTABLE xsltproc DOC "Path to the xsltproc executable") -mark_as_advanced(XSLTPROC_EXECUTABLE) - -if(XSLTPROC_EXECUTABLE) - set(Xsltproc_FOUND TRUE) - - # Macro to generate a D-Bus interface description from a KConfigXT file - macro(kcfg_generate_dbus_interface _kcfg _name) - add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${_name}.xml - COMMAND ${XSLTPROC_EXECUTABLE} --stringparam interfaceName ${_name} - ${CMAKE_SOURCE_DIR}/akonadi/kcfg2dbus.xsl - ${_kcfg} - > ${CMAKE_CURRENT_BINARY_DIR}/${_name}.xml - DEPENDS ${CMAKE_SOURCE_DIR}/akonadi/kcfg2dbus.xsl - ${_kcfg} - ) - endmacro() -endif() - diff --git a/metainfo.yaml b/metainfo.yaml --- a/metainfo.yaml +++ b/metainfo.yaml @@ -1,5 +1,6 @@ name: akonadi description: PIM Storage Framework +maintainer: dvratil group: kdepim platforms: - name: Linux diff --git a/src/agentbase/agentbase.h b/src/agentbase/agentbase.h --- a/src/agentbase/agentbase.h +++ b/src/agentbase/agentbase.h @@ -464,17 +464,19 @@ template static int init(int argc, char **argv) { + // Disable session management qunsetenv("SESSION_MANAGER"); QApplication app(argc, argv); + debugAgent(argc, argv); const QString id = parseArguments(argc, argv); T *r = new T(id); // check if T also inherits AgentBase::Observer and // if it does, automatically register it on itself Observer *observer = dynamic_cast(r); - if (observer != 0) { + if (observer != nullptr) { r->registerObserver(observer); } return init(r); @@ -490,22 +492,22 @@ * - 2 - Broken * - 3 - NotConfigured */ - virtual int status() const; + Q_REQUIRED_RESULT virtual int status() const; /** * This method returns an i18n'ed description of the current status code. */ - virtual QString statusMessage() const; + Q_REQUIRED_RESULT virtual QString statusMessage() const; /** * This method returns the current progress of the agent in percentage. */ - virtual int progress() const; + Q_REQUIRED_RESULT virtual int progress() const; /** * This method returns an i18n'ed description of the current progress. */ - virtual QString progressMessage() const; + Q_REQUIRED_RESULT virtual QString progressMessage() const; public Q_SLOTS: /** @@ -524,7 +526,7 @@ /** * This method returns the windows id, which should be used for dialogs. */ - WId winIdForDialogs() const; + Q_REQUIRED_RESULT WId winIdForDialogs() const; #ifdef Q_OS_WIN /** @@ -537,7 +539,7 @@ /** * Returns the instance identifier of this agent. */ - QString identifier() const; + Q_REQUIRED_RESULT QString identifier() const; /** * This method is called when the agent is removed from @@ -571,7 +573,7 @@ * * @since 4.3 */ - QString agentName() const; + Q_REQUIRED_RESULT QString agentName() const; Q_SIGNALS: /** @@ -754,6 +756,8 @@ void setTemporaryOffline(int makeOnlineInSeconds = 300); //@cond PRIVATE + static void debugAgent(int argc, char **argv); + AgentBasePrivate *d_ptr; explicit AgentBase(AgentBasePrivate *d, const QString &id); friend class ObserverV2; diff --git a/src/agentbase/agentbase.cpp b/src/agentbase/agentbase.cpp --- a/src/agentbase/agentbase.cpp +++ b/src/agentbase/agentbase.cpp @@ -58,6 +58,12 @@ # include // for dumping memory information #endif +#ifdef Q_OS_WIN +#include +#include +#include +#endif + using namespace Akonadi; static AgentBase *sAgentBase = nullptr; @@ -377,7 +383,7 @@ q->error(i18n("Unable to register object at dbus: %1", KDBusConnectionPool::threadConnection().lastError().message())); } - mSettings = new QSettings(QStringLiteral("%1/agent_config_%2").arg(StandardDirs::saveDir("config"), mId), QSettings::IniFormat); + mSettings = new QSettings(ServerManager::agentConfigFilePath(mId), QSettings::IniFormat); mChangeRecorder = new ChangeRecorder(q); mChangeRecorder->setObjectName(QStringLiteral("AgentBaseChangeRecorder")); @@ -420,9 +426,9 @@ this, &AgentBasePrivate::collectionUnsubscribed); connect(q, SIGNAL(status(int,QString)), q, SLOT(slotStatus(int,QString))); - connect(q, SIGNAL(percent(int)), q, SLOT(slotPercent(int))); - connect(q, SIGNAL(warning(QString)), q, SLOT(slotWarning(QString))); - connect(q, SIGNAL(error(QString)), q, SLOT(slotError(QString))); + connect(q, &AgentBase::percent, q, [this](int value) { slotPercent(value); }); + connect(q, &AgentBase::warning, q, [this](const QString &str) { slotWarning(str); }); + connect(q, &AgentBase::error, q, [this](const QString &str) { slotError(str); }); mPowerInterface = new QDBusInterface(QStringLiteral("org.kde.Solid.PowerManagement"), QStringLiteral("/org/kde/Solid/PowerManagement/Actions/SuspendSession"), @@ -642,7 +648,7 @@ AgentBase::ObserverV4 *observer4 = dynamic_cast(mObserver); if (observer4) { - observer4->tagRemoved(tag);; + observer4->tagRemoved(tag); } else { changeProcessed(); } @@ -907,6 +913,20 @@ delete d_ptr; } +void AgentBase::debugAgent(int argc, char **argv) +{ +#ifdef Q_OS_WIN + if (qEnvironmentVariableIsSet("AKONADI_DEBUG_WAIT")) { + if (QByteArray(argv[0]).endsWith(qgetenv("AKONADI_DEBUG_WAIT") + ".exe")) { + while (!IsDebuggerPresent()) { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + DebugBreak(); + } + } +#endif +} + QString AgentBase::parseArguments(int argc, char **argv) { Q_UNUSED(argc); @@ -1034,7 +1054,7 @@ if (!d->mTemporaryOfflineTimer) { d->mTemporaryOfflineTimer = new QTimer(d); d->mTemporaryOfflineTimer->setSingleShot(true); - connect(d->mTemporaryOfflineTimer, SIGNAL(timeout()), this, SLOT(slotTemporaryOfflineTimeout())); + connect(d->mTemporaryOfflineTimer, &QTimer::timeout, this, [this]() { d_ptr->slotTemporaryOfflineTimeout(); }); } d->mTemporaryOfflineTimer->setInterval(makeOnlineInSeconds * 1000); d->mTemporaryOfflineTimer->start(); @@ -1110,6 +1130,8 @@ if (d->mSettings) { d->mChangeRecorder->setConfig(nullptr); d->mSettings->sync(); + delete d->mSettings; + d->mSettings = nullptr; } delete d->mEventLoopLocker; diff --git a/src/agentbase/agentbase_p.h b/src/agentbase/agentbase_p.h --- a/src/agentbase/agentbase_p.h +++ b/src/agentbase/agentbase_p.h @@ -43,7 +43,7 @@ public: explicit AgentBasePrivate(AgentBase *parent); - virtual ~AgentBasePrivate(); + ~AgentBasePrivate() override; void init(); virtual void delayedInit(); @@ -92,7 +92,7 @@ int mStatusCode; QString mStatusMessage; - uint mProgress; + int mProgress; QString mProgressMessage; bool mNeedsNetwork; diff --git a/src/agentbase/agentfactory.h b/src/agentbase/agentfactory.h --- a/src/agentbase/agentfactory.h +++ b/src/agentbase/agentfactory.h @@ -54,7 +54,7 @@ */ explicit AgentFactoryBase(const char *catalogName, QObject *parent = nullptr); - virtual ~AgentFactoryBase(); + ~AgentFactoryBase() override; public Q_SLOTS: /** diff --git a/src/agentbase/preprocessorbase.h b/src/agentbase/preprocessorbase.h --- a/src/agentbase/preprocessorbase.h +++ b/src/agentbase/preprocessorbase.h @@ -164,7 +164,7 @@ /** * Destroys the preprocessor base agent. */ - virtual ~PreprocessorBase(); + ~PreprocessorBase() override; private: //@cond PRIVATE diff --git a/src/agentbase/resourcebase.h b/src/agentbase/resourcebase.h --- a/src/agentbase/resourcebase.h +++ b/src/agentbase/resourcebase.h @@ -107,7 +107,8 @@ * Note that these three functions don't get the full payload of the items by default, * you need to change the item fetch scope of the change recorder to fetch the full * payload. This can be expensive with big payloads, though.
- * Once you have handled changes in these methods, call changeCommitted(). + * Once you have handled changes in itemAdded() and itemChanged(), call changeCommitted(). + * Once you have handled changes in itemRemoved(), call changeProcessed(); * These methods are called whenever a local item related to this resource is * added, modified or deleted. They are only called if the resource is online, otherwise * all changes are recorded and replayed as soon the resource is online again. @@ -136,7 +137,8 @@ * - collectionAdded() * - collectionChanged() * - collectionRemoved() - * Once you have handled changes in these methods call changeCommitted(). + * Once you have handled changes in collectionAdded() and collectionChanged(), call changeCommitted(). + * Once you have handled changes in collectionRemoved(), call changeProcessed(); * These methods are called whenever a local collection related to this resource is * added, modified or deleted. They are only called if the resource is online, otherwise * all changes are recorded and replayed as soon the resource is online again. @@ -179,10 +181,12 @@ template static int init(int argc, char **argv) { + // Disable session management qunsetenv("SESSION_MANAGER"); QApplication app(argc, argv); + debugAgent(argc, argv); const QString id = parseArguments(argc, argv); T *r = new T(id); @@ -204,7 +208,7 @@ /** * Returns the name of the resource. */ - QString name() const; + Q_REQUIRED_RESULT QString name() const; /** * Enable or disable automatic progress reporting. By default, it is enabled. @@ -395,7 +399,7 @@ /** * Destroys the base resource. */ - ~ResourceBase(); + ~ResourceBase() override; /** * Call this method from retrieveItem() once the result is available. @@ -590,7 +594,7 @@ * * @param mode Item merging mode (see ItemCreateJob for details on item merging) * @see Akonadi::ItemSync::MergeMode - * @ince 4.14.11 + * @since 4.14.11 */ void setItemMergingMode(ItemSync::MergeMode mode); @@ -733,8 +737,7 @@ void cancelTask(const QString &error); /** - * Stops the execution of the current task and continues with the next one. - * The current task will be tried again later. + * Suspends the execution of the current task and tries again to execute it. * * This can be used to delay the task processing until the resource has reached a safe * state, e.g. login to a server succeeded. diff --git a/src/agentbase/resourcebase.cpp b/src/agentbase/resourcebase.cpp --- a/src/agentbase/resourcebase.cpp +++ b/src/agentbase/resourcebase.cpp @@ -1287,6 +1287,10 @@ void ResourceBase::deferTask() { Q_D(ResourceBase); + qCDebug(AKONADIAGENTBASE_LOG) << "Deferring task" << d->scheduler->currentTask(); + // Deferring a CollectionSync is just not implemented. + // We'd need to d->mItemSyncer->rollback() but also to NOT call taskDone in slotItemSyncDone() here... + Q_ASSERT(!d->mItemSyncer); d->scheduler->deferTask(); } diff --git a/src/agentbase/resourcescheduler_p.h b/src/agentbase/resourcescheduler_p.h --- a/src/agentbase/resourcescheduler_p.h +++ b/src/agentbase/resourcescheduler_p.h @@ -75,7 +75,6 @@ Task() : serial(++latestSerial) , type(Invalid) - , receiver(0) { } qint64 serial; diff --git a/src/agentbase/resourcesettings.h b/src/agentbase/resourcesettings.h --- a/src/agentbase/resourcesettings.h +++ b/src/agentbase/resourcesettings.h @@ -1,5 +1,5 @@ /* - Copyright (C) 2010-2017 Laurent Montel + Copyright (C) 2010-2018 Laurent Montel 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 @@ -34,7 +34,7 @@ private: ResourceSettings(); - virtual ~ResourceSettings(); + ~ResourceSettings() override; static ResourceSettings *mSelf; }; diff --git a/src/agentbase/resourcesettings.cpp b/src/agentbase/resourcesettings.cpp --- a/src/agentbase/resourcesettings.cpp +++ b/src/agentbase/resourcesettings.cpp @@ -1,5 +1,5 @@ /* - Copyright (C) 2010-2017 Laurent Montel + Copyright (C) 2010-2018 Laurent Montel 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 diff --git a/src/agentbase/transportresourcebase.cpp b/src/agentbase/transportresourcebase.cpp --- a/src/agentbase/transportresourcebase.cpp +++ b/src/agentbase/transportresourcebase.cpp @@ -51,7 +51,7 @@ { if (job->error()) { const Item::Id id = job->property("id").toLongLong(); - emit transportResult(id, (int)TransportResourceBase::TransportFailed, job->errorText()); + emit transportResult(id, static_cast(TransportResourceBase::TransportFailed), job->errorText()); return; } @@ -76,7 +76,7 @@ TransportResult result, const QString &message) { - emit d->transportResult(item.id(), (int)result, message); + emit d->transportResult(item.id(), static_cast(result), message); } #include "moc_transportresourcebase_p.cpp" diff --git a/src/agentserver/CMakeLists.txt b/src/agentserver/CMakeLists.txt --- a/src/agentserver/CMakeLists.txt +++ b/src/agentserver/CMakeLists.txt @@ -50,4 +50,4 @@ endif() install(TARGETS akonadi_agent_server - ${KF5_INSTALL_TARGETS_DEFAULT_ARGS}}) + ${KF5_INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/src/agentserver/agentpluginloader.h b/src/agentserver/agentpluginloader.h --- a/src/agentserver/agentpluginloader.h +++ b/src/agentserver/agentpluginloader.h @@ -39,7 +39,7 @@ @return the plugin for @p pluginName or 0 if the plugin is not found. */ - QPluginLoader *load(const QString &pluginName); + Q_REQUIRED_RESULT QPluginLoader *load(const QString &pluginName); private: Q_DISABLE_COPY(AgentPluginLoader) diff --git a/src/agentserver/agentpluginloader.cpp b/src/agentserver/agentpluginloader.cpp --- a/src/agentserver/agentpluginloader.cpp +++ b/src/agentserver/agentpluginloader.cpp @@ -19,11 +19,8 @@ #include "agentpluginloader.h" #include "akonadiagentserver_debug.h" -#include #include -using namespace Akonadi; - AgentPluginLoader::AgentPluginLoader() { } @@ -36,23 +33,17 @@ QPluginLoader *AgentPluginLoader::load(const QString &pluginName) { - const QString pluginFile = XdgBaseDirs::findPluginFile(pluginName); - if (pluginFile.isEmpty()) { - qCWarning(AKONADIAGENTSERVER_LOG) << "plugin file:" << pluginName << "not found!"; - return nullptr; - } - - QPluginLoader *loader = m_pluginLoaders.value(pluginFile); + QPluginLoader *loader = m_pluginLoaders.value(pluginName); if (loader) { return loader; } else { - loader = new QPluginLoader(pluginFile); + loader = new QPluginLoader(pluginName); if (!loader->load()) { qCWarning(AKONADIAGENTSERVER_LOG) << "Failed to load agent: " << loader->errorString(); delete loader; return nullptr; } - m_pluginLoaders.insert(pluginFile, loader); + m_pluginLoaders.insert(pluginName, loader); return loader; } } diff --git a/src/agentserver/agentserver.cpp b/src/agentserver/agentserver.cpp --- a/src/agentserver/agentserver.cpp +++ b/src/agentserver/agentserver.cpp @@ -21,7 +21,6 @@ #include "akonadiagentserver_debug.h" #include "agentthread.h" -#include #include #include diff --git a/src/akonadicontrol/agentinstance.h b/src/akonadicontrol/agentinstance.h --- a/src/akonadicontrol/agentinstance.h +++ b/src/akonadicontrol/agentinstance.h @@ -55,7 +55,7 @@ } /** Set/get the unique identifier of this AgentInstance */ - QString identifier() const + Q_REQUIRED_RESULT QString identifier() const { return mIdentifier; } @@ -65,32 +65,32 @@ mIdentifier = identifier; } - QString agentType() const + Q_REQUIRED_RESULT QString agentType() const { return mType; } - int status() const + Q_REQUIRED_RESULT int status() const { return mStatus; } - QString statusMessage() const + Q_REQUIRED_RESULT QString statusMessage() const { return mStatusMessage; } - int progress() const + Q_REQUIRED_RESULT int progress() const { return mPercent; } - bool isOnline() const + Q_REQUIRED_RESULT bool isOnline() const { return mOnline; } - QString resourceName() const + Q_REQUIRED_RESULT QString resourceName() const { return mResourceName; } @@ -101,17 +101,17 @@ virtual void restartWhenIdle() = 0; virtual void configure(qlonglong windowId) = 0; - bool hasResourceInterface() const + Q_REQUIRED_RESULT bool hasResourceInterface() const { return mResourceInterface; } - bool hasAgentInterface() const + Q_REQUIRED_RESULT bool hasAgentInterface() const { return mAgentControlInterface && mAgentStatusInterface; } - bool hasPreprocessorInterface() const + Q_REQUIRED_RESULT bool hasPreprocessorInterface() const { return mPreprocessorInterface; } @@ -180,12 +180,12 @@ org::freedesktop::Akonadi::Resource *mResourceInterface = nullptr; org::freedesktop::Akonadi::Preprocessor *mPreprocessorInterface = nullptr; - int mStatus; + int mStatus = 0; QString mStatusMessage; - int mPercent; + int mPercent = 0; QString mResourceName; - bool mOnline; - bool mPendingQuit; + bool mOnline = false; + bool mPendingQuit = false; }; diff --git a/src/akonadicontrol/agentinstance.cpp b/src/akonadicontrol/agentinstance.cpp --- a/src/akonadicontrol/agentinstance.cpp +++ b/src/akonadicontrol/agentinstance.cpp @@ -23,20 +23,9 @@ #include "agenttype.h" #include "agentmanager.h" -#include - AgentInstance::AgentInstance(AgentManager *manager) : QObject(manager) , mManager(manager) - , mAgentControlInterface(nullptr) - , mAgentStatusInterface(nullptr) - , mSearchInterface(nullptr) - , mResourceInterface(nullptr) - , mPreprocessorInterface(nullptr) - , mStatus(0) - , mPercent(0) - , mOnline(false) - , mPendingQuit(false) { } diff --git a/src/akonadicontrol/agentmanager.cpp b/src/akonadicontrol/agentmanager.cpp --- a/src/akonadicontrol/agentmanager.cpp +++ b/src/akonadicontrol/agentmanager.cpp @@ -32,7 +32,6 @@ #include "akonadicontrol_debug.h" #include -#include #include #include #include @@ -71,7 +70,7 @@ qFatal("akonadiserver already running!"); } - const QSettings settings(Akonadi::StandardDirs::agentConfigFile(Akonadi::XdgBaseDirs::ReadOnly), QSettings::IniFormat); + const QSettings settings(Akonadi::StandardDirs::agentsConfigFile(Akonadi::StandardDirs::ReadOnly), QSettings::IniFormat); mAgentServerEnabled = settings.value(QStringLiteral("AgentServer/Enabled"), enableAgentServerDefault).toBool(); QStringList serviceArgs; @@ -540,7 +539,7 @@ } if (agentInfo.launchMethod == AgentType::Process) { - const QString executable = Akonadi::XdgBaseDirs::findExecutableFile(agentInfo.exec); + const QString executable = Akonadi::StandardDirs::findExecutable(agentInfo.exec); if (executable.isEmpty()) { qCWarning(AKONADICONTROL_LOG) << "Executable" << agentInfo.exec << "for agent" << agentInfo.identifier << "could not be found!"; continue; @@ -560,15 +559,15 @@ QStringList AgentManager::pluginInfoPathList() { - return Akonadi::XdgBaseDirs::findAllResourceDirs("data", QStringLiteral("akonadi/agents")); + return Akonadi::StandardDirs::locateAllResourceDirs(QStringLiteral("akonadi/agents")); } void AgentManager::load() { org::freedesktop::Akonadi::ResourceManager resmanager(Akonadi::DBus::serviceName(Akonadi::DBus::Server), QStringLiteral("/ResourceManager"), QDBusConnection::sessionBus(), this); const QStringList knownResources = resmanager.resourceInstances(); - QSettings file(Akonadi::StandardDirs::agentConfigFile(Akonadi::XdgBaseDirs::ReadOnly), QSettings::IniFormat); + QSettings file(Akonadi::StandardDirs::agentsConfigFile(Akonadi::StandardDirs::ReadOnly), QSettings::IniFormat); file.beginGroup(QStringLiteral("Instances")); const QStringList entries = file.childGroups(); for (int i = 0; i < entries.count(); ++i) { @@ -609,7 +608,7 @@ void AgentManager::save() { - QSettings file(Akonadi::StandardDirs::agentConfigFile(Akonadi::XdgBaseDirs::WriteOnly), QSettings::IniFormat); + QSettings file(Akonadi::StandardDirs::agentsConfigFile(Akonadi::StandardDirs::WriteOnly), QSettings::IniFormat); for (const AgentType &info : qAsConst(mAgents)) { info.save(&file); diff --git a/src/akonadicontrol/agentprocessinstance.h b/src/akonadicontrol/agentprocessinstance.h --- a/src/akonadicontrol/agentprocessinstance.h +++ b/src/akonadicontrol/agentprocessinstance.h @@ -34,7 +34,7 @@ public: explicit AgentProcessInstance(AgentManager *manager); - + ~AgentProcessInstance() override {} bool start(const AgentType &agentInfo) override; void quit() override; void cleanup() override; diff --git a/src/akonadicontrol/agentprocessinstance.cpp b/src/akonadicontrol/agentprocessinstance.cpp --- a/src/akonadicontrol/agentprocessinstance.cpp +++ b/src/akonadicontrol/agentprocessinstance.cpp @@ -22,14 +22,14 @@ #include "agenttype.h" #include "processcontrol.h" #include "akonadicontrol_debug.h" +#include "private/standarddirs_p.h" -#include +#include using namespace Akonadi; AgentProcessInstance::AgentProcessInstance(AgentManager *manager) : AgentInstance(manager) - , mController(nullptr) { } @@ -46,7 +46,7 @@ agentInfo.launchMethod == AgentType::Launcher); const QString executable = (agentInfo.launchMethod == AgentType::Process) - ? XdgBaseDirs::findExecutableFile(agentInfo.exec) : agentInfo.exec; + ? Akonadi::StandardDirs::findExecutable(agentInfo.exec) : agentInfo.exec; if (executable.isEmpty()) { qCWarning(AKONADICONTROL_LOG) << "Unable to find agent executable" << agentInfo.exec; @@ -62,7 +62,7 @@ } else { Q_ASSERT(agentInfo.launchMethod == AgentType::Launcher); const QStringList arguments = QStringList() << executable << identifier(); - const QString agentLauncherExec = XdgBaseDirs::findExecutableFile(QStringLiteral("akonadi_agent_launcher")); + const QString agentLauncherExec = Akonadi::StandardDirs::findExecutable(QStringLiteral("akonadi_agent_launcher")); mController->start(agentLauncherExec, arguments); } return true; diff --git a/src/akonadicontrol/agentthreadinstance.h b/src/akonadicontrol/agentthreadinstance.h --- a/src/akonadicontrol/agentthreadinstance.h +++ b/src/akonadicontrol/agentthreadinstance.h @@ -30,6 +30,7 @@ Q_OBJECT public: explicit AgentThreadInstance(AgentManager *manager); + ~AgentThreadInstance() override {} bool start(const AgentType &agentInfo) override; void quit() override; diff --git a/src/akonadicontrol/agenttype.h b/src/akonadicontrol/agenttype.h --- a/src/akonadicontrol/agenttype.h +++ b/src/akonadicontrol/agenttype.h @@ -39,7 +39,7 @@ public: AgentType(); - bool load(const QString &fileName, AgentManager *manager); + Q_REQUIRED_RESULT bool load(const QString &fileName, AgentManager *manager); void save(QSettings *config) const; QString identifier; diff --git a/src/akonadicontrol/agenttype.cpp b/src/akonadicontrol/agenttype.cpp --- a/src/akonadicontrol/agenttype.cpp +++ b/src/akonadicontrol/agenttype.cpp @@ -101,7 +101,7 @@ // load instance count if needed if (!capabilities.contains(CapabilityUnique)) { - QSettings agentrc(Akonadi::StandardDirs::agentConfigFile(XdgBaseDirs::ReadOnly), QSettings::IniFormat); + QSettings agentrc(StandardDirs::agentsConfigFile(StandardDirs::ReadOnly), QSettings::IniFormat); instanceCounter = agentrc.value(QStringLiteral("InstanceCounters/%1/InstanceCounter") .arg(identifier), 0).toInt(); } diff --git a/src/akonadicontrol/processcontrol.h b/src/akonadicontrol/processcontrol.h --- a/src/akonadicontrol/processcontrol.h +++ b/src/akonadicontrol/processcontrol.h @@ -91,7 +91,7 @@ /** * Returns true if the process is currently running. */ - bool isRunning() const; + Q_REQUIRED_RESULT bool isRunning() const; /** * Sets the time (in msecs) we wait for the process to shut down before we send terminate/kill signals. diff --git a/src/akonadicontrol/processcontrol.cpp b/src/akonadicontrol/processcontrol.cpp --- a/src/akonadicontrol/processcontrol.cpp +++ b/src/akonadicontrol/processcontrol.cpp @@ -238,24 +238,28 @@ qPrintable(mApplication), qPrintable(mProcess.errorString())); Q_EMIT unableToStart(); return; - } - -#ifdef Q_OS_UNIX - else { + } else { QString agentDebug = QString::fromLocal8Bit(qgetenv("AKONADI_DEBUG_WAIT")); - pid_t pid = mProcess.pid(); + auto pid = mProcess.processId(); if (!agentDebug.isEmpty() && mApplication.contains(agentDebug)) { qCDebug(AKONADICONTROL_LOG); qCDebug(AKONADICONTROL_LOG) << "============================================================"; qCDebug(AKONADICONTROL_LOG) << "ProcessControl: Suspending process" << mApplication; +#ifdef Q_OS_UNIX qCDebug(AKONADICONTROL_LOG) << "'gdb --pid" << pid << "' to debug"; qCDebug(AKONADICONTROL_LOG) << "'kill -SIGCONT" << pid << "' to continue"; + kill(pid, SIGSTOP); +#endif +#ifdef Q_OS_WIN + qCDebug(AKONADICONTROL_LOG) << "PID:" << pid; + qCDebug(AKONADICONTROL_LOG) << "Process is waiting for a debugger..."; + // the agent process will wait for a debugger to be attached in AgentBase::debugAgent() +#endif qCDebug(AKONADICONTROL_LOG) << "============================================================"; qCDebug(AKONADICONTROL_LOG); - kill(pid, SIGSTOP); + } } -#endif } void ProcessControl::resetCrashCount() diff --git a/src/akonadictl/CMakeLists.txt b/src/akonadictl/CMakeLists.txt --- a/src/akonadictl/CMakeLists.txt +++ b/src/akonadictl/CMakeLists.txt @@ -16,6 +16,7 @@ add_executable(akonadictl ${akonadictl_SRCS}) set_target_properties(akonadictl PROPERTIES OUTPUT_NAME akonadictl) +ecm_mark_nongui_executable(akonadictl) target_link_libraries(akonadictl akonadi_shared KF5AkonadiPrivate @@ -25,5 +26,5 @@ ) install(TARGETS akonadictl - ${KF5_INSTALL_TARGETS_DEFAULT_ARGS}} + ${KF5_INSTALL_TARGETS_DEFAULT_ARGS} ) diff --git a/src/akonadictl/akonadistarter.h b/src/akonadictl/akonadistarter.h --- a/src/akonadictl/akonadistarter.h +++ b/src/akonadictl/akonadistarter.h @@ -27,13 +27,13 @@ Q_OBJECT public: explicit AkonadiStarter(QObject *parent = nullptr); - bool start(bool verbose); + Q_REQUIRED_RESULT bool start(bool verbose); private Q_SLOTS: void serviceOwnerChanged(const QString &name, const QString &oldOwner, const QString &newOwner); private: - bool mRegistered; + bool mRegistered = false; }; #endif diff --git a/src/akonadictl/akonadistarter.cpp b/src/akonadictl/akonadistarter.cpp --- a/src/akonadictl/akonadistarter.cpp +++ b/src/akonadictl/akonadistarter.cpp @@ -35,7 +35,6 @@ AkonadiStarter::AkonadiStarter(QObject *parent) : QObject(parent) - , mRegistered(false) { QDBusServiceWatcher *watcher = new QDBusServiceWatcher(Akonadi::DBus::serviceName(Akonadi::DBus::ControlLock), QDBusConnection::sessionBus(), diff --git a/src/akonadictl/main.cpp b/src/akonadictl/main.cpp --- a/src/akonadictl/main.cpp +++ b/src/akonadictl/main.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include @@ -37,18 +38,13 @@ #include #include -#include #include #include - -#if defined(HAVE_UNISTD_H) && !defined(Q_WS_WIN) -#include -#else -#define WIN32_LEAN_AND_MEAN -#include -#endif +#include #include +#include +#include static bool startServer(bool verbose) { @@ -103,21 +99,20 @@ if (!pluginOverride.isEmpty()) { searchMethods << pluginOverride; } else { - const QStringList dirs = Akonadi::XdgBaseDirs::findPluginDirs(); + const QStringList dirs = QCoreApplication::libraryPaths(); for (const QString &pluginDir : dirs) { - QDir dir(pluginDir + QLatin1String("/akonadi")); - const QStringList desktopFiles = dir.entryList(QStringList() << QStringLiteral("*.desktop"), QDir::Files); - for (const QString &desktopFileName : desktopFiles) { - QSettings desktop(pluginDir + QLatin1String("/akonadi/") + desktopFileName, QSettings::IniFormat); - desktop.beginGroup(QStringLiteral("Desktop Entry")); - if (desktop.value(QStringLiteral("Type")).toString() != QLatin1String("AkonadiSearchPlugin")) { + QDir dir(pluginDir + QLatin1String("/akonadi/")); + const QStringList pluginFiles = dir.entryList(QDir::Files); + for (const QString &pluginFileName : pluginFiles) { + QPluginLoader loader(dir.absolutePath() + QLatin1Char('/') + pluginFileName); + const QVariantMap metadata = loader.metaData().value(QStringLiteral("MetaData")).toVariant().toMap(); + if (metadata.value(QStringLiteral("X-Akonadi-PluginType")).toString() != QLatin1String("SearchPlugin")) { continue; } - if (!desktop.value(QStringLiteral("X-Akonadi-LoadByDefault"), true).toBool()) { + if (!metadata.value(QStringLiteral("X-Akonadi-LoadByDefault"), true).toBool()) { continue; } - - searchMethods << desktop.value(QStringLiteral("Name")).toString(); + searchMethods << metadata.value(QStringLiteral("X-Akonadi-PluginName")).toString(); } } } @@ -129,7 +124,7 @@ static bool checkAvailableAgentTypes() { - const QStringList dirs = Akonadi::XdgBaseDirs::findAllResourceDirs("data", QStringLiteral("akonadi/agents")); + const auto dirs = Akonadi::StandardDirs::locateAllResourceDirs(QStringLiteral("akonadi/agents")); QStringList types; for (const QString &pluginDir : dirs) { QDir dir(pluginDir); @@ -172,7 +167,11 @@ bool running; }; QVector instances { { QStringLiteral("(default)"), instanceRunning() } }; - const QDir instanceDir(Akonadi::XdgBaseDirs::saveDir("config", QStringLiteral("akonadi/instance"))); +#ifdef Q_OS_WIN + const QDir instanceDir(QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QStringLiteral("/akonadi/config/instance")); +#else + const QDir instanceDir(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + QStringLiteral("/akonadi/instance")); +#endif if (instanceDir.exists()) { const auto list = instanceDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); for (const auto &e : list) { @@ -274,11 +273,7 @@ return 4; } else { do { -#if defined(HAVE_UNISTD_H) && !defined(Q_WS_WIN) - usleep(100000); -#else - Sleep(100000); -#endif + std::this_thread::sleep_for(std::chrono::milliseconds(100)); } while (QDBusConnection::sessionBus().interface()->isServiceRegistered(Akonadi::DBus::serviceName(Akonadi::DBus::Control))); if (!startServer(verbose)) { return 3; diff --git a/src/asapcat/main.cpp b/src/asapcat/main.cpp --- a/src/asapcat/main.cpp +++ b/src/asapcat/main.cpp @@ -40,7 +40,11 @@ Session session(args[0]); QObject::connect(&session, &Session::disconnected, QCoreApplication::instance(), &QCoreApplication::quit); +#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) + QMetaObject::invokeMethod(&session, &Session::connectToHost, Qt::QueuedConnection); +#else QMetaObject::invokeMethod(&session, "connectToHost", Qt::QueuedConnection); +#endif const int result = app.exec(); session.printStats(); diff --git a/src/asapcat/session.h b/src/asapcat/session.h --- a/src/asapcat/session.h +++ b/src/asapcat/session.h @@ -55,8 +55,8 @@ QSocketNotifier *m_notifier = nullptr; QTime m_connectionTime; - qint64 m_receivedBytes; - qint64 m_sentBytes; + qint64 m_receivedBytes = 0; + qint64 m_sentBytes = 0; }; #endif // SESSION_H diff --git a/src/asapcat/session.cpp b/src/asapcat/session.cpp --- a/src/asapcat/session.cpp +++ b/src/asapcat/session.cpp @@ -33,11 +33,6 @@ Session::Session(const QString &input, QObject *parent) : QObject(parent) - , m_input(nullptr) - , m_session(nullptr) - , m_notifier(nullptr) - , m_receivedBytes(0) - , m_sentBytes(0) { QFile *file = new QFile(this); if (input != QLatin1String("-")) { diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -33,6 +33,7 @@ entitydisplayattribute.cpp entityhiddenattribute.cpp exception.cpp + favoritecollectionattribute.cpp firstrun.cpp gidextractor.cpp indexpolicyattribute.cpp @@ -55,6 +56,7 @@ pluginloader.cpp pop3resourceattribute.cpp protocolhelper.cpp + remotelog.cpp relation.cpp relationsync.cpp searchquery.cpp @@ -96,6 +98,7 @@ EntityDisplayAttribute EntityHiddenAttribute ExceptionBase + FavoriteCollectionAttribute GidExtractorInterface IndexPolicyAttribute Item @@ -303,6 +306,7 @@ KF5::IconThemes KF5::ConfigCore KF5AkonadiPrivate + akonadi_shared ) set_target_properties(KF5AkonadiCore PROPERTIES diff --git a/src/core/agentinstance.h b/src/core/agentinstance.h --- a/src/core/agentinstance.h +++ b/src/core/agentinstance.h @@ -96,22 +96,22 @@ /** * Returns whether the agent instance object is valid. */ - bool isValid() const; + Q_REQUIRED_RESULT bool isValid() const; /** * Returns the agent type of this instance. */ - AgentType type() const; + Q_REQUIRED_RESULT AgentType type() const; /** * Returns the unique identifier of the agent instance. */ - QString identifier() const; + Q_REQUIRED_RESULT QString identifier() const; /** * Returns the user visible name of the agent instance. */ - QString name() const; + Q_REQUIRED_RESULT QString name() const; /** * Sets the user visible @p name of the agent instance. @@ -121,23 +121,23 @@ /** * Returns the status of the agent instance. */ - Status status() const; + Q_REQUIRED_RESULT Status status() const; /** * Returns a textual presentation of the status of the agent instance. */ - QString statusMessage() const; + Q_REQUIRED_RESULT QString statusMessage() const; /** * Returns the progress of the agent instance in percent, or -1 if no * progress information are available. */ - int progress() const; + Q_REQUIRED_RESULT int progress() const; /** * Returns whether the agent instance is online currently. */ - bool isOnline() const; + Q_REQUIRED_RESULT bool isOnline() const; /** * Sets @p online status of the agent instance. @@ -171,7 +171,7 @@ * @internal * @param other other agent instance */ - bool operator==(const AgentInstance &other) const; + Q_REQUIRED_RESULT bool operator==(const AgentInstance &other) const; /** * Tell the agent to abort its current operation. diff --git a/src/core/agentinstance.cpp b/src/core/agentinstance.cpp --- a/src/core/agentinstance.cpp +++ b/src/core/agentinstance.cpp @@ -74,11 +74,11 @@ return Idle; case 1: return Running; + case 3: + return NotConfigured; case 2: default: return Broken; - case 3: - return NotConfigured; } } diff --git a/src/core/agentmanager.h b/src/core/agentmanager.h --- a/src/core/agentmanager.h +++ b/src/core/agentmanager.h @@ -75,18 +75,18 @@ /** * Returns the list of all available agent types. */ - AgentType::List types() const; + Q_REQUIRED_RESULT AgentType::List types() const; /** * Returns the agent type with the given @p identifier or * an invalid agent type if the identifier does not exist. */ - AgentType type(const QString &identifier) const; + Q_REQUIRED_RESULT AgentType type(const QString &identifier) const; /** * Returns the list of all available agent instances. */ - AgentInstance::List instances() const; + Q_REQUIRED_RESULT AgentInstance::List instances() const; /** * Returns the agent instance with the given @p identifier or @@ -96,7 +96,7 @@ * identifier of a resource is the same as that of its agent instance. * @param identifier identifier to choose the agent instance */ - AgentInstance instance(const QString &identifier) const; + Q_REQUIRED_RESULT AgentInstance instance(const QString &identifier) const; /** * Removes the given agent @p instance. diff --git a/src/core/agentmanager.cpp b/src/core/agentmanager.cpp --- a/src/core/agentmanager.cpp +++ b/src/core/agentmanager.cpp @@ -249,7 +249,7 @@ { qlonglong winId = 0; if (parent) { - winId = (qlonglong)(parent->window()->winId()); + winId = static_cast(parent->window()->winId()); } mManager->agentInstanceConfigure(instance.identifier(), winId); diff --git a/src/core/agentmanager_p.h b/src/core/agentmanager_p.h --- a/src/core/agentmanager_p.h +++ b/src/core/agentmanager_p.h @@ -42,7 +42,6 @@ public: explicit AgentManagerPrivate(AgentManager *parent) : mParent(parent) - , mManager(nullptr) { } diff --git a/src/core/agenttype.h b/src/core/agenttype.h --- a/src/core/agenttype.h +++ b/src/core/agenttype.h @@ -83,48 +83,48 @@ /** * Returns whether the agent type is valid. */ - bool isValid() const; + Q_REQUIRED_RESULT bool isValid() const; /** * Returns the unique identifier of the agent type. */ - QString identifier() const; + Q_REQUIRED_RESULT QString identifier() const; /** * Returns the i18n'ed name of the agent type. */ - QString name() const; + Q_REQUIRED_RESULT QString name() const; /** * Returns the description of the agent type. */ - QString description() const; + Q_REQUIRED_RESULT QString description() const; /** * Returns the name of the icon of the agent type. */ - QString iconName() const; + Q_REQUIRED_RESULT QString iconName() const; /** * Returns the icon of the agent type. */ - QIcon icon() const; + Q_REQUIRED_RESULT QIcon icon() const; /** * Returns the list of supported mime types of the agent type. */ - QStringList mimeTypes() const; + Q_REQUIRED_RESULT QStringList mimeTypes() const; /** * Returns the list of supported capabilities of the agent type. */ - QStringList capabilities() const; + Q_REQUIRED_RESULT QStringList capabilities() const; /** * Returns a Map of custom properties of the agent type. * @since 4.12 */ - QVariantMap customProperties() const; + Q_REQUIRED_RESULT QVariantMap customProperties() const; /** * @internal diff --git a/src/core/attributefactory.cpp b/src/core/attributefactory.cpp --- a/src/core/attributefactory.cpp +++ b/src/core/attributefactory.cpp @@ -28,6 +28,7 @@ #include "entitydeletedattribute.h" #include "tagattribute.h" #include "entityannotationsattribute.h" +#include "favoritecollectionattribute.h" #include @@ -99,6 +100,7 @@ AttributeFactory::registerAttribute(); AttributeFactory::registerAttribute(); AttributeFactory::registerAttribute(); + AttributeFactory::registerAttribute(); } bool initialized; }; diff --git a/src/core/braveheart.cpp b/src/core/braveheart.cpp --- a/src/core/braveheart.cpp +++ b/src/core/braveheart.cpp @@ -52,7 +52,7 @@ // Fight for freedom every 15 minutes freedom->start(15 * 60 * 1000); qApp->setProperty("__Akonadi__Braveheart", true); - }; + } public: explicit Braveheart() diff --git a/src/core/cachepolicy.h b/src/core/cachepolicy.h --- a/src/core/cachepolicy.h +++ b/src/core/cachepolicy.h @@ -100,7 +100,7 @@ /** * Returns the parts to permanently cache locally. */ - QStringList localParts() const; + Q_REQUIRED_RESULT QStringList localParts() const; /** * Specifies the parts to permanently cache locally. @@ -111,7 +111,7 @@ * Returns the cache timeout for non-permanently cached parts in minutes; * -1 means indefinitely. */ - int cacheTimeout() const; + Q_REQUIRED_RESULT int cacheTimeout() const; /** * Sets cache timeout for non-permanently cached parts. @@ -122,7 +122,7 @@ /** * Returns the interval check time in minutes, -1 for never. */ - int intervalCheckTime() const; + Q_REQUIRED_RESULT int intervalCheckTime() const; /** * Sets interval check time. @@ -134,7 +134,7 @@ * Returns whether the collection will be synced automatically when necessary, * i.e. as soon as it is accessed by a client. */ - bool syncOnDemand() const; + Q_REQUIRED_RESULT bool syncOnDemand() const; /** * Sets whether the collection shall be synced automatically when necessary, @@ -153,7 +153,7 @@ * @internal * @param other other cache policy */ - bool operator==(const CachePolicy &other) const; + Q_REQUIRED_RESULT bool operator==(const CachePolicy &other) const; private: //@cond PRIVATE diff --git a/src/core/cachepolicy.cpp b/src/core/cachepolicy.cpp --- a/src/core/cachepolicy.cpp +++ b/src/core/cachepolicy.cpp @@ -30,10 +30,6 @@ public: Private() : QSharedData() - , inherit(true) - , timeout(-1) - , interval(-1) - , syncOnDemand(false) { } @@ -47,11 +43,11 @@ syncOnDemand = other.syncOnDemand; } - bool inherit; QStringList localParts; - int timeout; - int interval; - bool syncOnDemand; + int timeout = -1; + int interval = -1; + bool inherit = true; + bool syncOnDemand = false; }; CachePolicy::CachePolicy() diff --git a/src/core/changenotification.h b/src/core/changenotification.h --- a/src/core/changenotification.h +++ b/src/core/changenotification.h @@ -60,18 +60,18 @@ ChangeNotification &operator=(const ChangeNotification &other); - bool isValid() const; + Q_REQUIRED_RESULT bool isValid() const; - QDateTime timestamp() const; + Q_REQUIRED_RESULT QDateTime timestamp() const; void setTimestamp(const QDateTime ×tamp); - QVector listeners() const; + Q_REQUIRED_RESULT QVector listeners() const; void setListeners(const QVector &listeners); - Type type() const; + Q_REQUIRED_RESULT Type type() const; void setType(Type type); - Protocol::ChangeNotificationPtr notification() const; + Q_REQUIRED_RESULT Protocol::ChangeNotificationPtr notification() const; void setNotification(const Protocol::ChangeNotificationPtr &ntf); private: diff --git a/src/core/changenotificationdependenciesfactory.cpp b/src/core/changenotificationdependenciesfactory.cpp --- a/src/core/changenotificationdependenciesfactory.cpp +++ b/src/core/changenotificationdependenciesfactory.cpp @@ -29,13 +29,26 @@ using namespace Akonadi; -Connection *ChangeNotificationDependenciesFactory::createNotificationConnection(Session *session) +Connection *ChangeNotificationDependenciesFactory::createNotificationConnection(Session *session, + CommandBuffer *commandBuffer) { if (!Akonadi::ServerManager::self()->isRunning()) { return nullptr; } - return session->d->sessionThread()->createConnection(Connection::NotificationConnection, session->sessionId()); + auto connection = new Connection(Connection::NotificationConnection, session->sessionId(), commandBuffer); + addConnection(session, connection); + return connection; +} + +void ChangeNotificationDependenciesFactory::addConnection(Session *session, Connection *connection) +{ + session->d->sessionThread()->addConnection(connection); +} + +void ChangeNotificationDependenciesFactory::destroyNotificationConnection(Session *session, Connection *connection) +{ + session->d->sessionThread()->destroyConnection(connection); } QObject *ChangeNotificationDependenciesFactory::createChangeMediator(QObject *parent) diff --git a/src/core/changenotificationdependenciesfactory_p.h b/src/core/changenotificationdependenciesfactory_p.h --- a/src/core/changenotificationdependenciesfactory_p.h +++ b/src/core/changenotificationdependenciesfactory_p.h @@ -27,6 +27,7 @@ { class Connection; +class CommandBuffer; /** * This class exists so that we can create a fake notification source in @@ -38,13 +39,19 @@ virtual ~ChangeNotificationDependenciesFactory() { } - virtual Connection *createNotificationConnection(Session *parent); + + virtual Connection *createNotificationConnection(Session *parent, CommandBuffer *commandBuffer); + virtual void destroyNotificationConnection(Session *parent, Connection *connection); + virtual QObject *createChangeMediator(QObject *parent); virtual Akonadi::CollectionCache *createCollectionCache(int maxCapacity, Session *session); virtual Akonadi::ItemCache *createItemCache(int maxCapacity, Session *session); virtual Akonadi::ItemListCache *createItemListCache(int maxCapacity, Session *session); virtual Akonadi::TagListCache *createTagListCache(int maxCapacity, Session *session); + +protected: + void addConnection(Session *session, Connection *connection); }; } diff --git a/src/core/changerecorder.h b/src/core/changerecorder.h --- a/src/core/changerecorder.h +++ b/src/core/changerecorder.h @@ -69,7 +69,7 @@ /** * Returns whether there are recorded changes. */ - bool isEmpty() const; + Q_REQUIRED_RESULT bool isEmpty() const; /** * Removes the previously emitted change from the records. @@ -87,7 +87,7 @@ /** * Debugging: dump current list of notifications, as saved on disk. */ - QString dumpNotificationListToString() const; + Q_REQUIRED_RESULT QString dumpNotificationListToString() const; public Q_SLOTS: /** diff --git a/src/core/changerecorder_p.cpp b/src/core/changerecorder_p.cpp --- a/src/core/changerecorder_p.cpp +++ b/src/core/changerecorder_p.cpp @@ -129,7 +129,7 @@ notificationsLoaded(); } -static const quint64 s_currentVersion = Q_UINT64_C(0x000600000000); +static const quint64 s_currentVersion = Q_UINT64_C(0x000800000000); static const quint64 s_versionMask = Q_UINT64_C(0xFFFF00000000); static const quint64 s_sizeMask = Q_UINT64_C(0x0000FFFFFFFF); @@ -182,7 +182,7 @@ msg = loadRelationNotification(stream, version); break; default: - qWarning() << "Unknown notification type"; + qCWarning(AKONADICORE_LOG) << "Unknown notification type"; break; } @@ -241,7 +241,7 @@ saveRelationNotification(stream, Protocol::cmdCast(msg)); break; default: - qWarning() << "Unexpected type?"; + qCWarning(AKONADICORE_LOG) << "Unexpected type?"; return; } } @@ -380,10 +380,12 @@ auto msg = Protocol::ItemChangeNotificationPtr::create(); msg->setSessionId(settings->value(QStringLiteral("sessionId")).toByteArray()); msg->setOperation(mapItemOperation(static_cast(settings->value(QStringLiteral("op")).toInt()))); - msg->setItems({ { settings->value(QStringLiteral("uid")).toLongLong(), - settings->value(QStringLiteral("rid")).toString(), - QString(), - settings->value(QStringLiteral("mimeType")).toString() } }); + Protocol::FetchItemsResponse item; + item.setId(settings->value(QStringLiteral("uid")).toLongLong()); + item.setRemoteId(settings->value(QStringLiteral("rid")).toString()); + item.setMimeType(settings->value(QStringLiteral("mimeType")).toString()); + msg->setItems({std::move(item)}); + msg->addMetadata("FETCH_ITEM"); msg->setResource(settings->value(QStringLiteral("resource")).toByteArray()); msg->setParentCollection(settings->value(QStringLiteral("parentCol")).toLongLong()); msg->setParentDestCollection(settings->value(QStringLiteral("parentDestCol")).toLongLong()); @@ -401,8 +403,11 @@ auto msg = Protocol::CollectionChangeNotificationPtr::create(); msg->setSessionId(settings->value(QStringLiteral("sessionId")).toByteArray()); msg->setOperation(mapCollectionOperation(static_cast(settings->value(QStringLiteral("op")).toInt()))); - msg->setId(settings->value(QStringLiteral("uid")).toLongLong()); - msg->setRemoteId(settings->value(QStringLiteral("rid")).toString()); + Protocol::FetchCollectionsResponse collection; + collection.setId(settings->value(QStringLiteral("uid")).toLongLong()); + collection.setRemoteId(settings->value(QStringLiteral("rid")).toString()); + msg->setCollection(std::move(collection)); + msg->addMetadata("FETCH_COLLECTION"); msg->setResource(settings->value(QStringLiteral("resource")).toByteArray()); msg->setParentCollection(settings->value(QStringLiteral("parentCol")).toLongLong()); msg->setParentDestCollection(settings->value(QStringLiteral("parentDestCol")).toLongLong()); @@ -445,7 +450,7 @@ QString remoteId, mimeType, remoteRevision; QSet itemParts, addedFlags, removedFlags; QSet addedTags, removedTags; - QVector items; + QVector items; auto msg = Protocol::ItemChangeNotificationPtr::create(); @@ -459,20 +464,145 @@ stream >> mimeType; stream >> itemParts; - items << Protocol::ChangeNotification::Item{ uid, remoteId, QString(), mimeType }; + Protocol::FetchItemsResponse item; + item.setId(uid); + item.setRemoteId(remoteId); + item.setMimeType(mimeType); + items.push_back(std::move(item)); + msg->addMetadata("FETCH_ITEM"); } else if (version >= 2) { stream >> operation; stream >> entityCnt; - for (int j = 0; j < entityCnt; ++j) { - stream >> uid; - stream >> remoteId; - stream >> remoteRevision; - stream >> mimeType; - if (stream.status() != QDataStream::Ok) { - qCWarning(AKONADICORE_LOG) << "Error reading saved notifications! Aborting"; - return msg; + if (version >= 7) { + QByteArray ba; + qint64 i64; + int i; + QDateTime dt; + QString str; + QVector bav; + QVector i64v; + QMap babaMap; + int cnt; + for (int j = 0; j < entityCnt; ++j) { + Protocol::FetchItemsResponse item; + stream >> i64; + item.setId(i64); + stream >> i; + item.setRevision(i); + stream >> i64; + item.setParentId(i64); + stream >> str; + item.setRemoteId(str); + stream >> str; + item.setRemoteRevision(str); + stream >> str; + item.setGid(str); + stream >> i64; + item.setSize(i64); + stream >> str; + item.setMimeType(str); + stream >> dt; + item.setMTime(dt); + stream >> bav; + item.setFlags(bav); + stream >> cnt; + QVector tags; + for (int k = 0; k < cnt; ++k) { + Protocol::FetchTagsResponse tag; + stream >> i64; + tag.setId(i64); + stream >> i64; + tag.setParentId(i64); + stream >> ba; + tag.setGid(ba); + stream >> ba; + tag.setType(ba); + stream >> ba; + tag.setRemoteId(ba); + stream >> babaMap; + tag.setAttributes(babaMap); + tags << tag; + } + item.setTags(tags); + stream >> i64v; + item.setVirtualReferences(i64v); + stream >> cnt; + QVector relations; + for (int k = 0; k < cnt; ++k) { + Protocol::FetchRelationsResponse relation; + stream >> i64; + relation.setLeft(i64); + stream >> ba; + relation.setLeftMimeType(ba); + stream >> i64; + relation.setRight(i64); + stream >> ba; + relation.setRightMimeType(ba); + stream >> ba; + relation.setType(ba); + stream >> ba; + relation.setRemoteId(ba); + relations << relation; + } + item.setRelations(relations); + stream >> cnt; + QVector ancestors; + for (int k = 0; k < cnt; ++k) { + Protocol::Ancestor ancestor; + stream >> i64; + ancestor.setId(i64); + stream >> str; + ancestor.setRemoteId(str); + stream >> str; + ancestor.setName(str); + stream >> babaMap; + ancestor.setAttributes(babaMap); + ancestors << ancestor; + } + item.setAncestors(ancestors); + stream >> cnt; + QVector parts; + for (int k = 0; k < cnt; ++k) { + Protocol::StreamPayloadResponse part; + stream >> ba; + part.setPayloadName(ba); + Protocol::PartMetaData metaData; + stream >> ba; + metaData.setName(ba); + stream >> i64; + metaData.setSize(i64); + stream >> i; + metaData.setVersion(i); + stream >> i; + metaData.setStorageType(static_cast(i)); + part.setMetaData(metaData); + stream >> ba; + part.setData(ba); + parts << part; + } + item.setParts(parts); + stream >> bav; + item.setCachedParts(bav); + items.push_back(std::move(item)); + } + } else { + for (int j = 0; j < entityCnt; ++j) { + stream >> uid; + stream >> remoteId; + stream >> remoteRevision; + stream >> mimeType; + if (stream.status() != QDataStream::Ok) { + qCWarning(AKONADICORE_LOG) << "Error reading saved notifications! Aborting"; + return msg; + } + Protocol::FetchItemsResponse item; + item.setId(uid); + item.setRemoteId(remoteId); + item.setRemoteRevision(remoteRevision); + item.setMimeType(mimeType); + items.push_back(std::move(item)); } - items << Protocol::ChangeNotification::Item{ uid, remoteId, remoteRevision, mimeType }; + msg->addMetadata("FETCH_ITEM"); } stream >> resource; stream >> destinationResource; @@ -485,6 +615,11 @@ stream >> addedTags; stream >> removedTags; } + if (version >= 8) { + bool boolean; + stream >> boolean; + msg->setMustRetrieve(boolean); + } } else { qCWarning(AKONADICORE_LOG) << "Error version is not correct here" << version; return msg; @@ -520,14 +655,63 @@ void ChangeRecorderPrivate::saveItemNotification(QDataStream &stream, const Protocol::ItemChangeNotification &msg) { + // Version 8 + stream << int(msg.operation()); - const auto items = msg.items(); + const auto &items = msg.items(); stream << items.count(); - for (const Protocol::ItemChangeNotification::Item &item : items) { - stream << quint64(item.id); - stream << item.remoteId; - stream << item.remoteRevision; - stream << item.mimeType; + for (const auto &item : items) { + stream << item.id() + << item.revision() + << item.parentId() + << item.remoteId() + << item.remoteRevision() + << item.gid() + << item.size() + << item.mimeType() + << item.mTime() + << item.flags(); + const auto tags = item.tags(); + stream << tags.count(); + for (const auto &tag : tags) { + stream << tag.id() + << tag.parentId() + << tag.gid() + << tag.type() + << tag.remoteId() + << tag.attributes(); + } + stream << item.virtualReferences(); + const auto relations = item.relations(); + stream << relations.count(); + for (const auto &relation : relations) { + stream << relation.left() + << relation.leftMimeType() + << relation.right() + << relation.rightMimeType() + << relation.type() + << relation.remoteId(); + } + const auto ancestors = item.ancestors(); + stream << ancestors.count(); + for (const auto &ancestor : ancestors) { + stream << ancestor.id() + << ancestor.remoteId() + << ancestor.name() + << ancestor.attributes(); + } + const auto parts = item.parts(); + stream << parts.count(); + for (const auto &part : parts) { + const auto metaData = part.metaData(); + stream << part.payloadName() + << metaData.name() + << metaData.size() + << metaData.version() + << static_cast(metaData.storageType()) + << part.data(); + } + stream << item.cachedParts(); } stream << msg.resource(); stream << msg.destinationResource(); @@ -538,6 +722,7 @@ stream << msg.removedFlags() + encodeRelations(msg.removedRelations()); stream << msg.addedTags(); stream << msg.removedTags(); + stream << msg.mustRetrieve(); } Protocol::ChangeNotificationPtr ChangeRecorderPrivate::loadCollectionNotification(QDataStream &stream, quint64 version) const @@ -561,23 +746,119 @@ stream >> dummyString; stream >> changedParts; - msg->setId(uid); - msg->setRemoteId(remoteId); + Protocol::FetchCollectionsResponse collection; + collection.setId(uid); + collection.setRemoteId(remoteId); + msg->setCollection(std::move(collection)); + msg->addMetadata("FETCH_COLLECTION"); } else if (version >= 2) { stream >> operation; stream >> entityCnt; - for (int j = 0; j < entityCnt; ++j) { + if (version >= 7) { + QString str; + QStringList stringList; + qint64 i64; + QVector vb; + QMap attrs; + bool b; + int i; + Tristate tristate; + Protocol::FetchCollectionsResponse collection; + stream >> uid; + collection.setId(uid); stream >> uid; - stream >> remoteId; - stream >> remoteRevision; - stream >> dummyString; - if (stream.status() != QDataStream::Ok) { - qCWarning(AKONADICORE_LOG) << "Error reading saved notifications! Aborting"; - return msg; + collection.setParentId(uid); + stream >> str; + collection.setName(str); + stream >> stringList; + collection.setMimeTypes(stringList); + stream >> str; + collection.setRemoteId(str); + stream >> str; + collection.setRemoteRevision(str); + stream >> str; + collection.setResource(str); + + Protocol::FetchCollectionStatsResponse stats; + stream >> i64; + stats.setCount(i64); + stream >> i64; + stats.setUnseen(i64); + stream >> i64; + stats.setSize(i64); + collection.setStatistics(stats); + + stream >> str; + collection.setSearchQuery(str); + stream >> vb; + collection.setSearchCollections(vb); + stream >> entityCnt; + QVector ancestors; + for (int i = 0; i < entityCnt; ++i) { + Protocol::Ancestor ancestor; + stream >> i64; + ancestor.setId(i64); + stream >> str; + ancestor.setRemoteId(str); + stream >> str; + ancestor.setName(str); + stream >> attrs; + ancestor.setAttributes(attrs); + ancestors.push_back(ancestor); + + if (stream.status() != QDataStream::Ok) { + qCWarning(AKONADICORE_LOG) << "Erorr reading saved notifications! Aborting"; + return msg; + } + } + collection.setAncestors(ancestors); + + Protocol::CachePolicy cachePolicy; + stream >> b; + cachePolicy.setInherit(b); + stream >> i; + cachePolicy.setCheckInterval(i); + stream >> i; + cachePolicy.setCacheTimeout(i); + stream >> b; + cachePolicy.setSyncOnDemand(b); + stream >> stringList; + cachePolicy.setLocalParts(stringList); + collection.setCachePolicy(cachePolicy); + + stream >> attrs; + collection.setAttributes(attrs); + stream >> b; + collection.setEnabled(b); + stream >> reinterpret_cast(tristate); + collection.setDisplayPref(tristate); + stream >> reinterpret_cast(tristate); + collection.setSyncPref(tristate); + stream >> reinterpret_cast(tristate); + collection.setIndexPref(tristate); + stream >> b; + collection.setReferenced(b); + stream >> b; + collection.setIsVirtual(b); + + msg->setCollection(std::move(collection)); + } else { + for (int j = 0; j < entityCnt; ++j) { + stream >> uid; + stream >> remoteId; + stream >> remoteRevision; + stream >> dummyString; + if (stream.status() != QDataStream::Ok) { + qCWarning(AKONADICORE_LOG) << "Error reading saved notifications! Aborting"; + return msg; + } + Protocol::FetchCollectionsResponse collection; + collection.setId(uid); + collection.setRemoteId(remoteId); + collection.setRemoteRevision(remoteRevision); + msg->setCollection(std::move(collection)); + msg->addMetadata("FETCH_COLLECTION"); } - msg->setId(uid); - msg->setRemoteId(remoteId); - msg->setRemoteRevision(remoteRevision); } stream >> resource; stream >> destinationResource; @@ -610,12 +891,48 @@ void Akonadi::ChangeRecorderPrivate::saveCollectionNotification(QDataStream &stream, const Protocol::CollectionChangeNotification &msg) { + // Version 7 + + const auto &col = msg.collection(); + stream << int(msg.operation()); stream << int(1); - stream << msg.id(); - stream << msg.remoteId(); - stream << msg.remoteRevision(); - stream << QString(); + stream << col.id(); + stream << col.parentId(); + stream << col.name(); + stream << col.mimeTypes(); + stream << col.remoteId(); + stream << col.remoteRevision(); + stream << col.resource(); + const auto stats = col.statistics(); + stream << stats.count(); + stream << stats.unseen(); + stream << stats.size(); + stream << col.searchQuery(); + stream << col.searchCollections(); + const auto ancestors = col.ancestors(); + stream << ancestors.count(); + for (const auto &ancestor : ancestors) { + stream << ancestor.id() + << ancestor.remoteId() + << ancestor.name() + << ancestor.attributes(); + } + const auto cachePolicy = col.cachePolicy(); + stream << cachePolicy.inherit(); + stream << cachePolicy.checkInterval(); + stream << cachePolicy.cacheTimeout(); + stream << cachePolicy.syncOnDemand(); + stream << cachePolicy.localParts(); + stream << col.attributes(); + stream << col.enabled(); + stream << static_cast(col.displayPref()); + stream << static_cast(col.syncPref()); + stream << static_cast(col.indexPref()); + stream << col.referenced(); + stream << col.isVirtual(); + + stream << msg.resource(); stream << msg.destinationResource(); stream << quint64(msg.parentCollection()); @@ -648,61 +965,85 @@ stream >> dummyString; stream >> dummyBaV; - msg->setId(uid); - msg->setRemoteId(remoteId); + Protocol::FetchTagsResponse tag; + tag.setId(uid); + tag.setRemoteId(remoteId.toLatin1()); + msg->setTag(std::move(tag)); + msg->addMetadata("FETCH_TAG"); } else if (version >= 2) { stream >> operation; stream >> entityCnt; - for (int j = 0; j < entityCnt; ++j) { + if (version >= 7) { + QByteArray ba; + QMap attrs; + + Protocol::FetchTagsResponse tag; + + stream >> uid; + tag.setId(uid); + stream >> ba; + tag.setParentId(uid); + stream >> attrs; + tag.setGid(ba); + stream >> ba; + tag.setType(ba); stream >> uid; - stream >> remoteId; - stream >> dummyString; - stream >> dummyString; - if (stream.status() != QDataStream::Ok) { - qCWarning(AKONADICORE_LOG) << "Error reading saved notifications! Aborting"; - return msg; + tag.setRemoteId(ba); + stream >> ba; + tag.setAttributes(attrs); + msg->setTag(std::move(tag)); + + stream >> resource; + } else { + for (int j = 0; j < entityCnt; ++j) { + stream >> uid; + stream >> remoteId; + stream >> dummyString; + stream >> dummyString; + if (stream.status() != QDataStream::Ok) { + qCWarning(AKONADICORE_LOG) << "Error reading saved notifications! Aborting"; + return msg; + } + Protocol::FetchTagsResponse tag; + tag.setId(uid); + tag.setRemoteId(remoteId.toLatin1()); + msg->setTag(std::move(tag)); + msg->addMetadata("FETCH_TAG"); + } + stream >> resource; + stream >> dummyBa; + stream >> dummyI; + stream >> dummyI; + stream >> dummyBaV; + stream >> dummyBaV; + stream >> dummyBaV; + if (version >= 3) { + stream >> dummyIv; + stream >> dummyIv; } - msg->setId(uid); - msg->setRemoteId(remoteId); } - stream >> resource; - stream >> dummyBa; - stream >> dummyI; - stream >> dummyI; - stream >> dummyBaV; - stream >> dummyBaV; - stream >> dummyBaV; - if (version >= 3) { - stream >> dummyIv; - stream >> dummyIv; + if (version >= 5) { + msg->setOperation(static_cast(operation)); + } else { + msg->setOperation(mapTagOperation(static_cast(operation))); } } - if (version >= 5) { - msg->setOperation(static_cast(operation)); - } else { - msg->setOperation(mapTagOperation(static_cast(operation))); - } msg->setResource(resource); return msg; } void Akonadi::ChangeRecorderPrivate::saveTagNotification(QDataStream &stream, const Protocol::TagChangeNotification &msg) { + const auto &tag = msg.tag(); stream << int(msg.operation()); stream << int(1); - stream << msg.id(); - stream << msg.remoteId(); - stream << QString(); - stream << QString(); + stream << tag.id(); + stream << tag.parentId(); + stream << tag.gid(); + stream << tag.type(); + stream << tag.remoteId(); + stream << tag.attributes(); stream << msg.resource(); - stream << qint64(0); - stream << qint64(0); - stream << qint64(0); - stream << QSet(); - stream << QSet(); - stream << QSet(); - stream << QSet(); - stream << QSet(); } Protocol::ChangeNotificationPtr ChangeRecorderPrivate::loadRelationNotification(QDataStream &stream, quint64 version) const @@ -717,87 +1058,99 @@ auto msg = Protocol::RelationChangeNotificationPtr::create(); if (version == 1) { - qWarning() << "Invalid version of relation notification"; + qCWarning(AKONADICORE_LOG) << "Invalid version of relation notification"; return msg; } else if (version >= 2) { stream >> operation; stream >> entityCnt; - for (int j = 0; j < entityCnt; ++j) { + if (version >= 7) { + Protocol::FetchRelationsResponse relation; + qint64 i64; + QByteArray ba; + stream >> i64; + relation.setLeft(i64); + stream >> ba; + relation.setLeftMimeType(ba); + stream >> i64; + relation.setRight(i64); + stream >>ba; + relation.setRightMimeType(ba); + stream >> ba; + relation.setRemoteId(ba); + stream >> ba; + relation.setType(ba); + + msg->setRelation(std::move(relation)); + + } else { + for (int j = 0; j < entityCnt; ++j) { + stream >> dummyI; + stream >> dummyString; + stream >> dummyString; + stream >> dummyString; + if (stream.status() != QDataStream::Ok) { + qCWarning(AKONADICORE_LOG) << "Error reading saved notifications! Aborting"; + return msg; + } + } + stream >> dummyBa; + if (version == 5) { + // there was a bug in version 5 serializer that serialized this + // field as qint64 (8 bytes) instead of empty QByteArray (which is + // 4 bytes) + stream >> dummyI; + } else { + stream >> dummyBa; + } + stream >> dummyI; stream >> dummyI; - stream >> dummyString; - stream >> dummyString; - stream >> dummyString; - if (stream.status() != QDataStream::Ok) { - qCWarning(AKONADICORE_LOG) << "Error reading saved notifications! Aborting"; - return msg; + stream >> itemParts; + stream >> dummyBaV; + stream >> dummyBaV; + if (version >= 3) { + stream >> dummyIv; + stream >> dummyIv; } + + Protocol::FetchRelationsResponse relation; + for (const QByteArray &part : qAsConst(itemParts)) { + const QByteArrayList p = part.split(' '); + if (p.size() < 2) { + continue; + } + if (p[0] == "LEFT") { + relation.setLeft(p[1].toLongLong()); + } else if (p[0] == "RIGHT") { + relation.setRight(p[1].toLongLong()); + } else if (p[0] == "RID") { + relation.setRemoteId(p[1]); + } else if (p[0] == "TYPE") { + relation.setType(p[1]); + } + } + msg->setRelation(std::move(relation)); } - stream >> dummyBa; - if (version == 5) { - // there was a bug in version 5 serializer that serialized this - // field as qint64 (8 bytes) instead of empty QByteArray (which is - // 4 bytes) - stream >> dummyI; + if (version >= 5) { + msg->setOperation(static_cast(operation)); } else { - stream >> dummyBa; - } - stream >> dummyI; - stream >> dummyI; - stream >> itemParts; - stream >> dummyBaV; - stream >> dummyBaV; - if (version >= 3) { - stream >> dummyIv; - stream >> dummyIv; + msg->setOperation(mapRelationOperation(static_cast(operation))); } } - if (version >= 5) { - msg->setOperation(static_cast(operation)); - } else { - msg->setOperation(mapRelationOperation(static_cast(operation))); - } - for (const QByteArray &part : qAsConst(itemParts)) { - const QByteArrayList p = part.split(' '); - if (p.size() < 2) { - continue; - } - if (p[0] == "LEFT") { - msg->setLeftItem(p[1].toLongLong()); - } else if (p[0] == "RIGHT") { - msg->setRightItem(p[1].toLongLong()); - } else if (p[0] == "RID") { - msg->setRemoteId(QString::fromLatin1(p[1])); - } else if (p[0] == "TYPE") { - msg->setType(QString::fromLatin1(p[1])); - } - } return msg; } void Akonadi::ChangeRecorderPrivate::saveRelationNotification(QDataStream &stream, const Protocol::RelationChangeNotification &msg) { - QSet rv; - rv.insert("LEFT " + QByteArray::number(msg.leftItem())); - rv.insert("RIGHT " + QByteArray::number(msg.rightItem())); - rv.insert("RID " + msg.remoteId().toLatin1()); - rv.insert("TYPE " + msg.type().toLatin1()); - + const auto &rel = msg.relation(); stream << int(msg.operation()); stream << int(0); - stream << qint64(0); - stream << QString(); - stream << QString(); - stream << QString(); - stream << QByteArray(); - stream << QByteArray(); - stream << qint64(0); - stream << qint64(0); - stream << rv; - stream << QSet(); - stream << QSet(); - stream << QSet(); - stream << QSet(); + stream << rel.left(); + stream << rel.leftMimeType(); + stream << rel.right(); + stream << rel.rightMimeType(); + stream << rel.remoteId(); + stream << rel.type(); } Protocol::ItemChangeNotification::Operation ChangeRecorderPrivate::mapItemOperation(LegacyOp op) const @@ -843,7 +1196,7 @@ case Unsubscribe: return Protocol::CollectionChangeNotification::Unsubscribe; default: - qWarning() << "Unexpected operation type in collection notification"; + qCWarning(AKONADICORE_LOG) << "Unexpected operation type in collection notification"; return Protocol::CollectionChangeNotification::InvalidOp; } } @@ -858,7 +1211,7 @@ case Remove: return Protocol::TagChangeNotification::Remove; default: - qWarning() << "Unexpected operation type in tag notification"; + qCWarning(AKONADICORE_LOG) << "Unexpected operation type in tag notification"; return Protocol::TagChangeNotification::InvalidOp; } } @@ -871,7 +1224,7 @@ case Remove: return Protocol::RelationChangeNotification::Remove; default: - qWarning() << "Unexpected operation type in relation notification"; + qCWarning(AKONADICORE_LOG) << "Unexpected operation type in relation notification"; return Protocol::RelationChangeNotification::InvalidOp; } } @@ -888,7 +1241,7 @@ case Protocol::Command::RelationChangeNotification: return Relation; default: - qWarning() << "Unexpected notification type"; + qCWarning(AKONADICORE_LOG) << "Unexpected notification type"; return InvalidType; } } diff --git a/src/core/collection.h b/src/core/collection.h --- a/src/core/collection.h +++ b/src/core/collection.h @@ -139,7 +139,7 @@ /** * Returns the unique identifier of the collection. */ - Id id() const; + Q_REQUIRED_RESULT Id id() const; /** * Sets the remote @p id of the collection. @@ -149,7 +149,7 @@ /** * Returns the remote id of the collection. */ - QString remoteId() const; + Q_REQUIRED_RESULT QString remoteId() const; /** * Sets the remote @p revision of the collection. @@ -168,24 +168,24 @@ * @note This method is supposed to be used by resources only. * @since 4.5 */ - QString remoteRevision() const; + Q_REQUIRED_RESULT QString remoteRevision() const; /** * Returns whether the collection is valid. */ - bool isValid() const; + Q_REQUIRED_RESULT bool isValid() const; /** * Returns whether this collections's id equals the * id of the @p other collection. */ - bool operator==(const Collection &other) const; + Q_REQUIRED_RESULT bool operator==(const Collection &other) const; /** * Returns whether the collection's id does not equal the id * of the @p other collection. */ - bool operator!=(const Collection &other) const; + Q_REQUIRED_RESULT bool operator!=(const Collection &other) const; /** * Assigns the @p other to this collection and returns a reference to this @@ -199,23 +199,23 @@ * * @since 4.8 */ - bool operator<(const Collection &other) const; + Q_REQUIRED_RESULT bool operator<(const Collection &other) const; /** * Returns the parent collection of this object. * @note This will of course only return a useful value if it was explictly retrieved * from the Akonadi server. * @since 4.4 */ - Collection parentCollection() const; + Q_REQUIRED_RESULT Collection parentCollection() const; /** * Returns a reference to the parent collection of this object. * @note This will of course only return a useful value if it was explictly retrieved * from the Akonadi server. * @since 4.4 */ - Collection &parentCollection(); + Q_REQUIRED_RESULT Collection &parentCollection(); /** * Set the parent collection of this object. @@ -254,7 +254,7 @@ /** * Returns a list of all attributes of the collection. */ - Attribute::List attributes() const; + Q_REQUIRED_RESULT Attribute::List attributes() const; /** * Removes and deletes all attributes of the collection. @@ -304,16 +304,16 @@ /** * Returns the i18n'ed name of the collection. */ - QString name() const; + Q_REQUIRED_RESULT QString name() const; /** * Returns the display name (EntityDisplayAttribute::displayName()) if set, * and Collection::name() otherwise. For human-readable strings this is preferred * over Collection::name(). * * @since 4.11 */ - QString displayName() const; + Q_REQUIRED_RESULT QString displayName() const; /** * Sets the i18n'ed name of the collection. @@ -325,7 +325,7 @@ /** * Returns the rights the user has on the collection. */ - Rights rights() const; + Q_REQUIRED_RESULT Rights rights() const; /** * Sets the @p rights the user has on the collection. @@ -337,7 +337,7 @@ * e.g. message/rfc822, x-akonadi/collection for a mail folder that * supports sub-folders. */ - QStringList contentMimeTypes() const; + Q_REQUIRED_RESULT QStringList contentMimeTypes() const; /** * Sets the list of possible content mime @p types. @@ -347,24 +347,24 @@ /** * Returns the root collection. */ - static Collection root(); + Q_REQUIRED_RESULT static Collection root(); /** * Returns the mimetype used for collections. */ - static QString mimeType(); + Q_REQUIRED_RESULT static QString mimeType(); /** * Returns the mimetype used for virtual collections * * @since 4.11 */ - static QString virtualMimeType(); + Q_REQUIRED_RESULT static QString virtualMimeType(); /** * Returns the identifier of the resource owning the collection. */ - QString resource() const; + Q_REQUIRED_RESULT QString resource() const; /** * Sets the @p identifier of the resource owning the collection. @@ -374,7 +374,7 @@ /** * Returns the cache policy of the collection. */ - CachePolicy cachePolicy() const; + Q_REQUIRED_RESULT CachePolicy cachePolicy() const; /** * Sets the cache @p policy of the collection. @@ -384,7 +384,7 @@ /** * Returns the collection statistics of the collection. */ - CollectionStatistics statistics() const; + Q_REQUIRED_RESULT CollectionStatistics statistics() const; /** * Sets the collection @p statistics for the collection. @@ -406,14 +406,14 @@ * @param type the type of url * @since 4.7 */ - QUrl url(UrlType type = UrlShort) const; + Q_REQUIRED_RESULT QUrl url(UrlType type = UrlShort) const; /** * Returns whether the collection is virtual, for example a search collection. * * @since 4.6 */ - bool isVirtual() const; + Q_REQUIRED_RESULT bool isVirtual() const; /** * Sets whether the collection is virtual or not. @@ -449,7 +449,7 @@ * @since 4.14 * @see localListPreference */ - bool enabled() const; + Q_REQUIRED_RESULT bool enabled() const; /** * Describes the list preference value @@ -491,16 +491,16 @@ * @since 4.14 * @see setLocalListPreference */ - ListPreference localListPreference(ListPurpose purpose) const; + Q_REQUIRED_RESULT ListPreference localListPreference(ListPurpose purpose) const; /** * Returns whether the collection should be listed or not for the specified purpose * Takes enabled state and local preference into account. * * @since 4.14 * @see setLocalListPreference, setEnabled */ - bool shouldList(ListPurpose purpose) const; + Q_REQUIRED_RESULT bool shouldList(ListPurpose purpose) const; /** * Sets whether the collection should be listed or not for the specified purpose. @@ -530,7 +530,7 @@ * Returns the referenced state of the collection. * @since 4.14 */ - bool referenced() const; + Q_REQUIRED_RESULT bool referenced() const; /** * Set during sync to indicate that the provided parts are only default values; diff --git a/src/core/collectioncolorattribute.h b/src/core/collectioncolorattribute.h --- a/src/core/collectioncolorattribute.h +++ b/src/core/collectioncolorattribute.h @@ -44,7 +44,7 @@ CollectionColorAttribute(); explicit CollectionColorAttribute(const QColor &color); - virtual ~CollectionColorAttribute(); + ~CollectionColorAttribute() override; void setColor(const QColor &color); QColor color() const; diff --git a/src/core/collectioncolorattribute.cpp b/src/core/collectioncolorattribute.cpp --- a/src/core/collectioncolorattribute.cpp +++ b/src/core/collectioncolorattribute.cpp @@ -52,7 +52,7 @@ QByteArray CollectionColorAttribute::type() const { - return "collectioncolor"; + return QByteArrayLiteral("collectioncolor"); } CollectionColorAttribute *CollectionColorAttribute::clone() const diff --git a/src/core/collectionfetchscope.h b/src/core/collectionfetchscope.h --- a/src/core/collectionfetchscope.h +++ b/src/core/collectionfetchscope.h @@ -130,14 +130,14 @@ * @see setListFilter() * @since 4.14 */ - ListFilter listFilter() const; + Q_REQUIRED_RESULT ListFilter listFilter() const; /** * Returns whether collection statistics should be included in the retrieved results. * * @see setIncludeStatistics() */ - bool includeStatistics() const; + Q_REQUIRED_RESULT bool includeStatistics() const; /** * Sets whether collection statistics should be included in the retrieved results. @@ -151,7 +151,7 @@ * * @see setResource() */ - QString resource() const; + Q_REQUIRED_RESULT QString resource() const; /** * Sets a resource filter, that is only collections owned by the specified resource are @@ -174,7 +174,7 @@ * * @see setContentMimeTypes() */ - QStringList contentMimeTypes() const; + Q_REQUIRED_RESULT QStringList contentMimeTypes() const; /** * Sets how many levels of ancestor collections should be included in the retrieval. @@ -192,7 +192,7 @@ * * @see setAncestorRetrieval() */ - AncestorRetrieval ancestorRetrieval() const; + Q_REQUIRED_RESULT AncestorRetrieval ancestorRetrieval() const; /** * Sets the fetch scope for ancestor retrieval. @@ -204,7 +204,7 @@ /** * Returns the fetch scope for ancestor retrieval. */ - CollectionFetchScope ancestorFetchScope() const; + Q_REQUIRED_RESULT CollectionFetchScope ancestorFetchScope() const; /** * Returns the fetch scope for ancestor retrieval. @@ -218,7 +218,7 @@ * * @see fetchAttribute() */ - QSet attributes() const; + Q_REQUIRED_RESULT QSet attributes() const; /** * Sets whether the attribute of the given @p type should be fetched. @@ -254,7 +254,7 @@ * @see tagFetchScope() * @since 4.15 */ - bool fetchIdOnly() const; + Q_REQUIRED_RESULT bool fetchIdOnly() const; /** * Ignore retrieval errors while fetching collections, and always deliver what is available. @@ -271,12 +271,12 @@ * @see setIgnoreRetrievalErrors() * @since KF5 */ - bool ignoreRetrievalErrors() const; + Q_REQUIRED_RESULT bool ignoreRetrievalErrors() const; /** * Returns @c true if there is nothing to fetch. */ - bool isEmpty() const; + Q_REQUIRED_RESULT bool isEmpty() const; private: //@cond PRIVATE diff --git a/src/core/collectionfetchscope.cpp b/src/core/collectionfetchscope.cpp --- a/src/core/collectionfetchscope.cpp +++ b/src/core/collectionfetchscope.cpp @@ -32,8 +32,8 @@ public: CollectionFetchScopePrivate() : ancestorDepth(CollectionFetchScope::None) - , statistics(false) , listFilter(CollectionFetchScope::Enabled) + , statistics(false) , fetchAllAttributes(false) , fetchIdOnly(true) , mIgnoreRetrievalErrors(false) @@ -64,10 +64,10 @@ QString resource; QStringList contentMimeTypes; CollectionFetchScope::AncestorRetrieval ancestorDepth; - bool statistics; CollectionFetchScope::ListFilter listFilter; QSet attributes; QScopedPointer ancestorFetchScope; + bool statistics; bool fetchAllAttributes; bool fetchIdOnly; bool mIgnoreRetrievalErrors; diff --git a/src/core/collectionidentificationattribute.h b/src/core/collectionidentificationattribute.h --- a/src/core/collectionidentificationattribute.h +++ b/src/core/collectionidentificationattribute.h @@ -39,7 +39,7 @@ public: explicit CollectionIdentificationAttribute(const QByteArray &identifier = QByteArray(), const QByteArray &folderNamespace = QByteArray(), const QByteArray &name = QByteArray(), const QByteArray &organizationUnit = QByteArray(), const QByteArray &mail = QByteArray()); - ~CollectionIdentificationAttribute(); + ~CollectionIdentificationAttribute() override; /** * Sets an identifier for the collection. diff --git a/src/core/collectionidentificationattribute.cpp b/src/core/collectionidentificationattribute.cpp --- a/src/core/collectionidentificationattribute.cpp +++ b/src/core/collectionidentificationattribute.cpp @@ -107,7 +107,7 @@ QByteArray CollectionIdentificationAttribute::type() const { - return "collectionidentification"; + return QByteArrayLiteral("collectionidentification"); } Akonadi::Attribute *CollectionIdentificationAttribute::clone() const diff --git a/src/core/collectionpathresolver.h b/src/core/collectionpathresolver.h --- a/src/core/collectionpathresolver.h +++ b/src/core/collectionpathresolver.h @@ -82,32 +82,28 @@ /** * Destroys the collection path resolver. */ - ~CollectionPathResolver(); + ~CollectionPathResolver() override; /** * Returns the collection id. Only valid after the job succeeded. */ - Collection::Id collection() const; + Q_REQUIRED_RESULT Collection::Id collection() const; /** * Returns the collection path. Only valid after the job succeeded. */ - QString path() const; + Q_REQUIRED_RESULT QString path() const; /** * Returns the path delimiter for collections. */ - static QString pathDelimiter(); + Q_REQUIRED_RESULT static QString pathDelimiter(); protected: void doStart() override; private: Q_DECLARE_PRIVATE(CollectionPathResolver) - - //@cond PRIVATE - Q_PRIVATE_SLOT(d_func(), void jobResult(KJob *)) - //@endcond }; } diff --git a/src/core/collectionpathresolver.cpp b/src/core/collectionpathresolver.cpp --- a/src/core/collectionpathresolver.cpp +++ b/src/core/collectionpathresolver.cpp @@ -73,27 +73,27 @@ for (int i = 0; i < pathSize; ++i) { if (path[i] == QLatin1Char('/')) { QString pathElement = path.mid(begin, i - begin); - pathElement = pathElement.replace(QStringLiteral("\\/"), QStringLiteral("/")); + pathElement = pathElement.replace(QLatin1String("\\/"), QLatin1String("/")); rv.append(pathElement); begin = i + 1; } if (i < path.size() - 2 && path[i] == QLatin1Char('\\') && path[i + 1] == QLatin1Char('/')) { ++i; } } QString pathElement = path.mid(begin); - pathElement = pathElement.replace(QStringLiteral("\\/"), QStringLiteral("/")); + pathElement = pathElement.replace(QLatin1String("\\/"), QLatin1String("/")); rv.append(pathElement); return rv; } Q_DECLARE_PUBLIC(CollectionPathResolver) - Collection::Id mColId; - QString mPath; - bool mPathToId; - QStringList mPathParts; Collection mCurrentNode; + QStringList mPathParts; + QString mPath; + Collection::Id mColId; + bool mPathToId = false; }; void CollectionPathResolverPrivate::jobResult(KJob *job) @@ -149,7 +149,7 @@ } nextJob = new CollectionFetchJob(mCurrentNode, CollectionFetchJob::Base, q); } - q->connect(nextJob, SIGNAL(result(KJob*)), q, SLOT(jobResult(KJob*))); + q->connect(nextJob, &CollectionFetchJob::result, q, [this](KJob *job) { jobResult(job);}); } CollectionPathResolver::CollectionPathResolver(const QString &path, QObject *parent) @@ -222,7 +222,7 @@ } job = new CollectionFetchJob(d->mCurrentNode, CollectionFetchJob::Base, this); } - connect(job, SIGNAL(result(KJob*)), SLOT(jobResult(KJob*))); + connect(job, &CollectionFetchJob::result, this, [d](KJob *job) { d->jobResult(job);}); } //@endcond diff --git a/src/core/collectionquotaattribute.h b/src/core/collectionquotaattribute.h --- a/src/core/collectionquotaattribute.h +++ b/src/core/collectionquotaattribute.h @@ -68,7 +68,7 @@ /** * Destroys the collection quota attribute. */ - ~CollectionQuotaAttribute(); + ~CollectionQuotaAttribute() override; /** * Sets the current quota @p value for the collection. diff --git a/src/core/collectionrightsattribute_p.h b/src/core/collectionrightsattribute_p.h --- a/src/core/collectionrightsattribute_p.h +++ b/src/core/collectionrightsattribute_p.h @@ -53,7 +53,7 @@ /** * Destroys the collection rights attribute. */ - ~CollectionRightsAttribute(); + ~CollectionRightsAttribute() override; /** * Sets the @p rights of the collection. diff --git a/src/core/collectionstatistics.h b/src/core/collectionstatistics.h --- a/src/core/collectionstatistics.h +++ b/src/core/collectionstatistics.h @@ -93,7 +93,7 @@ * @see setCount() * @see unreadCount() */ - qint64 count() const; + Q_REQUIRED_RESULT qint64 count() const; /** * Sets the number of items in this collection. @@ -110,7 +110,7 @@ * @see setUnreadCount() * @see count() */ - qint64 unreadCount() const; + Q_REQUIRED_RESULT qint64 unreadCount() const; /** * Sets the number of unread items in this collection. @@ -127,7 +127,7 @@ * @see setSize() * @since 4.3 */ - qint64 size() const; + Q_REQUIRED_RESULT qint64 size() const; /** * Sets the total size of the items in this collection. diff --git a/src/core/collectionstatistics.cpp b/src/core/collectionstatistics.cpp --- a/src/core/collectionstatistics.cpp +++ b/src/core/collectionstatistics.cpp @@ -32,9 +32,6 @@ public: Private() : QSharedData() - , count(-1) - , unreadCount(-1) - , size(-1) { } @@ -46,9 +43,9 @@ size = other.size; } - qint64 count; - qint64 unreadCount; - qint64 size; + qint64 count = -1; + qint64 unreadCount = -1; + qint64 size = -1; }; CollectionStatistics::CollectionStatistics() diff --git a/src/core/collectionsync.cpp b/src/core/collectionsync.cpp --- a/src/core/collectionsync.cpp +++ b/src/core/collectionsync.cpp @@ -366,7 +366,7 @@ if (localCollection.contentMimeTypes().size() != remoteCollection.contentMimeTypes().size()) { return true; } else { - for (int i = 0; i < remoteCollection.contentMimeTypes().size(); i++) { + for (int i = 0, total = remoteCollection.contentMimeTypes().size(); i < total; ++i) { const QString &m = remoteCollection.contentMimeTypes().at(i); if (!localCollection.contentMimeTypes().contains(m)) { return true; diff --git a/src/core/collectionsync_p.h b/src/core/collectionsync_p.h --- a/src/core/collectionsync_p.h +++ b/src/core/collectionsync_p.h @@ -67,7 +67,7 @@ /** Destroys this job. */ - ~CollectionSync(); + ~CollectionSync() override; /** Sets the result of a full remote collection listing. diff --git a/src/core/collectionutils.h b/src/core/collectionutils.h --- a/src/core/collectionutils.h +++ b/src/core/collectionutils.h @@ -34,40 +34,40 @@ */ namespace CollectionUtils { -inline bool isVirtualParent(const Collection &collection) +Q_REQUIRED_RESULT inline bool isVirtualParent(const Collection &collection) { return (collection.parentCollection() == Collection::root() && collection.isVirtual()); } -inline bool isReadOnly(const Collection &collection) +Q_REQUIRED_RESULT inline bool isReadOnly(const Collection &collection) { return !(collection.rights() &Collection::CanCreateItem); } -inline bool isRoot(const Collection &collection) +Q_REQUIRED_RESULT inline bool isRoot(const Collection &collection) { return (collection == Collection::root()); } -inline bool isResource(const Collection &collection) +Q_REQUIRED_RESULT inline bool isResource(const Collection &collection) { return (collection.parentCollection() == Collection::root()); } -inline bool isStructural(const Collection &collection) +Q_REQUIRED_RESULT inline bool isStructural(const Collection &collection) { return collection.contentMimeTypes().isEmpty(); } -inline bool isFolder(const Collection &collection) +Q_REQUIRED_RESULT inline bool isFolder(const Collection &collection) { return (!isRoot(collection) && !isResource(collection) && !isStructural(collection) && collection.resource() != QLatin1String("akonadi_search_resource")); } -inline QString defaultIconName(const Collection &col) +Q_REQUIRED_RESULT inline QString defaultIconName(const Collection &col) { if (CollectionUtils::isVirtualParent(col)) { return QStringLiteral("edit-find"); @@ -88,24 +88,24 @@ const QStringList content = col.contentMimeTypes(); if ((content.size() == 1) || (content.size() == 2 && content.contains(Collection::mimeType()))) { - if (content.contains(QStringLiteral("text/x-vcard")) || - content.contains(QStringLiteral("text/directory")) || - content.contains(QStringLiteral("text/vcard"))) { + if (content.contains(QLatin1String("text/x-vcard")) || + content.contains(QLatin1String("text/directory")) || + content.contains(QLatin1String("text/vcard"))) { return QStringLiteral("x-office-address-book"); } // TODO: add all other content types and/or fix their mimetypes - if (content.contains(QStringLiteral("akonadi/event")) || content.contains(QStringLiteral("text/ical"))) { + if (content.contains(QLatin1String("akonadi/event")) || content.contains(QLatin1String("text/ical"))) { return QStringLiteral("view-pim-calendar"); } - if (content.contains(QStringLiteral("akonadi/task"))) { + if (content.contains(QLatin1String("akonadi/task"))) { return QStringLiteral("view-pim-tasks"); } } else if (content.isEmpty()) { return QStringLiteral("folder-grey"); } return QStringLiteral("folder"); } -inline QString displayIconName(const Collection &col) +Q_REQUIRED_RESULT inline QString displayIconName(const Collection &col) { QString iconName = defaultIconName(col); if (col.hasAttribute() && @@ -119,7 +119,7 @@ return iconName; } -inline bool hasValidHierarchicalRID(const Collection &col) +Q_REQUIRED_RESULT inline bool hasValidHierarchicalRID(const Collection &col) { if (col == Collection::root()) { return true; @@ -129,7 +129,7 @@ } return hasValidHierarchicalRID(col.parentCollection()); } -inline bool hasValidHierarchicalRID(const Item &item) +Q_REQUIRED_RESULT inline bool hasValidHierarchicalRID(const Item &item) { return !item.remoteId().isEmpty() && hasValidHierarchicalRID(item.parentCollection()); } diff --git a/src/core/commandbuffer_p.h b/src/core/commandbuffer_p.h new file mode 100644 --- /dev/null +++ b/src/core/commandbuffer_p.h @@ -0,0 +1,146 @@ +/* + Copyright (c) 2018 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 AKONADICORE_COMMANDBUFFER_H_ +#define AKONADICORE_COMMANDBUFFER_H_ + +class QObject; + +#include +#include + +#include + +#include "akonadicore_debug.h" + +namespace Akonadi { + +class CommandBufferLocker; +class CommandBufferNotifyBlocker; + +class CommandBuffer +{ + friend class CommandBufferLocker; + friend class CommandBufferNotifyBlocker; +public: + struct Command { + qint64 tag; + Protocol::CommandPtr command; + }; + + CommandBuffer(QObject *parent, const char *notifySlot) + : mParent(parent) + , mNotifySlot(notifySlot) + { + } + + void enqueue(qint64 tag, const Protocol::CommandPtr &command) + { + mCommands.enqueue({ tag, command }); + if (mNotify) { + const bool ok = QMetaObject::invokeMethod(mParent, mNotifySlot.constData(), Qt::QueuedConnection); + Q_ASSERT(ok); Q_UNUSED(ok); + } + } + + inline Command dequeue() + { + return mCommands.dequeue(); + } + + inline bool isEmpty() const + { + return mCommands.isEmpty(); + } + + inline int size() const + { + return mCommands.size(); + } + +private: + QObject *mParent = nullptr; + QByteArray mNotifySlot; + + QQueue mCommands; + QMutex mLock; + + bool mNotify = true; +}; + +class CommandBufferLocker +{ +public: + explicit CommandBufferLocker(CommandBuffer *buffer) + : mBuffer(buffer) + { + relock(); + } + + ~CommandBufferLocker() + { + unlock(); + } + + inline void unlock() + { + if (mLocked) { + mBuffer->mLock.unlock(); + mLocked = false; + } + } + + inline void relock() + { + if (!mLocked) { + mBuffer->mLock.lock(); + mLocked = true; + } + } + +private: + CommandBuffer *mBuffer = nullptr; + bool mLocked = false; +}; + +class CommandBufferNotifyBlocker +{ +public: + explicit CommandBufferNotifyBlocker(CommandBuffer *buffer) + : mBuffer(buffer) + { + mBuffer->mNotify = false; + } + + ~CommandBufferNotifyBlocker() + { + unblock(); + } + + void unblock() + { + mBuffer->mNotify = true; + } +private: + CommandBuffer *mBuffer; +}; + +} // namespace + +#endif diff --git a/src/core/conflicthandler.cpp b/src/core/conflicthandler.cpp --- a/src/core/conflicthandler.cpp +++ b/src/core/conflicthandler.cpp @@ -20,9 +20,6 @@ #include "conflicthandler_p.h" -//KF5 PORT ME -//#include "conflictresolvedialog_p.h" - #include "itemcreatejob.h" #include "itemfetchjob.h" #include "itemfetchscope.h" @@ -71,7 +68,11 @@ } mConflictingItem = fetchJob->items().at(0); +#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) + QMetaObject::invokeMethod(this, &ConflictHandler::resolve, Qt::QueuedConnection); +#else QMetaObject::invokeMethod(this, "resolve", Qt::QueuedConnection); +#endif } void ConflictHandler::resolve() diff --git a/src/core/connection.cpp b/src/core/connection.cpp --- a/src/core/connection.cpp +++ b/src/core/connection.cpp @@ -21,6 +21,8 @@ #include "session_p.h" #include "servermanager_p.h" #include "akonadicore_debug.h" +#include "commandbuffer_p.h" +#include #include #include @@ -30,19 +32,21 @@ #include #include #include +#include +#include -#include #include #include +#include using namespace Akonadi; -Connection::Connection(ConnectionType connType, const QByteArray &sessionId, QObject *parent) +Connection::Connection(ConnectionType connType, const QByteArray &sessionId, + CommandBuffer *commandBuffer, QObject *parent) : QObject(parent) , mConnectionType(connType) - , mSocket(nullptr) - , mLogFile(nullptr) , mSessionId(sessionId) + , mCommandBuffer(commandBuffer) { qRegisterMetaType(); qRegisterMetaType(); @@ -66,19 +70,49 @@ { delete mLogFile; if (mSocket) { - mSocket->disconnect(this); + mSocket->disconnect(); + mSocket->disconnectFromServer(); mSocket->close(); - delete mSocket; + mSocket.reset(); } } void Connection::reconnect() { +#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) + const bool ok = QMetaObject::invokeMethod(this, &Connection::doReconnect, Qt::QueuedConnection); +#else const bool ok = QMetaObject::invokeMethod(this, "doReconnect", Qt::QueuedConnection); +#endif Q_ASSERT(ok); Q_UNUSED(ok) } +QString Connection::defaultAddressForTypeAndMethod(ConnectionType type, const QString &method) +{ + if (method == QLatin1String("UnixPath")) { + const QString defaultSocketDir = StandardDirs::saveDir("data"); + if (type == CommandConnection) { + return defaultSocketDir % QStringLiteral("akonadiserver-cmd.socket"); + } else if (type == NotificationConnection) { + return defaultSocketDir % QStringLiteral("akonadiserver-ntf.socket"); + } + } else if (method == QLatin1String("NamedPipe")) { + QString suffix; + if (Instance::hasIdentifier()) { + suffix += QStringLiteral("%1-").arg(Instance::identifier()); + } + suffix += QString::fromUtf8(QUrl::toPercentEncoding(qApp->applicationDirPath())); + if (type == CommandConnection) { + return QStringLiteral("Akonadi-Cmd-") % suffix; + } else if (type == NotificationConnection) { + return QStringLiteral("Akonadi-Ntf-") % suffix; + } + } + + Q_UNREACHABLE(); +} + void Connection::doReconnect() { Q_ASSERT(QThread::currentThread() == thread()); @@ -89,6 +123,10 @@ return; } + if (ServerManager::self()->state() != ServerManager::Running) { + return; + } + // try to figure out where to connect to QString serverAddress; @@ -117,51 +155,66 @@ // try config file next, fall back to defaults if that fails as well if (serverAddress.isEmpty()) { - const QString connectionConfigFile = SessionPrivate::connectionFile(); + const QString connectionConfigFile = StandardDirs::connectionConfigFile(); const QFileInfo fileInfo(connectionConfigFile); if (!fileInfo.exists()) { qCDebug(AKONADICORE_LOG) << "Akonadi Client Session: connection config file '" - "akonadi/akonadiconnectionrc' can not be found in" - << XdgBaseDirs::homePath("config") << "nor in any of" - << XdgBaseDirs::systemPathList("config"); + "akonadi/akonadiconnectionrc' can not be found!"; } - const QSettings connectionSettings(connectionConfigFile, QSettings::IniFormat); - const QString defaultSocketDir = StandardDirs::saveDir("data"); + QSettings connectionSettings(connectionConfigFile, QSettings::IniFormat); + + QString connectionType; if (mConnectionType == CommandConnection) { - const QString defaultSocketPath = defaultSocketDir % QStringLiteral("/akonadiserver-cmd.socket"); - serverAddress = connectionSettings.value(QStringLiteral("Data/UnixPath"), defaultSocketPath).toString(); + connectionType = QStringLiteral("Data"); } else if (mConnectionType == NotificationConnection) { - const QString defaultSocketPath = defaultSocketDir % QStringLiteral("/akonadiserver-ntf.socket"); - serverAddress = connectionSettings.value(QStringLiteral("Notifications/UnixPath"), defaultSocketPath).toString(); + connectionType = QStringLiteral("Notifications"); } + + connectionSettings.beginGroup(connectionType); + const auto method = connectionSettings.value(QStringLiteral("Method"), QStringLiteral("UnixPath")).toString(); + serverAddress = connectionSettings.value(method, defaultAddressForTypeAndMethod(mConnectionType, method)).toString(); } // create sockets if not yet done, note that this does not yet allow changing socket types on the fly // but that's probably not something we need to support anyway if (!mSocket) { - mSocket = new QLocalSocket(this); - connect(mSocket, static_cast(&QLocalSocket::error), this, + mSocket.reset(new QLocalSocket(this)); + connect(mSocket.data(), static_cast(&QLocalSocket::error), this, [this](QLocalSocket::LocalSocketError) { qCWarning(AKONADICORE_LOG) << mSocket->errorString() << mSocket->serverName(); Q_EMIT socketError(mSocket->errorString()); Q_EMIT socketDisconnected(); }); - connect(mSocket, &QLocalSocket::disconnected, this, &Connection::socketDisconnected); - connect(mSocket, &QLocalSocket::readyRead, this, &Connection::dataReceived); + connect(mSocket.data(), &QLocalSocket::disconnected, this, &Connection::socketDisconnected); + connect(mSocket.data(), &QLocalSocket::readyRead, this, &Connection::handleIncomingData); } // actually do connect qCDebug(AKONADICORE_LOG) << "connectToServer" << serverAddress; mSocket->connectToServer(serverAddress); + if (!mSocket->waitForConnected()) { + qCWarning(AKONADICORE_LOG) << "Failed to connect to server!"; + Q_EMIT socketError(tr("Failed to connect to server!")); + mSocket.reset(); + return; + } + + QTimer::singleShot(0, this, &Connection::handleIncomingData); Q_EMIT reconnected(); } void Connection::forceReconnect() { +#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) + const bool ok = QMetaObject::invokeMethod(this, &Connection::doForceReconnect, + Qt::QueuedConnection); +#else const bool ok = QMetaObject::invokeMethod(this, "doForceReconnect", Qt::QueuedConnection); +#endif + Q_ASSERT(ok); Q_UNUSED(ok) } @@ -172,15 +225,20 @@ if (mSocket) { mSocket->disconnect(this, SIGNAL(socketDisconnected())); - delete mSocket; - mSocket = nullptr; + mSocket->disconnectFromServer(); + mSocket.reset(); } - mSocket = nullptr; } void Connection::closeConnection() { +#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) + const bool ok = QMetaObject::invokeMethod(this, &Connection::doCloseConnection, + Qt::QueuedConnection); +#else const bool ok = QMetaObject::invokeMethod(this, "doCloseConnection", Qt::QueuedConnection); +#endif + Q_ASSERT(ok); Q_UNUSED(ok) } @@ -192,26 +250,34 @@ if (mSocket) { mSocket->close(); mSocket->readAll(); + mSocket.reset(); } } -void Connection::dataReceived() +QLocalSocket *Connection::socket() const +{ + return mSocket.data(); +} + +void Connection::handleIncomingData() { Q_ASSERT(QThread::currentThread() == thread()); - QElapsedTimer timer; - timer.start(); - while (mSocket->bytesAvailable() > 0) { - QDataStream stream(mSocket); + if (!mSocket) { // not connected yet + return; + } + + while (mSocket->bytesAvailable() >= int(sizeof(qint64))) { + Protocol::DataStream stream(mSocket.data()); qint64 tag; - // TODO: Verify the tag matches the last tag we sent stream >> tag; Protocol::CommandPtr cmd; try { - cmd = Protocol::deserialize(mSocket); - } catch (const Akonadi::ProtocolException &) { - // cmd's type will be Invalid by default. + cmd = Protocol::deserialize(mSocket.data()); + } catch (const Akonadi::ProtocolException &e) { + qCWarning(AKONADICORE_LOG) << "Protocol exception:" << e.what(); + // cmd's type will be Invalid by default, so fall-through } if (!cmd || (cmd->type() == Protocol::Command::Invalid)) { qCWarning(AKONADICORE_LOG) << "Invalid command, the world is going to end!"; @@ -235,27 +301,9 @@ Q_ASSERT(cmd->isResponse()); } - Q_EMIT commandReceived(tag, cmd); - /* - if (!handleCommand(tag, cmd)) { - break; - } - */ - - // FIXME: It happens often that data are arriving from the server faster - // than we Jobs can process them which means, that we often process all - // responses in single dataReceived() call and thus not returning to back - // to QEventLoop, which breaks batch-delivery of ItemFetchJob (among other - // things). To workaround that we force processing of events every - // now and then. - // - // Longterm we want something better, like processing and parsing in - // separate thread which would only post the parsed Protocol::Commands - // to the jobs through event loop. That will be overall slower but should - // result in much more responsive applications. - if (timer.elapsed() > 50) { - thread()->eventDispatcher()->processEvents(QEventLoop::ExcludeSocketNotifiers); - timer.restart(); + { + CommandBufferLocker locker(mCommandBuffer); + mCommandBuffer->enqueue(tag, cmd); } } } @@ -285,15 +333,23 @@ } if (mSocket && mSocket->isOpen()) { - QDataStream stream(mSocket); + Protocol::DataStream stream(mSocket.data()); stream << tag; try { - Protocol::serialize(mSocket, cmd); + Protocol::serialize(mSocket.data(), cmd); } catch (const Akonadi::ProtocolException &e) { qCWarning(AKONADICORE_LOG) << "Protocol Exception:" << QString::fromUtf8(e.what()); mSocket->close(); mSocket->readAll(); reconnect(); + return; + } + if (!mSocket->waitForBytesWritten()) { + qCWarning(AKONADICORE_LOG) << "Socket write timeout"; + mSocket->close(); + mSocket->readAll(); + reconnect(); + return; } } else { // TODO: Queue the commands and resend on reconnect? diff --git a/src/core/connection_p.h b/src/core/connection_p.h --- a/src/core/connection_p.h +++ b/src/core/connection_p.h @@ -24,6 +24,7 @@ #include #include #include +#include #include "private/protocol_p.h" @@ -35,6 +36,8 @@ { class SessionThread; +class SessionPrivate; +class CommandBuffer; class AKONADICORE_EXPORT Connection : public QObject { @@ -47,14 +50,21 @@ }; Q_ENUM(ConnectionType) - explicit Connection(ConnectionType connType, const QByteArray &sessionId, QObject *parent = nullptr); + explicit Connection(ConnectionType connType, const QByteArray &sessionId, + CommandBuffer *commandBuffer, QObject *parent = nullptr); ~Connection(); + void setSession(SessionPrivate *session); + + QLocalSocket *socket() const; + Q_INVOKABLE void reconnect(); void forceReconnect(); void closeConnection(); void sendCommand(qint64 tag, const Protocol::CommandPtr &command); + void handleIncomingData(); + Q_SIGNALS: void connected(); void reconnected(); @@ -68,22 +78,16 @@ void doCloseConnection(); void doSendCommand(qint64 tag, const Akonadi::Protocol::CommandPtr &command); - void dataReceived(); private: - + QString defaultAddressForTypeAndMethod(ConnectionType type, const QString &method); bool handleCommand(qint64 tag, const Protocol::CommandPtr &cmd); ConnectionType mConnectionType; - QLocalSocket *mSocket = nullptr; + QScopedPointer mSocket; QFile *mLogFile = nullptr; QByteArray mSessionId; - QMutex mLock; - struct Command { - qint64 tag; - Protocol::CommandPtr cmd; - }; - QQueue mOutQueue; + CommandBuffer *mCommandBuffer; friend class Akonadi::SessionThread; }; diff --git a/src/core/control.h b/src/core/control.h --- a/src/core/control.h +++ b/src/core/control.h @@ -104,9 +104,6 @@ //@cond PRIVATE class Private; Private *const d; - - Q_PRIVATE_SLOT(d, void serverStateChanged(Akonadi::ServerManager::State)) - Q_PRIVATE_SLOT(d, void cleanup()) //@endcond }; diff --git a/src/core/control.cpp b/src/core/control.cpp --- a/src/core/control.cpp +++ b/src/core/control.cpp @@ -54,9 +54,6 @@ Private(Control *parent) : mParent(parent) , mEventLoop(nullptr) - , mSuccess(false) - , mStarting(false) - , mStopping(false) { } @@ -73,10 +70,10 @@ QPointer mParent; QEventLoop *mEventLoop = nullptr; - bool mSuccess; + bool mSuccess = false; - bool mStarting; - bool mStopping; + bool mStarting = false; + bool mStopping = false; }; bool Control::Private::exec() @@ -121,7 +118,7 @@ // mProgressIndicator is a widget, so it better be deleted before the QApplication is deleted // Otherwise we get a crash in QCursor code with Qt-4.5 if (QCoreApplication::instance()) { - connect(QCoreApplication::instance(), SIGNAL(aboutToQuit()), this, SLOT(cleanup())); + connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, [this]() {d->cleanup();}); } } diff --git a/src/core/entityannotationsattribute.cpp b/src/core/entityannotationsattribute.cpp --- a/src/core/entityannotationsattribute.cpp +++ b/src/core/entityannotationsattribute.cpp @@ -101,7 +101,7 @@ if (line.trimmed().isEmpty()) { continue; } - int wsIndex = line.indexOf(' '); + const int wsIndex = line.indexOf(' '); if (wsIndex > 0) { const QByteArray key = line.mid(0, wsIndex); const QByteArray value = line.mid(wsIndex + 1); diff --git a/src/core/entitycache_p.h b/src/core/entitycache_p.h --- a/src/core/entitycache_p.h +++ b/src/core/entitycache_p.h @@ -101,7 +101,7 @@ { } - ~EntityCache() + ~EntityCache() override { qDeleteAll(mCache); } @@ -312,7 +312,7 @@ { } - ~EntityListCache() + ~EntityListCache() override { qDeleteAll(mCache); } diff --git a/src/core/entitydeletedattribute.h b/src/core/entitydeletedattribute.h --- a/src/core/entitydeletedattribute.h +++ b/src/core/entitydeletedattribute.h @@ -55,7 +55,7 @@ /** * Destroys the entity deleted attribute. */ - ~EntityDeletedAttribute(); + ~EntityDeletedAttribute() override; /** * Sets the collection used to restore items which have been moved to trash using a TrashJob * If the Resource is set on the collection, the resource root will be used as fallback during the restore operation. diff --git a/src/core/entitydisplayattribute.h b/src/core/entitydisplayattribute.h --- a/src/core/entitydisplayattribute.h +++ b/src/core/entitydisplayattribute.h @@ -48,7 +48,7 @@ /** * Destroys the entity display attribute. */ - ~EntityDisplayAttribute(); + ~EntityDisplayAttribute() override; /** * Sets the @p name that should be used for display. diff --git a/src/core/entitydisplayattribute.cpp b/src/core/entitydisplayattribute.cpp --- a/src/core/entitydisplayattribute.cpp +++ b/src/core/entitydisplayattribute.cpp @@ -91,6 +91,7 @@ QByteArray EntityDisplayAttribute::serialized() const { QList l; + l.reserve(4); l << ImapParser::quote(d->name.toUtf8()); l << ImapParser::quote(d->icon.toUtf8()); l << ImapParser::quote(d->activeIcon.toUtf8()); diff --git a/src/core/entityhiddenattribute.h b/src/core/entityhiddenattribute.h --- a/src/core/entityhiddenattribute.h +++ b/src/core/entityhiddenattribute.h @@ -70,7 +70,7 @@ /** * Destroys the entity hidden attribute. */ - ~EntityHiddenAttribute(); + ~EntityHiddenAttribute() override; /** * Reimplemented from Attribute diff --git a/src/core/exceptionbase.h b/src/core/exceptionbase.h --- a/src/core/exceptionbase.h +++ b/src/core/exceptionbase.h @@ -63,7 +63,7 @@ /** Destructor. */ - virtual ~Exception() throw(); + ~Exception() throw() override; /** Returns the error message associated with this exception. diff --git a/tests/libs/etm_test_app/mainwindow.h b/src/core/favoritecollectionattribute.h copy from tests/libs/etm_test_app/mainwindow.h copy to src/core/favoritecollectionattribute.h --- a/tests/libs/etm_test_app/mainwindow.h +++ b/src/core/favoritecollectionattribute.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2010 Stephen Kelly + Copyright 2017 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 @@ -17,30 +17,26 @@ 02110-1301, USA. */ -#ifndef MAINWINDOW_H -#define MAINWINDOW_H +#ifndef AKONADI_CORE_FAVORITECOLLECTIONATTRIBUTE_H_ +#define AKONADI_CORE_FAVORITECOLLECTIONATTRIBUTE_H_ -#include +#include "attribute.h" +#include "akonadicore_export.h" -namespace Akonadi -{ -class EntityTreeModel; -} - -class FakeServerData; +namespace Akonadi { -class MainWindow : public QMainWindow +class AKONADICORE_EXPORT FavoriteCollectionAttribute : public Attribute { - Q_OBJECT public: - MainWindow(QWidget *parent = nullptr, Qt::WindowFlags flags = 0); + explicit FavoriteCollectionAttribute(); -private Q_SLOTS: - void moveCollection(); + Attribute *clone() const override; + QByteArray type() const override; -private: - FakeServerData *m_serverData = nullptr; - Akonadi::EntityTreeModel *m_model = nullptr; + void deserialize(const QByteArray &data) override; + QByteArray serialized() const override; }; +} + #endif diff --git a/src/agentbase/resourcesettings.cpp b/src/core/favoritecollectionattribute.cpp copy from src/agentbase/resourcesettings.cpp copy to src/core/favoritecollectionattribute.cpp --- a/src/agentbase/resourcesettings.cpp +++ b/src/core/favoritecollectionattribute.cpp @@ -1,5 +1,5 @@ /* - Copyright (C) 2010-2017 Laurent Montel + Copyright 2017 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 @@ -17,26 +17,31 @@ 02110-1301, USA. */ -#include "resourcesettings.h" +#include "favoritecollectionattribute.h" using namespace Akonadi; -ResourceSettings *ResourceSettings::mSelf = nullptr; +FavoriteCollectionAttribute::FavoriteCollectionAttribute() + : Attribute() +{ +} -ResourceSettings *ResourceSettings::self() +Attribute *FavoriteCollectionAttribute::clone() const { - if (!mSelf) { - mSelf = new ResourceSettings(); - mSelf->load(); - } + return new FavoriteCollectionAttribute(); +} - return mSelf; +QByteArray FavoriteCollectionAttribute::type() const +{ + return QByteArrayLiteral("favorite"); } -ResourceSettings::ResourceSettings() +void FavoriteCollectionAttribute::deserialize(const QByteArray &) { + // unused } -ResourceSettings::~ResourceSettings() +QByteArray FavoriteCollectionAttribute::serialized() const { + return {}; } diff --git a/src/core/firstrun.cpp b/src/core/firstrun.cpp --- a/src/core/firstrun.cpp +++ b/src/core/firstrun.cpp @@ -25,6 +25,7 @@ #include "agentinstancecreatejob.h" #include "agentmanager.h" #include "agenttype.h" +#include #include "akonadicore_debug.h" @@ -77,7 +78,7 @@ void Firstrun::findPendingDefaults() { const KConfigGroup cfg(mConfig, "ProcessedDefaults"); - const QStringList paths = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("akonadi/firstrun"), QStandardPaths::LocateDirectory); + const auto paths = StandardDirs::locateAllResourceDirs(QStringLiteral("akonadi/firstrun")); for (const QString &dirName : paths) { const QStringList files = QDir(dirName).entryList(QDir::Files | QDir::Readable); for (const QString &fileName : files) { @@ -116,7 +117,7 @@ setupNext(); return; } - if (type.capabilities().contains(QStringLiteral("Unique"))) { + if (type.capabilities().contains(QLatin1String("Unique"))) { const Akonadi::AgentInstance::List lstAgents = AgentManager::self()->instances(); for (const AgentInstance &agent : lstAgents) { if (agent.type() == type) { diff --git a/src/core/indexpolicyattribute.h b/src/core/indexpolicyattribute.h --- a/src/core/indexpolicyattribute.h +++ b/src/core/indexpolicyattribute.h @@ -45,12 +45,12 @@ /** * Destroys the index policy attribute. */ - ~IndexPolicyAttribute(); + ~IndexPolicyAttribute() override; /** * Returns whether this collection is supposed to be indexed at all. */ - bool indexingEnabled() const; + Q_REQUIRED_RESULT bool indexingEnabled() const; /** * Sets whether this collection should be indexed at all. diff --git a/src/core/indexpolicyattribute.cpp b/src/core/indexpolicyattribute.cpp --- a/src/core/indexpolicyattribute.cpp +++ b/src/core/indexpolicyattribute.cpp @@ -28,10 +28,9 @@ { public: Private() - : enable(true) { } - bool enable; + bool enable = true; }; IndexPolicyAttribute::IndexPolicyAttribute() @@ -70,6 +69,7 @@ QByteArray IndexPolicyAttribute::serialized() const { QList l; + l.reserve(2); l.append("ENABLE"); l.append(d->enable ? "true" : "false"); return "(" + ImapParser::join(l, " ") + ')'; //krazy:exclude=doublequote_chars diff --git a/src/core/item.h b/src/core/item.h --- a/src/core/item.h +++ b/src/core/item.h @@ -716,35 +716,35 @@ template typename std::enable_if::isPolymorphic, void>::type - setPayloadImpl(const T &p, const int * /*disambiguate*/ = 0); + setPayloadImpl(const T &p, const int * /*disambiguate*/ = nullptr); template typename std::enable_if < !Internal::PayloadTrait::isPolymorphic, void >::type setPayloadImpl(const T &p); template typename std::enable_if::isPolymorphic, T>::type - payloadImpl(const int * /*disambiguate*/ = 0) const; + payloadImpl(const int * /*disambiguate*/ = nullptr) const; template typename std::enable_if < !Internal::PayloadTrait::isPolymorphic, T >::type payloadImpl() const; template typename std::enable_if::isPolymorphic, bool>::type - hasPayloadImpl(const int * /*disambiguate*/ = 0) const; + hasPayloadImpl(const int * /*disambiguate*/ = nullptr) const; template typename std::enable_if < !Internal::PayloadTrait::isPolymorphic, bool >::type hasPayloadImpl() const; template typename std::enable_if::value, bool>::type - tryToClone(T *ret, const int * /*disambiguate*/ = 0) const; + tryToClone(T *ret, const int * /*disambiguate*/ = nullptr) const; template typename std::enable_if < !Internal::is_shared_pointer::value, bool >::type tryToClone(T *ret) const; template typename std::enable_if < !std::is_same::value, bool >::type - tryToCloneImpl(T *ret, const int * /*disambiguate*/ = 0) const; + tryToCloneImpl(T *ret, const int * /*disambiguate*/ = nullptr) const; template typename std::enable_if::value, bool>::type tryToCloneImpl(T *ret) const; @@ -997,7 +997,7 @@ return true; } - return tryToClone(0); + return tryToClone(nullptr); } template diff --git a/src/core/item.cpp b/src/core/item.cpp --- a/src/core/item.cpp +++ b/src/core/item.cpp @@ -648,9 +648,12 @@ void Item::throwPayloadException(int spid, int mtid) const { if (d_ptr->mPayloads.empty()) { + qCDebug(AKONADICORE_LOG) << "Throwing PayloadException: No payload set"; throw PayloadException("No payload set"); } else { - throw PayloadException(QStringLiteral("Wrong payload type (requested: %1; present: %2") + qCDebug(AKONADICORE_LOG) << "Throwing PayloadException: Wrong payload type (requested:" << format_type(spid, mtid) + << "; present: " << format_types(d_ptr->mPayloads) << "), item mime type is" << mimeType(); + throw PayloadException(QStringLiteral("Wrong payload type (requested: %1; present: %2)") .arg(format_type(spid, mtid), format_types(d_ptr->mPayloads))); } } diff --git a/src/core/item_p.h b/src/core/item_p.h --- a/src/core/item_p.h +++ b/src/core/item_p.h @@ -107,7 +107,7 @@ public: operator save_bool() const { - return get() ? &_save_bool::f : 0; + return get() ? &_save_bool::f : nullptr; } }; diff --git a/src/core/itemfetchscope.h b/src/core/itemfetchscope.h --- a/src/core/itemfetchscope.h +++ b/src/core/itemfetchscope.h @@ -109,7 +109,7 @@ * * @see fetchPayloadPart() */ - QSet payloadParts() const; + Q_REQUIRED_RESULT QSet payloadParts() const; /** * Sets which payload parts shall be fetched. @@ -125,7 +125,7 @@ * * @see fetchFullPayload() */ - bool fullPayload() const; + Q_REQUIRED_RESULT bool fullPayload() const; /** * Sets whether the full payload shall be fetched. @@ -142,7 +142,7 @@ * * @see fetchAttribute() */ - QSet attributes() const; + Q_REQUIRED_RESULT QSet attributes() const; /** * Sets whether the attribute of the given @p type should be fetched. @@ -168,7 +168,7 @@ * * @see fetchAllAttributes() */ - bool allAttributes() const; + Q_REQUIRED_RESULT bool allAttributes() const; /** * Sets whether all available attributes should be fetched. @@ -184,7 +184,7 @@ * * @see setCacheOnly() */ - bool cacheOnly() const; + Q_REQUIRED_RESULT bool cacheOnly() const; /** * Sets whether payload data should be requested from remote sources or just @@ -211,7 +211,7 @@ * * @since 4.11 */ - bool checkForCachedPayloadPartsOnly() const; + Q_REQUIRED_RESULT bool checkForCachedPayloadPartsOnly() const; /** * Sets how many levels of ancestor collections should be included in the retrieval. @@ -228,7 +228,7 @@ * @see setAncestorRetrieval() * @since 4.4 */ - AncestorRetrieval ancestorRetrieval() const; + Q_REQUIRED_RESULT AncestorRetrieval ancestorRetrieval() const; /** * Enables retrieval of the item modification time. @@ -245,7 +245,7 @@ * @see setFetchModificationTime() * @since 4.6 */ - bool fetchModificationTime() const; + Q_REQUIRED_RESULT bool fetchModificationTime() const; /** * Enables retrieval of the item GID. @@ -262,7 +262,7 @@ * @see setFetchGid() * @since 4.12 */ - bool fetchGid() const; + Q_REQUIRED_RESULT bool fetchGid() const; /** * Ignore retrieval errors while fetching items, and always deliver what is available. @@ -283,12 +283,12 @@ * @see setIgnoreRetrievalErrors() * @since 4.10 */ - bool ignoreRetrievalErrors() const; + Q_REQUIRED_RESULT bool ignoreRetrievalErrors() const; /** * Returns @c true if there is nothing to fetch. */ - bool isEmpty() const; + Q_REQUIRED_RESULT bool isEmpty() const; /** * Only fetch items that were added or modified after given timestamp @@ -305,7 +305,7 @@ /** * Returns timestamp of the oldest item to fetch. */ - QDateTime fetchChangedSince() const; + Q_REQUIRED_RESULT QDateTime fetchChangedSince() const; /** * Fetch remote identification for items. @@ -326,7 +326,7 @@ * @see setFetchRemoteIdentification() * @since 4.12 */ - bool fetchRemoteIdentification() const; + Q_REQUIRED_RESULT bool fetchRemoteIdentification() const; /** * Fetch tags for items. @@ -346,7 +346,7 @@ * @see setFetchTags() * @since 4.13 */ - bool fetchTags() const; + Q_REQUIRED_RESULT bool fetchTags() const; /** * Sets the tag fetch scope. @@ -389,7 +389,7 @@ * @see setFetchScope() for replacing the current tag fetch scope * @since 4.15 */ - TagFetchScope tagFetchScope() const; + Q_REQUIRED_RESULT TagFetchScope tagFetchScope() const; /** * Returns whether to fetch list of virtual collections the item is linked to @@ -405,7 +405,7 @@ * @see setFetchVirtualReferences() * @since 4.14 */ - bool fetchVirtualReferences() const; + Q_REQUIRED_RESULT bool fetchVirtualReferences() const; /** * Fetch relations for items. @@ -423,7 +423,7 @@ * @see setFetchRelations() * @since 4.15 */ - bool fetchRelations() const; + Q_REQUIRED_RESULT bool fetchRelations() const; private: //@cond PRIVATE diff --git a/src/core/itempayloadinternals_p.h b/src/core/itempayloadinternals_p.h --- a/src/core/itempayloadinternals_p.h +++ b/src/core/itempayloadinternals_p.h @@ -81,15 +81,15 @@ template static T *clone(U) { - return 0; + return nullptr; } }; template struct clone_traits_helper { static T *clone(T *t) { - return t ? t->clone() : 0; + return t ? t->clone() : nullptr; } }; diff --git a/src/core/itemserializer.cpp b/src/core/itemserializer.cpp --- a/src/core/itemserializer.cpp +++ b/src/core/itemserializer.cpp @@ -197,7 +197,7 @@ qCDebug(AKONADICORE_LOG) << " -> found a plugin that feels responsible, trying serialising the payload"; QBuffer buffer; buffer.open(QIODevice::ReadWrite); - int version; + int version = 0; serialize(item, Item::FullPayload, buffer, version); buffer.seek(0); qCDebug(AKONADICORE_LOG) << " -> serialized payload into" << buffer.size() << "bytes" << endl diff --git a/src/core/itemsync.h b/src/core/itemsync.h --- a/src/core/itemsync.h +++ b/src/core/itemsync.h @@ -72,7 +72,7 @@ /** * Destroys the item synchronizer. */ - ~ItemSync(); + ~ItemSync() override; /** * Sets the full item list for the collection. @@ -187,7 +187,7 @@ * @see setBatchSize() * @since 4.14 */ - int batchSize() const; + Q_REQUIRED_RESULT int batchSize() const; /** * Set the batch size. @@ -220,7 +220,7 @@ * @see setMergeMode() * @since 5.1 */ - MergeMode mergeMode() const; + Q_REQUIRED_RESULT MergeMode mergeMode() const; /** * Set what merge method should be used for next ItemSync run diff --git a/src/core/itemsync.cpp b/src/core/itemsync.cpp --- a/src/core/itemsync.cpp +++ b/src/core/itemsync.cpp @@ -513,7 +513,7 @@ void ItemSync::rollback() { Q_D(ItemSync); - qCWarning(AKONADICORE_LOG) << "The item sync is being rolled-back."; + qCDebug(AKONADICORE_LOG) << "The item sync is being rolled-back."; setError(UserCanceled); if (d->mCurrentTransaction) { d->mCurrentTransaction->rollback(); diff --git a/src/core/jobs/agentinstancecreatejob.h b/src/core/jobs/agentinstancecreatejob.h --- a/src/core/jobs/agentinstancecreatejob.h +++ b/src/core/jobs/agentinstancecreatejob.h @@ -93,7 +93,7 @@ /** * Destroys the agent instance create job. */ - ~AgentInstanceCreateJob(); + ~AgentInstanceCreateJob() override; /** * Setup the job to show agent configuration dialog once the agent instance @@ -105,7 +105,7 @@ /** * Returns the AgentInstance object of the newly created agent instance. */ - AgentInstance instance() const; + Q_REQUIRED_RESULT AgentInstance instance() const; /** * Starts the instance creation. diff --git a/src/core/jobs/agentinstancecreatejob.cpp b/src/core/jobs/agentinstancecreatejob.cpp --- a/src/core/jobs/agentinstancecreatejob.cpp +++ b/src/core/jobs/agentinstancecreatejob.cpp @@ -201,7 +201,7 @@ if (!agentValgrind.isEmpty() && agentType.identifier().contains(agentValgrind)) { timeout *= 15; } - +#endif // change the timeout when debugging the agent, because we need time to start the debugger const QString agentDebugging = QString::fromLocal8Bit(qgetenv("AKONADI_DEBUG_WAIT")); if (!agentDebugging.isEmpty()) { @@ -215,7 +215,6 @@ timeout = agentDebuggingTimeout.toInt(); } } -#endif safetyTimer->start(timeout); } } diff --git a/src/core/jobs/collectionattributessynchronizationjob.h b/src/core/jobs/collectionattributessynchronizationjob.h --- a/src/core/jobs/collectionattributessynchronizationjob.h +++ b/src/core/jobs/collectionattributessynchronizationjob.h @@ -67,7 +67,7 @@ /** * Destroys the synchronization job. */ - ~CollectionAttributesSynchronizationJob(); + ~CollectionAttributesSynchronizationJob() override; /* reimpl */ void start() override; diff --git a/src/core/jobs/collectioncopyjob.h b/src/core/jobs/collectioncopyjob.h --- a/src/core/jobs/collectioncopyjob.h +++ b/src/core/jobs/collectioncopyjob.h @@ -73,7 +73,7 @@ /** * Destroys the collection copy job. */ - ~CollectionCopyJob(); + ~CollectionCopyJob() override; protected: void doStart() override; diff --git a/src/core/jobs/collectioncreatejob.h b/src/core/jobs/collectioncreatejob.h --- a/src/core/jobs/collectioncreatejob.h +++ b/src/core/jobs/collectioncreatejob.h @@ -69,12 +69,12 @@ /** * Destroys the collection create job. */ - virtual ~CollectionCreateJob(); + ~CollectionCreateJob() override; /** * Returns the created collection if the job was executed successfully. */ - Collection collection() const; + Q_REQUIRED_RESULT Collection collection() const; protected: void doStart() override; diff --git a/src/core/jobs/collectiondeletejob.h b/src/core/jobs/collectiondeletejob.h --- a/src/core/jobs/collectiondeletejob.h +++ b/src/core/jobs/collectiondeletejob.h @@ -81,7 +81,7 @@ /** * Destroys the collection delete job. */ - ~CollectionDeleteJob(); + ~CollectionDeleteJob() override; protected: void doStart() override; diff --git a/src/core/jobs/collectionfetchjob.h b/src/core/jobs/collectionfetchjob.h --- a/src/core/jobs/collectionfetchjob.h +++ b/src/core/jobs/collectionfetchjob.h @@ -133,12 +133,12 @@ /** * Destroys the collection fetch job. */ - virtual ~CollectionFetchJob(); + ~CollectionFetchJob() override; /** * Returns the list of fetched collection. */ - Collection::List collections() const; + Q_REQUIRED_RESULT Collection::List collections() const; /** * Sets the collection fetch scope. @@ -167,7 +167,7 @@ * @see setFetchScope() for replacing the current collection fetch scope * @since 4.4 */ - CollectionFetchScope &fetchScope(); + Q_REQUIRED_RESULT CollectionFetchScope &fetchScope(); Q_SIGNALS: /** diff --git a/src/core/jobs/collectionfetchjob.cpp b/src/core/jobs/collectionfetchjob.cpp --- a/src/core/jobs/collectionfetchjob.cpp +++ b/src/core/jobs/collectionfetchjob.cpp @@ -44,8 +44,6 @@ CollectionFetchJobPrivate(CollectionFetchJob *parent) : JobPrivate(parent) , mType(CollectionFetchJob::Base) - , mEmitTimer(nullptr) - , mBasePrefetch(false) { } @@ -67,7 +65,7 @@ CollectionFetchScope mScope; Collection::List mPendingCollections; QTimer *mEmitTimer = nullptr; - bool mBasePrefetch; + bool mBasePrefetch = false; Collection::List mPrefetchList; void aboutToFinish() override { @@ -383,7 +381,7 @@ } d_ptr->mCurrentSubJob = nullptr; removeSubjob(job); - QTimer::singleShot(0, this, SLOT(startNext())); + QTimer::singleShot(0, this, [d]() { d->startNext(); }); } else { Job::slotResult(job); } diff --git a/src/core/jobs/collectionmodifyjob.h b/src/core/jobs/collectionmodifyjob.h --- a/src/core/jobs/collectionmodifyjob.h +++ b/src/core/jobs/collectionmodifyjob.h @@ -99,14 +99,14 @@ /** * Destroys the collection modify job. */ - ~CollectionModifyJob(); + ~CollectionModifyJob() override; /** * Returns the modified collection. * * @since 4.4 */ - Collection collection() const; + Q_REQUIRED_RESULT Collection collection() const; protected: void doStart() override; diff --git a/src/core/jobs/collectionstatisticsjob.h b/src/core/jobs/collectionstatisticsjob.h --- a/src/core/jobs/collectionstatisticsjob.h +++ b/src/core/jobs/collectionstatisticsjob.h @@ -79,18 +79,18 @@ /** * Destroys the collection statistics job. */ - virtual ~CollectionStatisticsJob(); + ~CollectionStatisticsJob() override; /** * Returns the fetched collection statistics. */ - CollectionStatistics statistics() const; + Q_REQUIRED_RESULT CollectionStatistics statistics() const; /** * Returns the corresponding collection, if the job was executed successfully, * the collection is already updated. */ - Collection collection() const; + Q_REQUIRED_RESULT Collection collection() const; protected: void doStart() override; diff --git a/src/core/jobs/invalidatecachejob.cpp b/src/core/jobs/invalidatecachejob.cpp --- a/src/core/jobs/invalidatecachejob.cpp +++ b/src/core/jobs/invalidatecachejob.cpp @@ -75,7 +75,7 @@ } ItemFetchJob *itemFetch = new ItemFetchJob(collection, q); - QObject::connect(itemFetch, SIGNAL(result(KJob*)), q, SLOT(itemFetchResult(KJob*))); + QObject::connect(itemFetch, &ItemFetchJob::result, q, [this](KJob* job) { itemFetchResult(job);} ); } void InvalidateCacheJobPrivate::itemFetchResult(KJob *job) diff --git a/src/core/jobs/invalidatecachejob_p.h b/src/core/jobs/invalidatecachejob_p.h --- a/src/core/jobs/invalidatecachejob_p.h +++ b/src/core/jobs/invalidatecachejob_p.h @@ -48,7 +48,6 @@ private: Q_DECLARE_PRIVATE(InvalidateCacheJob) Q_PRIVATE_SLOT(d_func(), void collectionFetchResult(KJob *job)) - Q_PRIVATE_SLOT(d_func(), void itemFetchResult(KJob *job)) Q_PRIVATE_SLOT(d_func(), void itemStoreResult(KJob *job)) }; diff --git a/src/core/jobs/itemcopyjob.h b/src/core/jobs/itemcopyjob.h --- a/src/core/jobs/itemcopyjob.h +++ b/src/core/jobs/itemcopyjob.h @@ -85,7 +85,7 @@ /** * Destroys the item copy job. */ - ~ItemCopyJob(); + ~ItemCopyJob() override; protected: void doStart() override; diff --git a/src/core/jobs/itemcreatejob.h b/src/core/jobs/itemcreatejob.h --- a/src/core/jobs/itemcreatejob.h +++ b/src/core/jobs/itemcreatejob.h @@ -90,12 +90,12 @@ /** * Destroys the item create job. */ - ~ItemCreateJob(); + ~ItemCreateJob() override; /** * Returns the created item with the new unique id, or an invalid item if the job failed. */ - Item item() const; + Q_REQUIRED_RESULT Item item() const; enum MergeOption { NoMerge = 0, ///< Don't merge diff --git a/src/core/jobs/itemcreatejob.cpp b/src/core/jobs/itemcreatejob.cpp --- a/src/core/jobs/itemcreatejob.cpp +++ b/src/core/jobs/itemcreatejob.cpp @@ -43,8 +43,6 @@ public: ItemCreateJobPrivate(ItemCreateJob *parent) : JobPrivate(parent) - , mMergeOptions(ItemCreateJob::NoMerge) - , mItemReceived(false) { } @@ -57,8 +55,8 @@ QSet mForeignParts; QDateTime mDatetime; QByteArray mPendingData; - ItemCreateJob::MergeOptions mMergeOptions; - bool mItemReceived; + ItemCreateJob::MergeOptions mMergeOptions = ItemCreateJob::NoMerge; + bool mItemReceived = false; }; diff --git a/src/core/jobs/itemdeletejob.h b/src/core/jobs/itemdeletejob.h --- a/src/core/jobs/itemdeletejob.h +++ b/src/core/jobs/itemdeletejob.h @@ -128,13 +128,13 @@ /** * Destroys the item delete job. */ - ~ItemDeleteJob(); + ~ItemDeleteJob() override; /** * Returns the items passed on in the constructor. * @since 4.4 */ - Item::List deletedItems() const; + Q_REQUIRED_RESULT Item::List deletedItems() const; protected: void doStart() override; diff --git a/src/core/jobs/itemdeletejob.cpp b/src/core/jobs/itemdeletejob.cpp --- a/src/core/jobs/itemdeletejob.cpp +++ b/src/core/jobs/itemdeletejob.cpp @@ -41,7 +41,7 @@ Item::List mItems; Collection mCollection; - Tag mTag; + Tag mCurrentTag; }; @@ -90,7 +90,7 @@ { Q_D(ItemDeleteJob); - d->mTag = tag; + d->mCurrentTag = tag; } ItemDeleteJob::~ItemDeleteJob() @@ -111,7 +111,7 @@ try { d->sendCommand(Protocol::DeleteItemsCommandPtr::create( d->mItems.isEmpty() ? Scope() : ProtocolHelper::entitySetToScope(d->mItems), - ProtocolHelper::commandContextToProtocol(d->mCollection, d->mTag, d->mItems))); + ProtocolHelper::commandContextToProtocol(d->mCollection, d->mCurrentTag, d->mItems))); } catch (const Akonadi::Exception &e) { setError(Job::Unknown); setErrorText(QString::fromUtf8(e.what())); diff --git a/src/core/jobs/itemfetchjob.h b/src/core/jobs/itemfetchjob.h --- a/src/core/jobs/itemfetchjob.h +++ b/src/core/jobs/itemfetchjob.h @@ -156,7 +156,7 @@ /** * Destroys the item fetch job. */ - virtual ~ItemFetchJob(); + ~ItemFetchJob() override; /** * Returns the fetched items. @@ -166,7 +166,7 @@ * @note The items are invalid before the result(KJob*) * signal has been emitted or if an error occurred. */ - Item::List items() const; + Q_REQUIRED_RESULT Item::List items() const; /** * Save memory by clearing the fetched items. diff --git a/src/core/jobs/itemfetchjob.cpp b/src/core/jobs/itemfetchjob.cpp --- a/src/core/jobs/itemfetchjob.cpp +++ b/src/core/jobs/itemfetchjob.cpp @@ -39,15 +39,11 @@ public: ItemFetchJobPrivate(ItemFetchJob *parent) : JobPrivate(parent) - , mEmitTimer(nullptr) - , mValuePool(nullptr) - , mCount(0) { mCollection = Collection::root(); - mDeliveryOptions = ItemFetchJob::Default; } - ~ItemFetchJobPrivate() + ~ItemFetchJobPrivate() override { delete mValuePool; } @@ -114,15 +110,15 @@ Q_DECLARE_PUBLIC(ItemFetchJob) Collection mCollection; - Tag mTag; + Tag mCurrentTag; Item::List mRequestedItems; Item::List mResultItems; ItemFetchScope mFetchScope; Item::List mPendingItems; // items pending for emitting itemsReceived() QTimer *mEmitTimer = nullptr; ProtocolHelperValuePool *mValuePool = nullptr; - ItemFetchJob::DeliveryOptions mDeliveryOptions; - int mCount; + ItemFetchJob::DeliveryOptions mDeliveryOptions = ItemFetchJob::Default; + int mCount = 0; }; ItemFetchJob::ItemFetchJob(const Collection &collection, QObject *parent) @@ -183,7 +179,7 @@ Q_D(ItemFetchJob); d->init(); - d->mTag = tag; + d->mCurrentTag = tag; d->mValuePool = new ProtocolHelperValuePool; } @@ -198,7 +194,7 @@ try { d->sendCommand(Protocol::FetchItemsCommandPtr::create( d->mRequestedItems.isEmpty() ? Scope() : ProtocolHelper::entitySetToScope(d->mRequestedItems), - ProtocolHelper::commandContextToProtocol(d->mCollection, d->mTag, d->mRequestedItems), + ProtocolHelper::commandContextToProtocol(d->mCollection, d->mCurrentTag, d->mRequestedItems), ProtocolHelper::itemFetchScopeToProtocol(d->mFetchScope))); } catch (const Akonadi::Exception &e) { setError(Job::Unknown); diff --git a/src/core/jobs/itemmodifyjob.h b/src/core/jobs/itemmodifyjob.h --- a/src/core/jobs/itemmodifyjob.h +++ b/src/core/jobs/itemmodifyjob.h @@ -129,7 +129,7 @@ /** * Destroys the item modify job. */ - virtual ~ItemModifyJob(); + ~ItemModifyJob() override; /** * Sets whether the payload of the modified item shall be @@ -144,7 +144,7 @@ * Returns whether the payload of the modified item shall be * omitted from transmission to the Akonadi storage. */ - bool ignorePayload() const; + Q_REQUIRED_RESULT bool ignorePayload() const; /** * Sets whether the GID shall be updated either from the gid parameter or @@ -162,7 +162,7 @@ * Returns whether the GID should be updated. * @since 4.12 */ - bool updateGid() const; + Q_REQUIRED_RESULT bool updateGid() const; /** * Disables the check of the revision number. @@ -176,14 +176,14 @@ * * @note Use this method only when using the single item constructor. */ - Item item() const; + Q_REQUIRED_RESULT Item item() const; /** * Returns the modified and stored items including the changed revision number. * * @since 4.6 */ - Item::List items() const; + Q_REQUIRED_RESULT Item::List items() const; /** * Disables the automatic handling of conflicts. @@ -204,9 +204,6 @@ private: //@cond PRIVATE Q_DECLARE_PRIVATE(ItemModifyJob) - - Q_PRIVATE_SLOT(d_func(), void conflictResolved()) - Q_PRIVATE_SLOT(d_func(), void conflictResolveError(const QString &)) //@endcond }; diff --git a/src/core/jobs/itemmodifyjob.cpp b/src/core/jobs/itemmodifyjob.cpp --- a/src/core/jobs/itemmodifyjob.cpp +++ b/src/core/jobs/itemmodifyjob.cpp @@ -310,14 +310,17 @@ return true; } - if (resp.errorMessage().contains(QStringLiteral("[LLCONFLICT]"))) { + if (resp.errorMessage().contains(QLatin1String("[LLCONFLICT]"))) { if (d->mAutomaticConflictHandlingEnabled) { ConflictHandler *handler = new ConflictHandler(ConflictHandler::LocalLocalConflict, this); handler->setConflictingItems(d->mItems.first(), d->mItems.first()); - connect(handler, SIGNAL(conflictResolved()), SLOT(conflictResolved())); - connect(handler, SIGNAL(error(QString)), SLOT(conflictResolveError(QString))); - + connect(handler, &ConflictHandler::conflictResolved, this, [d]() { d->conflictResolved(); }); + connect(handler, &ConflictHandler::error, this, [d](const QString &str) { d->conflictResolveError(str); }); +#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) + QMetaObject::invokeMethod(handler, &ConflictHandler::start, Qt::QueuedConnection); +#else QMetaObject::invokeMethod(handler, "start", Qt::QueuedConnection); +#endif return true; } } diff --git a/src/core/jobs/itemmovejob.h b/src/core/jobs/itemmovejob.h --- a/src/core/jobs/itemmovejob.h +++ b/src/core/jobs/itemmovejob.h @@ -83,21 +83,21 @@ /** * Destroys the item move job. */ - ~ItemMoveJob(); + ~ItemMoveJob() override; /** * Returns the destination collection. * * @since 4.7 */ - Collection destinationCollection() const; + Q_REQUIRED_RESULT Collection destinationCollection() const; /** * Returns the list of items that where passed in the constructor. * * @since 4.7 */ - Akonadi::Item::List items() const; + Q_REQUIRED_RESULT Akonadi::Item::List items() const; protected: void doStart() override; diff --git a/src/core/jobs/itemsearchjob.h b/src/core/jobs/itemsearchjob.h --- a/src/core/jobs/itemsearchjob.h +++ b/src/core/jobs/itemsearchjob.h @@ -90,7 +90,7 @@ /** * Destroys the item search job. */ - ~ItemSearchJob(); + ~ItemSearchJob() override; /** * Sets the search @p query. @@ -129,7 +129,7 @@ /** * Returns the items that matched the search query. */ - Item::List items() const; + Q_REQUIRED_RESULT Item::List items() const; /** * Search only for items of given mime types. @@ -143,7 +143,7 @@ * * @since 4.13 */ - QStringList mimeTypes() const; + Q_REQUIRED_RESULT QStringList mimeTypes() const; /** * Search only in given collections. @@ -166,7 +166,7 @@ * * @since 4.13 */ - Collection::List searchCollections() const; + Q_REQUIRED_RESULT Collection::List searchCollections() const; /** * Sets whether the search should recurse into collections @@ -217,7 +217,7 @@ * * @since 4.13 */ - bool isRemoteSearchEnabled() const; + Q_REQUIRED_RESULT bool isRemoteSearchEnabled() const; Q_SIGNALS: /** diff --git a/src/core/jobs/itemsearchjob.cpp b/src/core/jobs/itemsearchjob.cpp --- a/src/core/jobs/itemsearchjob.cpp +++ b/src/core/jobs/itemsearchjob.cpp @@ -38,9 +38,6 @@ ItemSearchJobPrivate(ItemSearchJob *parent, const SearchQuery &query) : JobPrivate(parent) , mQuery(query) - , mRecursive(false) - , mRemote(false) - , mEmitTimer(nullptr) { } @@ -88,8 +85,8 @@ SearchQuery mQuery; Collection::List mCollections; QStringList mMimeTypes; - bool mRecursive; - bool mRemote; + bool mRecursive = false; + bool mRemote = false; ItemFetchScope mFetchScope; Item::List mItems; diff --git a/src/core/jobs/job.h b/src/core/jobs/job.h --- a/src/core/jobs/job.h +++ b/src/core/jobs/job.h @@ -130,7 +130,7 @@ /** * Destroys the job. */ - virtual ~Job(); + ~Job() override; /** * Jobs are started automatically once entering the event loop again, no need @@ -142,7 +142,7 @@ * Returns the error string, if there has been an error, an empty * string otherwise. */ - QString errorString() const override; + Q_REQUIRED_RESULT QString errorString() const override; Q_SIGNALS: /** diff --git a/src/core/jobs/job.cpp b/src/core/jobs/job.cpp --- a/src/core/jobs/job.cpp +++ b/src/core/jobs/job.cpp @@ -104,7 +104,12 @@ } else { mParentJob->addSubjob(q); } + publishJob(); +} +void JobPrivate::publishJob() +{ + Q_Q(Job); // if there's a job tracker running, tell it about the new job if (!s_jobtracker) { // Let's only check for the debugging console every 3 seconds, otherwise every single job @@ -120,6 +125,7 @@ QStringLiteral("/jobtracker"), QStringLiteral("org.freedesktop.Akonadi.JobTracker"), KDBusConnectionPool::threadConnection(), nullptr); + mSession->d->publishOtherJobs(q); } else { s_lastTime.restart(); } @@ -181,7 +187,7 @@ emit q->aboutToStart(q); q->doStart(); - QTimer::singleShot(0, q, SLOT(startNext())); + QTimer::singleShot(0, q, [this]() { startNext(); }); QMetaObject::invokeMethod(q, "signalStartedToJobTracker", Qt::QueuedConnection); } @@ -317,7 +323,7 @@ } QString Job::errorString() const -{ +{ QString str; switch (error()) { case NoError: @@ -333,7 +339,7 @@ break; case Unknown: return errorText(); - default: + case UserError: str = i18n("Unknown error."); break; } @@ -348,7 +354,7 @@ bool rv = KCompositeJob::addSubjob(job); if (rv) { connect(job, SIGNAL(aboutToStart(Akonadi::Job*)), SLOT(slotSubJobAboutToStart(Akonadi::Job*))); - QTimer::singleShot(0, this, SLOT(startNext())); + QTimer::singleShot(0, this, [this]() { d_ptr->startNext(); }); } return rv; } @@ -358,7 +364,7 @@ bool rv = KCompositeJob::removeSubjob(job); if (job == d_ptr->mCurrentSubJob) { d_ptr->mCurrentSubJob = nullptr; - QTimer::singleShot(0, this, SLOT(startNext())); + QTimer::singleShot(0, this, [this]() { d_ptr->startNext(); }); } return rv; } @@ -379,7 +385,7 @@ d_ptr->mCurrentSubJob = nullptr; KCompositeJob::slotResult(job); if (!job->error()) { - QTimer::singleShot(0, this, SLOT(startNext())); + QTimer::singleShot(0, this, [this]() { d_ptr->startNext(); }); } } else { // job that was still waiting for execution finished, probably canceled, diff --git a/src/core/jobs/job_p.h b/src/core/jobs/job_p.h --- a/src/core/jobs/job_p.h +++ b/src/core/jobs/job_p.h @@ -39,14 +39,6 @@ public: explicit JobPrivate(Job *parent) : q_ptr(parent) - , mParentJob(nullptr) - , mCurrentSubJob(nullptr) - , mTag(-1) - , mSession(nullptr) - , mWriteFinished(false) - , mReadingFinished(false) - , mStarted(false) - , mFinishPending(false) { } @@ -64,6 +56,7 @@ void signalCreationToJobTracker(); void signalStartedToJobTracker(); void delayedEmitResult(); + void publishJob(); /* Returns a string to display in akonadi console's job tracker. E.g. item ID. */ @@ -74,12 +67,12 @@ /** Returns a new unique command tag for communication with the backend. */ - qint64 newTag(); + Q_REQUIRED_RESULT qint64 newTag(); /** Return the tag used for the request. */ - qint64 tag() const; + Q_REQUIRED_RESULT qint64 tag() const; /** Sends the @p command to the backend @@ -120,19 +113,19 @@ */ virtual void aboutToFinish(); - int protocolVersion() const; + Q_REQUIRED_RESULT int protocolVersion() const; Job *q_ptr; Q_DECLARE_PUBLIC(Job) Job *mParentJob = nullptr; Job *mCurrentSubJob = nullptr; - qint64 mTag; + qint64 mTag = -1; Session *mSession = nullptr; - bool mWriteFinished; - bool mReadingFinished; - bool mStarted; - bool mFinishPending; + bool mWriteFinished = false; + bool mReadingFinished = false; + bool mStarted = false; + bool mFinishPending = false; }; } diff --git a/src/core/jobs/linkjob.h b/src/core/jobs/linkjob.h --- a/src/core/jobs/linkjob.h +++ b/src/core/jobs/linkjob.h @@ -81,7 +81,7 @@ /** * Destroys the link job. */ - ~LinkJob(); + ~LinkJob() override; protected: void doStart() override; diff --git a/src/core/jobs/recursiveitemfetchjob.h b/src/core/jobs/recursiveitemfetchjob.h --- a/src/core/jobs/recursiveitemfetchjob.h +++ b/src/core/jobs/recursiveitemfetchjob.h @@ -101,7 +101,7 @@ /** * Destroys the recursive item fetch job. */ - ~RecursiveItemFetchJob(); + ~RecursiveItemFetchJob() override; /** * Sets the item fetch scope. @@ -133,7 +133,7 @@ /** * Returns the list of fetched items. */ - Akonadi::Item::List items() const; + Q_REQUIRED_RESULT Akonadi::Item::List items() const; /** * Starts the recursive item fetch job. @@ -145,7 +145,6 @@ class Private; Private *const d; - Q_PRIVATE_SLOT(d, void collectionFetchResult(KJob *)) Q_PRIVATE_SLOT(d, void itemFetchResult(KJob *)) //@endcond }; diff --git a/src/core/jobs/recursiveitemfetchjob.cpp b/src/core/jobs/recursiveitemfetchjob.cpp --- a/src/core/jobs/recursiveitemfetchjob.cpp +++ b/src/core/jobs/recursiveitemfetchjob.cpp @@ -123,7 +123,7 @@ job->fetchScope().setContentMimeTypes(d->mMimeTypes); } - connect(job, SIGNAL(result(KJob*)), this, SLOT(collectionFetchResult(KJob*))); + connect(job, &CollectionFetchJob::result, this, [this](KJob *job) { d->collectionFetchResult(job); }); } Akonadi::Item::List RecursiveItemFetchJob::items() const diff --git a/src/core/jobs/relationcreatejob.h b/src/core/jobs/relationcreatejob.h --- a/src/core/jobs/relationcreatejob.h +++ b/src/core/jobs/relationcreatejob.h @@ -48,7 +48,7 @@ /** * Returns the relation. */ - Relation relation() const; + Q_REQUIRED_RESULT Relation relation() const; protected: void doStart() override; diff --git a/src/core/jobs/relationdeletejob.h b/src/core/jobs/relationdeletejob.h --- a/src/core/jobs/relationdeletejob.h +++ b/src/core/jobs/relationdeletejob.h @@ -48,7 +48,7 @@ /** * Returns the relation. */ - Relation relation() const; + Q_REQUIRED_RESULT Relation relation() const; protected: void doStart() override; diff --git a/src/core/jobs/relationfetchjob.h b/src/core/jobs/relationfetchjob.h --- a/src/core/jobs/relationfetchjob.h +++ b/src/core/jobs/relationfetchjob.h @@ -53,7 +53,7 @@ /** * Returns the relations. */ - Relation::List relations() const; + Q_REQUIRED_RESULT Relation::List relations() const; Q_SIGNALS: /** diff --git a/src/core/jobs/resourcesynchronizationjob.h b/src/core/jobs/resourcesynchronizationjob.h --- a/src/core/jobs/resourcesynchronizationjob.h +++ b/src/core/jobs/resourcesynchronizationjob.h @@ -72,15 +72,15 @@ /** * Destroys the synchronization job. */ - ~ResourceSynchronizationJob(); + ~ResourceSynchronizationJob() override; /** * Returns whether a full synchronization will be done, or just the collection tree (without items). * The default is @c false, i.e. a full sync will be requested. * * @since 4.8 */ - bool collectionTreeOnly() const; + Q_REQUIRED_RESULT bool collectionTreeOnly() const; /** * Sets the collectionTreeOnly property. @@ -93,16 +93,16 @@ /** * Returns the resource that has been synchronized. */ - AgentInstance resource() const; + Q_REQUIRED_RESULT AgentInstance resource() const; /* reimpl */ void start() override; /* * @since 5.1 */ void setTimeoutCountLimit(int count); - int timeoutCountLimit() const; + Q_REQUIRED_RESULT int timeoutCountLimit() const; private: //@cond PRIVATE diff --git a/src/core/jobs/resourcesynchronizationjob.cpp b/src/core/jobs/resourcesynchronizationjob.cpp --- a/src/core/jobs/resourcesynchronizationjob.cpp +++ b/src/core/jobs/resourcesynchronizationjob.cpp @@ -36,11 +36,6 @@ public: ResourceSynchronizationJobPrivate(ResourceSynchronizationJob *parent) : q(parent) - , interface(nullptr) - , safetyTimer(nullptr) - , timeoutCount(60) - , collectionTreeOnly(false) - , timeoutCountLimit(0) { } @@ -50,9 +45,9 @@ AgentInstance instance; QDBusInterface *interface = nullptr; QTimer *safetyTimer = nullptr; - int timeoutCount; - bool collectionTreeOnly; - int timeoutCountLimit; + int timeoutCount = 60; + bool collectionTreeOnly = false; + int timeoutCountLimit = 0; void slotSynchronized(); void slotTimeout(); diff --git a/src/core/jobs/searchcreatejob.h b/src/core/jobs/searchcreatejob.h --- a/src/core/jobs/searchcreatejob.h +++ b/src/core/jobs/searchcreatejob.h @@ -89,7 +89,7 @@ * * @since 4.13 */ - QStringList searchMimeTypes() const; + Q_REQUIRED_RESULT QStringList searchMimeTypes() const; /** * Sets list of collections to search in. @@ -107,7 +107,7 @@ * * @since 4.13 */ - QVector searchCollections() const; + Q_REQUIRED_RESULT QVector searchCollections() const; /** * Sets whether resources should be queried too. @@ -136,7 +136,7 @@ * * @since 4.13 */ - bool isRemoteSearchEnabled() const; + Q_REQUIRED_RESULT bool isRemoteSearchEnabled() const; /** * Sets whether the search should recurse into collections @@ -154,20 +154,20 @@ * * @since 4.13 */ - bool isRecursive() const; + Q_REQUIRED_RESULT bool isRecursive() const; /** * Destroys the search create job. */ - ~SearchCreateJob(); + ~SearchCreateJob() override; /** * Returns the newly created search collection once the job finished successfully. Returns an invalid * collection if the job has not yet finished or failed. * * @since 4.4 */ - Collection createdCollection() const; + Q_REQUIRED_RESULT Collection createdCollection() const; protected: /** diff --git a/src/core/jobs/searchcreatejob.cpp b/src/core/jobs/searchcreatejob.cpp --- a/src/core/jobs/searchcreatejob.cpp +++ b/src/core/jobs/searchcreatejob.cpp @@ -37,17 +37,15 @@ : JobPrivate(parent) , mName(name) , mQuery(query) - , mRecursive(false) - , mRemote(false) { } QString mName; SearchQuery mQuery; QStringList mMimeTypes; QVector mCollections; - bool mRecursive; - bool mRemote; + bool mRecursive = false; + bool mRemote = false; Collection mCreatedCollection; @@ -63,7 +61,7 @@ str += QStringLiteral("Recursive "); } if (mRemote) { - str += QStringLiteral("Remote "); + str += QStringLiteral("Remote"); } return str; } diff --git a/src/core/jobs/searchresultjob.cpp b/src/core/jobs/searchresultjob.cpp --- a/src/core/jobs/searchresultjob.cpp +++ b/src/core/jobs/searchresultjob.cpp @@ -31,10 +31,10 @@ public: SearchResultJobPrivate(SearchResultJob *parent); + QVector rid; QByteArray searchId; Collection collection; ImapSet uid; - QVector rid; // JobPrivate interface public: diff --git a/src/core/jobs/searchresultjob_p.h b/src/core/jobs/searchresultjob_p.h --- a/src/core/jobs/searchresultjob_p.h +++ b/src/core/jobs/searchresultjob_p.h @@ -35,10 +35,10 @@ Q_OBJECT public: explicit SearchResultJob(const QByteArray &searchId, const Collection &collection, QObject *parent = nullptr); - virtual ~SearchResultJob(); + ~SearchResultJob() override; void setSearchId(const QByteArray &searchId); - QByteArray searchId() const; + Q_REQUIRED_RESULT QByteArray searchId() const; void setResult(const ImapSet &set); void setResult(const QVector &remoteIds); diff --git a/src/core/jobs/specialcollectionsdiscoveryjob.h b/src/core/jobs/specialcollectionsdiscoveryjob.h --- a/src/core/jobs/specialcollectionsdiscoveryjob.h +++ b/src/core/jobs/specialcollectionsdiscoveryjob.h @@ -50,7 +50,7 @@ /** * Destroys the special collections request job. */ - ~SpecialCollectionsDiscoveryJob(); + ~SpecialCollectionsDiscoveryJob() override; void start() override; diff --git a/src/core/jobs/specialcollectionshelperjobs.cpp b/src/core/jobs/specialcollectionshelperjobs.cpp --- a/src/core/jobs/specialcollectionshelperjobs.cpp +++ b/src/core/jobs/specialcollectionshelperjobs.cpp @@ -216,7 +216,7 @@ fetchJob->fetchScope().setResource(d->mResourceId); fetchJob->fetchScope().setIncludeStatistics(true); fetchJob->fetchScope().setListFilter(CollectionFetchScope::Display); - connect(fetchJob, SIGNAL(result(KJob*)), this, SLOT(fetchResult(KJob*))); + connect(fetchJob, &CollectionFetchJob::result, this, [this](KJob *job) { d->fetchResult(job); }); } // ===================== DefaultResourceJob ============================ @@ -237,20 +237,20 @@ DefaultResourceJob *const q; KCoreConfigSkeleton *mSettings = nullptr; - bool mResourceWasPreexisting = true; - int mPendingModifyJobs = 0; - QString mDefaultResourceType; QVariantMap mDefaultResourceOptions; QList mKnownTypes; QMap mNameForTypeMap; QMap mIconForTypeMap; + QString mDefaultResourceType; + int mPendingModifyJobs = 0; + bool mResourceWasPreexisting = true; }; DefaultResourceJobPrivate::DefaultResourceJobPrivate(KCoreConfigSkeleton *settings, DefaultResourceJob *qq) : q(qq) , mSettings(settings) - , mResourceWasPreexisting(true /* for safety, so as not to accidentally delete data */) , mPendingModifyJobs(0) + , mResourceWasPreexisting(true /* for safety, so as not to accidentally delete data */) { } @@ -273,7 +273,7 @@ CollectionFetchJob *fetchJob = new CollectionFetchJob(Collection::root(), CollectionFetchJob::Recursive, q); fetchJob->fetchScope().setResource(resourceId); fetchJob->fetchScope().setIncludeStatistics(true); - q->connect(fetchJob, SIGNAL(result(KJob*)), q, SLOT(collectionFetchResult(KJob*))); + q->connect(fetchJob, &CollectionFetchJob::result, q, [this](KJob *job) { collectionFetchResult(job); }); } else { // Try harder: maybe the default resource has been removed and another one added // without updating the config file, in this case search for a resource @@ -299,7 +299,7 @@ qCDebug(AKONADICORE_LOG) << "Creating maildir resource."; const AgentType type = AgentManager::self()->type(mDefaultResourceType); AgentInstanceCreateJob *job = new AgentInstanceCreateJob(type, q); - QObject::connect(job, SIGNAL(result(KJob*)), q, SLOT(resourceCreateResult(KJob*))); + QObject::connect(job, &AgentInstanceCreateJob::result, q, [this](KJob *job) { resourceCreateResult(job); }); job->start(); // non-Akonadi::Job } } @@ -376,7 +376,7 @@ // Sync the resource. { ResourceSynchronizationJob *syncJob = new ResourceSynchronizationJob(agent, q); - QObject::connect(syncJob, SIGNAL(result(KJob*)), q, SLOT(resourceSyncResult(KJob*))); + QObject::connect(syncJob, &ResourceSynchronizationJob::result, q, [this](KJob *job) { resourceSyncResult(job); }); syncJob->start(); // non-Akonadi } } @@ -393,7 +393,7 @@ qCDebug(AKONADICORE_LOG) << "Fetching maildir collections."; CollectionFetchJob *fetchJob = new CollectionFetchJob(Collection::root(), CollectionFetchJob::Recursive, q); fetchJob->fetchScope().setResource(defaultResourceId(mSettings)); - QObject::connect(fetchJob, SIGNAL(result(KJob*)), q, SLOT(collectionFetchResult(KJob*))); + QObject::connect(fetchJob, &CollectionFetchJob::result, q, [this](KJob *job) { collectionFetchResult(job); }); } void DefaultResourceJobPrivate::collectionFetchResult(KJob *job) @@ -459,7 +459,7 @@ setCollectionAttributes(collection, type, mNameForTypeMap, mIconForTypeMap); CollectionModifyJob *modifyJob = new CollectionModifyJob(collection, q); - QObject::connect(modifyJob, SIGNAL(result(KJob*)), q, SLOT(collectionModifyResult(KJob*))); + QObject::connect(modifyJob, &CollectionModifyJob::result, q, [this](KJob *job) {collectionModifyResult(job); }); mPendingModifyJobs++; } else { qCDebug(AKONADICORE_LOG) << "Searching for names: " << typeForName.keys(); @@ -598,7 +598,7 @@ mSafetyTimer->setSingleShot(true); mSafetyTimer->setInterval(LOCK_WAIT_TIMEOUT_SECONDS * 1000); mSafetyTimer->start(); - connect(mSafetyTimer, SIGNAL(timeout()), q, SLOT(timeout())); + connect(mSafetyTimer, &QTimer::timeout, q, [this]() { timeout(); }); } } diff --git a/src/core/jobs/specialcollectionshelperjobs_p.h b/src/core/jobs/specialcollectionshelperjobs_p.h --- a/src/core/jobs/specialcollectionshelperjobs_p.h +++ b/src/core/jobs/specialcollectionshelperjobs_p.h @@ -55,12 +55,12 @@ /** Destroys this ResourceScanJob. */ - ~ResourceScanJob(); + ~ResourceScanJob() override; /** Returns the resource ID of the resource being scanned. */ - QString resourceId() const; + Q_REQUIRED_RESULT QString resourceId() const; /** Sets the resource ID of the resource to scan. @@ -72,13 +72,13 @@ This function relies on there being a single top-level collection owned by this resource. */ - Akonadi::Collection rootResourceCollection() const; + Q_REQUIRED_RESULT Akonadi::Collection rootResourceCollection() const; /** Returns all the collections of this resource which have a SpecialCollectionAttribute. These might include the root resource collection. */ - Akonadi::Collection::List specialCollections() const; + Q_REQUIRED_RESULT Akonadi::Collection::List specialCollections() const; protected: /* reimpl */ @@ -88,8 +88,6 @@ class Private; friend class Private; Private *const d; - - Q_PRIVATE_SLOT(d, void fetchResult(KJob *)) }; // ===================== DefaultResourceJob ============================ @@ -164,11 +162,6 @@ private: friend class DefaultResourceJobPrivate; DefaultResourceJobPrivate *const d; - - Q_PRIVATE_SLOT(d, void resourceCreateResult(KJob *)) - Q_PRIVATE_SLOT(d, void resourceSyncResult(KJob *)) - Q_PRIVATE_SLOT(d, void collectionFetchResult(KJob *)) - Q_PRIVATE_SLOT(d, void collectionModifyResult(KJob *)) }; // ===================== GetLockJob ============================ @@ -216,7 +209,6 @@ Q_PRIVATE_SLOT(d, void doStart()) Q_PRIVATE_SLOT(d, void serviceOwnerChanged(QString, QString, QString)) - Q_PRIVATE_SLOT(d, void timeout()) }; // ===================== helper functions ============================ diff --git a/src/core/jobs/specialcollectionsrequestjob.h b/src/core/jobs/specialcollectionsrequestjob.h --- a/src/core/jobs/specialcollectionsrequestjob.h +++ b/src/core/jobs/specialcollectionsrequestjob.h @@ -60,7 +60,7 @@ /** * Destroys the special collections request job. */ - ~SpecialCollectionsRequestJob(); + ~SpecialCollectionsRequestJob() override; /** * Requests a special collection of the given @p type in the default resource. @@ -75,7 +75,7 @@ /** * Returns the requested collection. */ - Collection collection() const; + Q_REQUIRED_RESULT Collection collection() const; protected: /** @@ -125,7 +125,6 @@ SpecialCollectionsRequestJobPrivate *const d; - Q_PRIVATE_SLOT(d, void lockResult(KJob *)) Q_PRIVATE_SLOT(d, void releaseLock()) Q_PRIVATE_SLOT(d, void resourceScanResult(KJob *)) Q_PRIVATE_SLOT(d, void collectionCreateResult(KJob *)) diff --git a/src/core/jobs/specialcollectionsrequestjob.cpp b/src/core/jobs/specialcollectionsrequestjob.cpp --- a/src/core/jobs/specialcollectionsrequestjob.cpp +++ b/src/core/jobs/specialcollectionsrequestjob.cpp @@ -347,7 +347,7 @@ emitResult(); } else { GetLockJob *lockJob = new GetLockJob(this); - connect(lockJob, SIGNAL(result(KJob*)), this, SLOT(lockResult(KJob*))); + connect(lockJob, &GetLockJob::result, this, [this](KJob*job) { d->lockResult(job);}); lockJob->start(); } } diff --git a/src/core/jobs/subscriptionjob_p.h b/src/core/jobs/subscriptionjob_p.h --- a/src/core/jobs/subscriptionjob_p.h +++ b/src/core/jobs/subscriptionjob_p.h @@ -48,7 +48,7 @@ /** * Destroys the subscription job. */ - ~SubscriptionJob(); + ~SubscriptionJob() override; /** * Subscribes to the given list of collections. diff --git a/src/core/jobs/tagcreatejob.h b/src/core/jobs/tagcreatejob.h --- a/src/core/jobs/tagcreatejob.h +++ b/src/core/jobs/tagcreatejob.h @@ -49,7 +49,7 @@ /** * Returns the created tag with the new unique id, or an invalid tag if the job failed. */ - Tag tag() const; + Q_REQUIRED_RESULT Tag tag() const; /** * Merges the tag by GID if it is already existing, and returns the merged version. diff --git a/src/core/jobs/tagcreatejob.cpp b/src/core/jobs/tagcreatejob.cpp --- a/src/core/jobs/tagcreatejob.cpp +++ b/src/core/jobs/tagcreatejob.cpp @@ -31,13 +31,12 @@ public: TagCreateJobPrivate(TagCreateJob *parent) : JobPrivate(parent) - , mMerge(false) { } Tag mTag; Tag mResultTag; - bool mMerge; + bool mMerge = false; }; TagCreateJob::TagCreateJob(const Akonadi::Tag &tag, QObject *parent) diff --git a/src/core/jobs/tagdeletejob.h b/src/core/jobs/tagdeletejob.h --- a/src/core/jobs/tagdeletejob.h +++ b/src/core/jobs/tagdeletejob.h @@ -45,7 +45,7 @@ /** * Returns the tags passed to the constructor. */ - Tag::List tags() const; + Q_REQUIRED_RESULT Tag::List tags() const; protected: void doStart() override; diff --git a/src/core/jobs/tagfetchjob.h b/src/core/jobs/tagfetchjob.h --- a/src/core/jobs/tagfetchjob.h +++ b/src/core/jobs/tagfetchjob.h @@ -112,7 +112,7 @@ /** * Returns the fetched tags after the job has been completed. */ - Tag::List tags() const; + Q_REQUIRED_RESULT Tag::List tags() const; Q_SIGNALS: /** diff --git a/src/core/jobs/tagfetchjob.cpp b/src/core/jobs/tagfetchjob.cpp --- a/src/core/jobs/tagfetchjob.cpp +++ b/src/core/jobs/tagfetchjob.cpp @@ -32,7 +32,6 @@ public: TagFetchJobPrivate(TagFetchJob *parent) : JobPrivate(parent) - , mEmitTimer(nullptr) { } diff --git a/src/core/jobs/tagmodifyjob.h b/src/core/jobs/tagmodifyjob.h --- a/src/core/jobs/tagmodifyjob.h +++ b/src/core/jobs/tagmodifyjob.h @@ -49,7 +49,7 @@ /** * Returns the modified tag. */ - Tag tag() const; + Q_REQUIRED_RESULT Tag tag() const; protected: void doStart() override; diff --git a/src/core/jobs/tagmodifyjob.cpp b/src/core/jobs/tagmodifyjob.cpp --- a/src/core/jobs/tagmodifyjob.cpp +++ b/src/core/jobs/tagmodifyjob.cpp @@ -76,7 +76,7 @@ bool TagModifyJob::doHandleResponse(qint64 tag, const Protocol::CommandPtr &response) { - Q_D(TagModifyJob); + //Q_D(TagModifyJob); if (response->isResponse()) { if (response->type() == Protocol::Command::FetchTags) { diff --git a/src/core/jobs/transactionjobs.h b/src/core/jobs/transactionjobs.h --- a/src/core/jobs/transactionjobs.h +++ b/src/core/jobs/transactionjobs.h @@ -32,7 +32,7 @@ Q_OBJECT public: - ~TransactionJob(); + ~TransactionJob() override; protected: explicit TransactionJob(QObject *parent); diff --git a/src/core/jobs/transactionsequence.h b/src/core/jobs/transactionsequence.h --- a/src/core/jobs/transactionsequence.h +++ b/src/core/jobs/transactionsequence.h @@ -80,7 +80,7 @@ /** * Destroys the transaction sequence. */ - ~TransactionSequence(); + ~TransactionSequence() override; /** * Commits the transaction as soon as all pending sub-jobs finished successfully. diff --git a/src/core/jobs/transactionsequence.cpp b/src/core/jobs/transactionsequence.cpp --- a/src/core/jobs/transactionsequence.cpp +++ b/src/core/jobs/transactionsequence.cpp @@ -32,7 +32,6 @@ TransactionSequencePrivate(TransactionSequence *parent) : JobPrivate(parent) , mState(Idle) - , mAutoCommit(true) { } @@ -48,7 +47,7 @@ TransactionState mState; QSet mIgnoredErrorJobs; - bool mAutoCommit; + bool mAutoCommit = true; void commitResult(KJob *job) { @@ -125,23 +124,27 @@ Job::removeSubjob(job); } - if (!hasSubjobs() && d->mState == TransactionSequencePrivate::WaitingForSubjobs) { - if (property("transactionsDisabled").toBool()) { + if (!hasSubjobs()) { + if (d->mState == TransactionSequencePrivate::WaitingForSubjobs) { + if (property("transactionsDisabled").toBool()) { + emitResult(); + return; + } + d->mState = TransactionSequencePrivate::Committing; + TransactionCommitJob *job = new TransactionCommitJob(this); + connect(job, &TransactionCommitJob::result, [d](KJob *job) { d->commitResult(job);}); + } else if (d->mState == TransactionSequencePrivate::RollingBack) { emitResult(); - return; } - d->mState = TransactionSequencePrivate::Committing; - TransactionCommitJob *job = new TransactionCommitJob(this); - connect(job, SIGNAL(result(KJob*)), SLOT(commitResult(KJob*))); } } else { setError(job->error()); setErrorText(job->errorText()); removeSubjob(job); // cancel all subjobs in case someone else is listening (such as ItemSync), but without notifying ourselves again foreach (KJob *job, subjobs()) { - disconnect(job, SIGNAL(result(KJob*)), this, SLOT(slotResult(KJob*))); + disconnect(job, &KJob::result, this, &TransactionSequence::slotResult); job->kill(EmitResult); } clearSubjobs(); @@ -153,7 +156,7 @@ } d->mState = TransactionSequencePrivate::RollingBack; TransactionRollbackJob *job = new TransactionRollbackJob(this); - connect(job, SIGNAL(result(KJob*)), SLOT(rollbackResult(KJob*))); + connect(job, &TransactionRollbackJob::result, this, [d](KJob *job) { d->rollbackResult(job);}); } } } @@ -181,11 +184,11 @@ if (!error()) { d->mState = TransactionSequencePrivate::Committing; TransactionCommitJob *job = new TransactionCommitJob(this); - connect(job, SIGNAL(result(KJob*)), SLOT(commitResult(KJob*))); + connect(job, &TransactionCommitJob::result, this, [d](KJob *job) { d->commitResult(job);}); } else { d->mState = TransactionSequencePrivate::RollingBack; TransactionRollbackJob *job = new TransactionRollbackJob(this); - connect(job, SIGNAL(result(KJob*)), SLOT(rollbackResult(KJob*))); + connect(job, &TransactionRollbackJob::result, this, [d](KJob *job) { d->rollbackResult(job);}); } } } @@ -234,7 +237,7 @@ d->mState = TransactionSequencePrivate::RollingBack; TransactionRollbackJob *job = new TransactionRollbackJob(this); - connect(job, SIGNAL(result(KJob*)), SLOT(rollbackResult(KJob*))); + connect(job, &TransactionRollbackJob::result, this, [d](KJob *job) { d->rollbackResult(job);}); } #include "moc_transactionsequence.cpp" diff --git a/src/core/jobs/trashjob.h b/src/core/jobs/trashjob.h --- a/src/core/jobs/trashjob.h +++ b/src/core/jobs/trashjob.h @@ -101,7 +101,7 @@ */ explicit TrashJob(const Collection &collection, QObject *parent = nullptr); - ~TrashJob(); + ~TrashJob() override; /** * Ignore configured Trash collections and keep all items local @@ -118,7 +118,7 @@ */ void deleteIfInTrash(bool enable); - Item::List items() const; + Q_REQUIRED_RESULT Item::List items() const; protected: void doStart() override; diff --git a/src/core/jobs/trashrestorejob.h b/src/core/jobs/trashrestorejob.h --- a/src/core/jobs/trashrestorejob.h +++ b/src/core/jobs/trashrestorejob.h @@ -65,16 +65,16 @@ explicit TrashRestoreJob(const Collection &collection, QObject *parent = nullptr); - ~TrashRestoreJob(); + ~TrashRestoreJob() override; /** * Sets the target collection, where the item is moved to. * If not set the item will be restored in the collection saved in the EntityDeletedAttribute. * @param collection the collection to set as target */ void setTargetCollection(const Collection &collection); - Item::List items() const; + Q_REQUIRED_RESULT Item::List items() const; protected: void doStart() override; diff --git a/src/core/jobs/trashrestorejob.cpp b/src/core/jobs/trashrestorejob.cpp --- a/src/core/jobs/trashrestorejob.cpp +++ b/src/core/jobs/trashrestorejob.cpp @@ -226,7 +226,7 @@ return; } - //Explicit target Q_DECL_OVERRIDEs the resource/configured restore collection + //Explicit target overrides the resource/configured restore collection if (mTargetCollection.isValid()) { targetCollection = mTargetCollection; } diff --git a/src/core/jobs/unlinkjob.h b/src/core/jobs/unlinkjob.h --- a/src/core/jobs/unlinkjob.h +++ b/src/core/jobs/unlinkjob.h @@ -81,7 +81,7 @@ /** * Destroys the unlink job. */ - ~UnlinkJob(); + ~UnlinkJob() override; protected: void doStart() override; diff --git a/src/core/mimetypechecker.h b/src/core/mimetypechecker.h --- a/src/core/mimetypechecker.h +++ b/src/core/mimetypechecker.h @@ -140,16 +140,16 @@ * * @see setWantedMimeTypes(), hasWantedMimeTypes() */ - QStringList wantedMimeTypes() const; + Q_REQUIRED_RESULT QStringList wantedMimeTypes() const; /** * Checks whether any wanted MIME types are set. * * @return @c true if any wanted MIME types are set, false otherwise. * * @since 5.6.43 */ - bool hasWantedMimeTypes() const; + Q_REQUIRED_RESULT bool hasWantedMimeTypes() const; /** * Sets the list of wanted MIME types this instance checks against. @@ -189,7 +189,7 @@ * @see setWantedMimeTypes() * @see Item::mimeType() */ - bool isWantedItem(const Item &item) const; + Q_REQUIRED_RESULT bool isWantedItem(const Item &item) const; /** * Checks whether a given @p collection has one of the wanted MIME types @@ -203,7 +203,7 @@ * @see setWantedMimeTypes() * @see Collection::contentMimeTypes() */ - bool isWantedCollection(const Collection &collection) const; + Q_REQUIRED_RESULT bool isWantedCollection(const Collection &collection) const; /** * Checks whether a given mime type is covered by one of the wanted MIME types. @@ -215,7 +215,7 @@ * * @since 4.6 */ - bool isWantedMimeType(const QString &mimeType) const; + Q_REQUIRED_RESULT bool isWantedMimeType(const QString &mimeType) const; /** * Checks whether any of the given MIME types is covered by one of the wanted MIME types. @@ -227,7 +227,7 @@ * * @since 4.6 */ - bool containsWantedMimeType(const QStringList &mimeTypes) const; + Q_REQUIRED_RESULT bool containsWantedMimeType(const QStringList &mimeTypes) const; /** * Checks whether a given @p item has the given wanted MIME type @@ -241,7 +241,7 @@ * @see setWantedMimeTypes() * @see Item::mimeType() */ - static bool isWantedItem(const Item &item, const QString &wantedMimeType); + Q_REQUIRED_RESULT static bool isWantedItem(const Item &item, const QString &wantedMimeType); /** * Checks whether a given @p collection has the given MIME type @@ -256,7 +256,7 @@ * @see setWantedMimeTypes() * @see Collection::contentMimeTypes() */ - static bool isWantedCollection(const Collection &collection, const QString &wantedMimeType); + Q_REQUIRED_RESULT static bool isWantedCollection(const Collection &collection, const QString &wantedMimeType); private: //@cond PRIVATE diff --git a/src/core/models/agentfilterproxymodel.h b/src/core/models/agentfilterproxymodel.h --- a/src/core/models/agentfilterproxymodel.h +++ b/src/core/models/agentfilterproxymodel.h @@ -64,7 +64,7 @@ /** * Destroys the agent filter proxy model. */ - ~AgentFilterProxyModel(); + ~AgentFilterProxyModel() override; /** * Accept agents supporting @p mimeType. diff --git a/src/core/models/agentinstancemodel.h b/src/core/models/agentinstancemodel.h --- a/src/core/models/agentinstancemodel.h +++ b/src/core/models/agentinstancemodel.h @@ -81,17 +81,17 @@ /** * Destroys the agent instance model. */ - virtual ~AgentInstanceModel(); - - QHash roleNames() const override; - int columnCount(const QModelIndex &parent = QModelIndex()) const override; - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; - QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; - QModelIndex parent(const QModelIndex &index) const override; - Qt::ItemFlags flags(const QModelIndex &index) const override; - bool setData(const QModelIndex &index, const QVariant &value, int role) override; + ~AgentInstanceModel() override; + + Q_REQUIRED_RESULT QHash roleNames() const override; + Q_REQUIRED_RESULT int columnCount(const QModelIndex &parent = QModelIndex()) const override; + Q_REQUIRED_RESULT int rowCount(const QModelIndex &parent = QModelIndex()) const override; + Q_REQUIRED_RESULT QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + Q_REQUIRED_RESULT QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + Q_REQUIRED_RESULT QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; + Q_REQUIRED_RESULT QModelIndex parent(const QModelIndex &index) const override; + Q_REQUIRED_RESULT Qt::ItemFlags flags(const QModelIndex &index) const override; + Q_REQUIRED_RESULT bool setData(const QModelIndex &index, const QVariant &value, int role) override; private: //@cond PRIVATE diff --git a/src/core/models/agentinstancemodel.cpp b/src/core/models/agentinstancemodel.cpp --- a/src/core/models/agentinstancemodel.cpp +++ b/src/core/models/agentinstancemodel.cpp @@ -92,12 +92,12 @@ this, [this](const Akonadi::AgentInstance &inst) { d->instanceAdded(inst);}); connect(AgentManager::self(), &AgentManager::instanceRemoved, this, [this](const Akonadi::AgentInstance &inst) { d->instanceRemoved(inst);}); - connect(AgentManager::self(), SIGNAL(instanceStatusChanged(Akonadi::AgentInstance)), - this, SLOT(instanceChanged(Akonadi::AgentInstance))); - connect(AgentManager::self(), SIGNAL(instanceProgressChanged(Akonadi::AgentInstance)), - this, SLOT(instanceChanged(Akonadi::AgentInstance))); - connect(AgentManager::self(), SIGNAL(instanceNameChanged(Akonadi::AgentInstance)), - this, SLOT(instanceChanged(Akonadi::AgentInstance))); + connect(AgentManager::self(), &AgentManager::instanceStatusChanged, + this, [this](const Akonadi::AgentInstance &inst) { d->instanceChanged(inst);}); + connect(AgentManager::self(), &AgentManager::instanceProgressChanged, + this, [this](const Akonadi::AgentInstance &inst) { d->instanceChanged(inst);}); + connect(AgentManager::self(), &AgentManager::instanceNameChanged, + this, [this](const Akonadi::AgentInstance &inst) { d->instanceChanged(inst);}); connect(AgentManager::self(), SIGNAL(instanceOnline(Akonadi::AgentInstance,bool)), this, SLOT(instanceChanged(Akonadi::AgentInstance))); } @@ -191,10 +191,8 @@ switch (section) { case 0: return i18nc("@title:column, name of a thing", "Name"); - break; default: return QVariant(); - break; } } diff --git a/src/core/models/agenttypemodel.h b/src/core/models/agenttypemodel.h --- a/src/core/models/agenttypemodel.h +++ b/src/core/models/agenttypemodel.h @@ -73,14 +73,14 @@ /** * Destroys the agent type model. */ - virtual ~AgentTypeModel(); + ~AgentTypeModel() override; - int columnCount(const QModelIndex &parent = QModelIndex()) const override; - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; - QModelIndex parent(const QModelIndex &index) const override; - Qt::ItemFlags flags(const QModelIndex &index) const override; + Q_REQUIRED_RESULT int columnCount(const QModelIndex &parent = QModelIndex()) const override; + Q_REQUIRED_RESULT int rowCount(const QModelIndex &parent = QModelIndex()) const override; + Q_REQUIRED_RESULT QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + Q_REQUIRED_RESULT QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; + Q_REQUIRED_RESULT QModelIndex parent(const QModelIndex &index) const override; + Q_REQUIRED_RESULT Qt::ItemFlags flags(const QModelIndex &index) const override; private: //@cond PRIVATE diff --git a/src/core/models/agenttypemodel.cpp b/src/core/models/agenttypemodel.cpp --- a/src/core/models/agenttypemodel.cpp +++ b/src/core/models/agenttypemodel.cpp @@ -142,7 +142,7 @@ } const AgentType &type = d->mTypes[index.row()]; - if (type.capabilities().contains(QStringLiteral("Unique")) && + if (type.capabilities().contains(QLatin1String("Unique")) && AgentManager::self()->instance(type.identifier()).isValid()) { return QAbstractItemModel::flags(index) & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled); } diff --git a/src/core/models/collectionfilterproxymodel.h b/src/core/models/collectionfilterproxymodel.h --- a/src/core/models/collectionfilterproxymodel.h +++ b/src/core/models/collectionfilterproxymodel.h @@ -67,7 +67,7 @@ /** * Destroys the collection proxy filter model. */ - virtual ~CollectionFilterProxyModel(); + ~CollectionFilterProxyModel() override; /** * Adds a list of mime types to be shown by the filter. @@ -86,7 +86,7 @@ /** * Returns the list of mime type filters. */ - QStringList mimeTypeFilters() const; + Q_REQUIRED_RESULT QStringList mimeTypeFilters() const; /** * Sets whether we want virtual collections to be filtered or not. @@ -100,14 +100,14 @@ /* * @since 4.12 */ - bool excludeVirtualCollections() const; + Q_REQUIRED_RESULT bool excludeVirtualCollections() const; /** * Clears all mime type filters. */ void clearFilters(); - Qt::ItemFlags flags(const QModelIndex &index) const override; + Q_REQUIRED_RESULT Qt::ItemFlags flags(const QModelIndex &index) const override; protected: bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; diff --git a/src/core/models/collectionfilterproxymodel.cpp b/src/core/models/collectionfilterproxymodel.cpp --- a/src/core/models/collectionfilterproxymodel.cpp +++ b/src/core/models/collectionfilterproxymodel.cpp @@ -35,7 +35,6 @@ public: Private(CollectionFilterProxyModel *parent) : mParent(parent) - , mExcludeVirtualCollections(false) { mimeChecker.addWantedMimeType(QStringLiteral("text/uri-list")); } @@ -45,7 +44,7 @@ QVector< QModelIndex > acceptedResources; CollectionFilterProxyModel *mParent = nullptr; MimeTypeChecker mimeChecker; - bool mExcludeVirtualCollections; + bool mExcludeVirtualCollections = false; }; bool CollectionFilterProxyModel::Private::collectionAccepted(const QModelIndex &index, bool checkResourceVisibility) @@ -167,7 +166,7 @@ { if (!index.isValid()) { // Don't crash - return 0; + return Qt::NoItemFlags; } const Collection collection = sourceModel()->data(mapToSource(index), EntityTreeModel::CollectionRole).value(); diff --git a/src/core/models/collectionmodel.h b/src/core/models/collectionmodel.h --- a/src/core/models/collectionmodel.h +++ b/src/core/models/collectionmodel.h @@ -60,8 +60,6 @@ * Describes the roles for collections. */ enum Roles { - OldCollectionIdRole = Qt::UserRole + 1, ///< The collection identifier. For binary compatibility to <4.3 - OldCollectionRole = Qt::UserRole + 2, ///< The actual collection object. For binary compatibility to <4.3 CollectionIdRole = Qt::UserRole + 10, ///< The collection identifier. CollectionRole = Qt::UserRole + 11, ///< The actual collection object. UserRole = Qt::UserRole + 42 ///< Role for user extensions. @@ -77,7 +75,7 @@ /** * Destroys the collection model. */ - virtual ~CollectionModel(); + ~CollectionModel() override; /** * Sets whether collection statistics information shall be provided @@ -93,19 +91,19 @@ */ void includeUnsubscribed(bool include = true); - int columnCount(const QModelIndex &parent = QModelIndex()) const override; - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; - QModelIndex parent(const QModelIndex &index) const override; - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; - bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role = Qt::EditRole) override; - bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; - Qt::ItemFlags flags(const QModelIndex &index) const override; - Qt::DropActions supportedDropActions() const override; - QMimeData *mimeData(const QModelIndexList &indexes) const override; - bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override; - QStringList mimeTypes() const override; + Q_REQUIRED_RESULT int columnCount(const QModelIndex &parent = QModelIndex()) const override; + Q_REQUIRED_RESULT QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + Q_REQUIRED_RESULT QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; + Q_REQUIRED_RESULT QModelIndex parent(const QModelIndex &index) const override; + Q_REQUIRED_RESULT int rowCount(const QModelIndex &parent = QModelIndex()) const override; + Q_REQUIRED_RESULT QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + Q_REQUIRED_RESULT bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role = Qt::EditRole) override; + Q_REQUIRED_RESULT bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; + Q_REQUIRED_RESULT Qt::ItemFlags flags(const QModelIndex &index) const override; + Q_REQUIRED_RESULT Qt::DropActions supportedDropActions() const override; + Q_REQUIRED_RESULT QMimeData *mimeData(const QModelIndexList &indexes) const override; + Q_REQUIRED_RESULT bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override; + Q_REQUIRED_RESULT QStringList mimeTypes() const override; protected: /** @@ -129,7 +127,6 @@ Q_PRIVATE_SLOT(d_func(), void collectionStatisticsChanged(Akonadi::Collection::Id, const Akonadi::CollectionStatistics &)) Q_PRIVATE_SLOT(d_func(), void listDone(KJob *)) - Q_PRIVATE_SLOT(d_func(), void editDone(KJob *)) Q_PRIVATE_SLOT(d_func(), void dropResult(KJob *)) Q_PRIVATE_SLOT(d_func(), void collectionsChanged(const Akonadi::Collection::List &)) //@endcond diff --git a/src/core/models/collectionmodel.cpp b/src/core/models/collectionmodel.cpp --- a/src/core/models/collectionmodel.cpp +++ b/src/core/models/collectionmodel.cpp @@ -93,10 +93,8 @@ return d->iconForCollection(col); } break; - case OldCollectionIdRole: // fall-through case CollectionIdRole: return col.id(); - case OldCollectionRole: // fall-through case CollectionRole: return QVariant::fromValue(col); } @@ -201,7 +199,7 @@ } col.setName(value.toString()); CollectionModifyJob *job = new CollectionModifyJob(col, d->session); - connect(job, SIGNAL(result(KJob*)), SLOT(editDone(KJob*))); + connect(job, &CollectionModifyJob::result, this, [d](KJob* job) { d->editDone(job); }); return true; } return QAbstractItemModel::setData(index, value, role); diff --git a/src/core/models/collectionmodel_p.h b/src/core/models/collectionmodel_p.h --- a/src/core/models/collectionmodel_p.h +++ b/src/core/models/collectionmodel_p.h @@ -48,10 +48,6 @@ Q_DECLARE_PUBLIC(CollectionModel) explicit CollectionModelPrivate(CollectionModel *parent) : q_ptr(parent) - , monitor(0) - , session(0) - , fetchStatistics(false) - , unsubscribed(false) , headerContent(i18nc("@title:column, name of a thing", "Name")) { } diff --git a/src/core/models/collectionmodel_p.cpp b/src/core/models/collectionmodel_p.cpp --- a/src/core/models/collectionmodel_p.cpp +++ b/src/core/models/collectionmodel_p.cpp @@ -296,7 +296,7 @@ session = new Session(QCoreApplication::instance()->applicationName().toUtf8() + QByteArray("-CollectionModel-") + QByteArray::number(qrand()), q); - QTimer::singleShot(0, q, SLOT(startFirstListJob())); + QTimer::singleShot(0, q, [this] { startFirstListJob(); }); // monitor collection changes monitor = new Monitor(); diff --git a/src/core/models/entitymimetypefiltermodel.h b/src/core/models/entitymimetypefiltermodel.h --- a/src/core/models/entitymimetypefiltermodel.h +++ b/src/core/models/entitymimetypefiltermodel.h @@ -74,7 +74,7 @@ /** * Destroys the entity mime type filter model. */ - virtual ~EntityMimeTypeFilterModel(); + ~EntityMimeTypeFilterModel() override; /** * Add mime types to be shown by the filter. @@ -107,12 +107,12 @@ /** * Returns the list of mime type inclusion filters. */ - QStringList mimeTypeInclusionFilters() const; + Q_REQUIRED_RESULT QStringList mimeTypeInclusionFilters() const; /** * Returns the list of mime type exclusion filters. */ - QStringList mimeTypeExclusionFilters() const; + Q_REQUIRED_RESULT QStringList mimeTypeExclusionFilters() const; /** * Clear all mime type filters. @@ -128,13 +128,13 @@ QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; - bool hasChildren(const QModelIndex &parent = QModelIndex()) const override; + Q_REQUIRED_RESULT bool hasChildren(const QModelIndex &parent = QModelIndex()) const override; - bool canFetchMore(const QModelIndex &parent) const override; + Q_REQUIRED_RESULT bool canFetchMore(const QModelIndex &parent) const override; - QModelIndexList match(const QModelIndex &start, int role, const QVariant &value, int hits = 1, Qt::MatchFlags flags = Qt::MatchFlags(Qt::MatchStartsWith | Qt::MatchWrap)) const override; + Q_REQUIRED_RESULT QModelIndexList match(const QModelIndex &start, int role, const QVariant &value, int hits = 1, Qt::MatchFlags flags = Qt::MatchFlags(Qt::MatchStartsWith | Qt::MatchWrap)) const override; - int columnCount(const QModelIndex &parent = QModelIndex()) const override; + Q_REQUIRED_RESULT int columnCount(const QModelIndex &parent = QModelIndex()) const override; protected: bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; diff --git a/src/core/models/entityorderproxymodel.h b/src/core/models/entityorderproxymodel.h --- a/src/core/models/entityorderproxymodel.h +++ b/src/core/models/entityorderproxymodel.h @@ -23,6 +23,7 @@ #define AKONADI_ENTITYORDERPROXYMODEL_H #include +#include "collection.h" #include "akonadicore_export.h" @@ -56,7 +57,7 @@ /** * Destroys the entity order proxy model. */ - virtual ~EntityOrderProxyModel(); + ~EntityOrderProxyModel() override; /** * Sets the config @p group that will be used for storing the order. @@ -74,26 +75,29 @@ /** * @reimp */ - bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; + Q_REQUIRED_RESULT bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; /** * @reimp */ - bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override; + Q_REQUIRED_RESULT bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override; /** * @reimp */ - QModelIndexList match(const QModelIndex &start, int role, const QVariant &value, int hits = 1, + Q_REQUIRED_RESULT QModelIndexList match(const QModelIndex &start, int role, const QVariant &value, int hits = 1, Qt::MatchFlags flags = Qt::MatchFlags(Qt::MatchStartsWith | Qt::MatchWrap)) const override; protected: EntityOrderProxyModelPrivate *const d_ptr; virtual QString parentConfigString(const QModelIndex &index) const; virtual QString configString(const QModelIndex &index) const; + virtual Akonadi::Collection parentCollection(const QModelIndex &index) const; private: + QStringList configStringsForDroppedUrls(const QList &urls, const Akonadi::Collection &parentCol, bool *containsMove) const; + //@cond PRIVATE Q_DECLARE_PRIVATE(EntityOrderProxyModel) //@endcond diff --git a/src/core/models/entityorderproxymodel.cpp b/src/core/models/entityorderproxymodel.cpp --- a/src/core/models/entityorderproxymodel.cpp +++ b/src/core/models/entityorderproxymodel.cpp @@ -76,16 +76,27 @@ Q_EMIT layoutChanged(); } +// reimplemented in FavoriteCollectionOrderProxyModel +Collection EntityOrderProxyModel::parentCollection(const QModelIndex &index) const +{ + return index.data(EntityTreeModel::ParentCollectionRole).value(); +} + +static QString configKey(const Collection &col) +{ + return !col.isValid() ? QStringLiteral("0") : QString::number(col.id()); +} + bool EntityOrderProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const { Q_D(const EntityOrderProxyModel); if (!d->m_orderConfig.isValid()) { return KRecursiveFilterProxyModel::lessThan(left, right); } - Collection col = left.data(EntityTreeModel::ParentCollectionRole).value(); + const Collection col = parentCollection(left); - const QStringList list = d->m_orderConfig.readEntry(QString::number(col.id()), QStringList()); + const QStringList list = d->m_orderConfig.readEntry(configKey(col), QStringList()); if (list.isEmpty()) { return KRecursiveFilterProxyModel::lessThan(left, right); @@ -104,6 +115,45 @@ return leftPosition < rightPosition; } +QStringList EntityOrderProxyModel::configStringsForDroppedUrls(const QList &urls, const Akonadi::Collection &parentCol, bool *containsMove) const +{ + QStringList droppedList; + droppedList.reserve(urls.count()); + for (const QUrl &url : urls) { + Collection col = Collection::fromUrl(url); + + if (!col.isValid()) { + Item item = Item::fromUrl(url); + if (!item.isValid()) { + continue; + } + + const QModelIndexList list = EntityTreeModel::modelIndexesForItem(this, item); + if (list.isEmpty()) { + continue; + } + + if (!*containsMove && parentCollection(list.first()).id() != parentCol.id()) { + *containsMove = true; + } + + droppedList << configString(list.first()); + } else { + const QModelIndex idx = EntityTreeModel::modelIndexForCollection(this, col); + if (!idx.isValid()) { + continue; + } + + if (!*containsMove && parentCollection(idx).id() != parentCol.id()) { + *containsMove = true; + } + + droppedList << configString(idx); + } + } + return droppedList; +} + bool EntityOrderProxyModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { Q_D(EntityOrderProxyModel); @@ -120,9 +170,11 @@ return KRecursiveFilterProxyModel::dropMimeData(data, action, row, column, parent); } - bool containsMove = false; const QList urls = data->urls(); + if (urls.isEmpty()) { + return false; + } Collection parentCol; @@ -134,47 +186,24 @@ } const QModelIndex targetIndex = index(0, column, parent); - - parentCol = targetIndex.data(EntityTreeModel::ParentCollectionRole).value(); + parentCol = parentCollection(targetIndex); } - QStringList droppedList; - for (const QUrl &url : urls) { - Collection col = Collection::fromUrl(url); - - if (!col.isValid()) { - Item item = Item::fromUrl(url); - if (!item.isValid()) { - continue; - } - const QModelIndexList list = EntityTreeModel::modelIndexesForItem(this, item); - if (list.isEmpty()) { - continue; - } - - if (!containsMove && list.first().data(EntityTreeModel::ParentCollectionRole).value().id() != parentCol.id()) { - containsMove = true; - } - - droppedList << configString(list.first()); - } else { - const QModelIndex idx = EntityTreeModel::modelIndexForCollection(this, col); - if (!idx.isValid()) { - continue; - } - - if (!containsMove && idx.data(EntityTreeModel::ParentCollectionRole).value().id() != parentCol.id()) { - containsMove = true; - } + bool containsMove = false; + QStringList droppedList = configStringsForDroppedUrls(urls, parentCol, &containsMove); - droppedList << configString(idx); + // Dropping new favorite folders + if (droppedList.isEmpty()) { + const bool ok = KRecursiveFilterProxyModel::dropMimeData(data, action, row, column, parent); + if (ok) { + droppedList = configStringsForDroppedUrls(urls, parentCol, &containsMove); } } QStringList existingList; if (d->m_orderConfig.hasKey(QString::number(parentCol.id()))) { - existingList = d->m_orderConfig.readEntry(QString::number(parentCol.id()), QStringList()); + existingList = d->m_orderConfig.readEntry(configKey(parentCol), QStringList()); } else { const int rowCount = this->rowCount(parent); existingList.reserve(rowCount); @@ -192,7 +221,7 @@ existingList.insert(row + i - (existingIndex > row ? 0 : 1), droppedList.at(i)); } - d->m_orderConfig.writeEntry(QString::number(parentCol.id()), existingList); + d->m_orderConfig.writeEntry(configKey(parentCol), existingList); if (containsMove) { bool result = KRecursiveFilterProxyModel::dropMimeData(data, action, row, column, parent); @@ -233,7 +262,7 @@ static const int column = 0; QModelIndex childIndex = q->index(0, column, parent); - QString parentKey = q->parentConfigString(childIndex); + const QString parentKey = q->parentConfigString(childIndex); if (parentKey.isEmpty()) { return; @@ -243,7 +272,7 @@ list << q->configString(childIndex); saveOrder(childIndex); - list.reserve(rowCount); + list.reserve(list.count() + rowCount); for (int row = 1; row < rowCount; ++row) { childIndex = q->index(row, column, parent); list << q->configString(childIndex); @@ -255,7 +284,7 @@ QString EntityOrderProxyModel::parentConfigString(const QModelIndex &index) const { - const Collection col = index.data(EntityTreeModel::ParentCollectionRole).value(); + const Collection col = parentCollection(index); Q_ASSERT(col.isValid()); if (!col.isValid()) { diff --git a/src/core/models/entityrightsfiltermodel.h b/src/core/models/entityrightsfiltermodel.h --- a/src/core/models/entityrightsfiltermodel.h +++ b/src/core/models/entityrightsfiltermodel.h @@ -73,7 +73,7 @@ /** * Destroys the entity rights filter model. */ - virtual ~EntityRightsFilterModel(); + ~EntityRightsFilterModel() override; /** * Sets the access @p rights the entities shall be filtered @@ -86,17 +86,17 @@ /** * Returns the access rights that are used for filtering. */ - Collection::Rights accessRights() const; + Q_REQUIRED_RESULT Collection::Rights accessRights() const; /** * @reimp */ - Qt::ItemFlags flags(const QModelIndex &index) const override; + Q_REQUIRED_RESULT Qt::ItemFlags flags(const QModelIndex &index) const override; /** * @reimp */ - QModelIndexList match(const QModelIndex &start, int role, const QVariant &value, int hits = 1, + Q_REQUIRED_RESULT QModelIndexList match(const QModelIndex &start, int role, const QVariant &value, int hits = 1, Qt::MatchFlags flags = Qt::MatchFlags(Qt::MatchStartsWith | Qt::MatchWrap)) const override; protected: diff --git a/src/core/models/entitytreemodel.h b/src/core/models/entitytreemodel.h --- a/src/core/models/entitytreemodel.h +++ b/src/core/models/entitytreemodel.h @@ -290,7 +290,7 @@ * - QVariant entityData( const Item &item, int column, int role = Qt::DisplayRole ) const; * -- Implement to return the data for a particular item and column. In the case of email for example, this would be the actual subject, sender and date of the email. * - * @note The entityData methods are just for convenience. the QAbstractItemMOdel::data method can be overridden if required. + * @note The entityData methods are just for convenience. the QAbstractItemModel::data method can be overridden if required. * * The application writer must then properly configure proxy models for the views, so that the correct data is shown in the correct view. * That is the purpose of these lines in the above example @@ -342,7 +342,7 @@ SessionRole, ///< @internal The Session used by this model CollectionRefRole, ///< @internal Used to increase the reference count on a Collection CollectionDerefRole, ///< @internal Used to decrease the reference count on a Collection - PendingCutRole, ///< @internal Used to indicate items which are to be cut + PendingCutRole, ///< Used to indicate items which are to be cut EntityUrlRole, ///< The akonadi:/ Url of the entity as a string. Item urls will contain the mimetype. UnreadCountRole, ///< Returns the number of unread items in a collection. @since 4.5 FetchStateRole, ///< Returns the FetchState of a particular item. @since 4.5 @@ -396,7 +396,7 @@ /** * Destroys the entity tree model. */ - virtual ~EntityTreeModel(); + ~EntityTreeModel() override; /** * Describes how the model should populated its items. @@ -419,14 +419,14 @@ /** * Returns @c true if internal system entities are shown, and @c false otherwise. */ - bool systemEntitiesShown() const; + Q_REQUIRED_RESULT bool systemEntitiesShown() const; /** * Returns the currently used listfilter. * * @since 4.14 */ - Akonadi::CollectionFetchScope::ListFilter listFilter() const; + Q_REQUIRED_RESULT Akonadi::CollectionFetchScope::ListFilter listFilter() const; /** * Sets the currently used listfilter. @@ -468,7 +468,7 @@ /** * Returns the item population strategy of the model. */ - ItemPopulationStrategy itemPopulationStrategy() const; + Q_REQUIRED_RESULT ItemPopulationStrategy itemPopulationStrategy() const; /** * Sets whether the root collection shall be provided by the model. @@ -480,7 +480,7 @@ /** * Returns whether the root collection is provided by the model. */ - bool includeRootCollection() const; + Q_REQUIRED_RESULT bool includeRootCollection() const; /** * Sets the display @p name of the root collection of the model. @@ -494,7 +494,7 @@ /** * Returns the display name of the root collection. */ - QString rootCollectionDisplayName() const; + Q_REQUIRED_RESULT QString rootCollectionDisplayName() const; /** * Describes what collections shall be fetched by and represent in the model. @@ -514,47 +514,47 @@ /** * Returns the collection fetch strategy of the model. */ - CollectionFetchStrategy collectionFetchStrategy() const; + Q_REQUIRED_RESULT CollectionFetchStrategy collectionFetchStrategy() const; - QHash roleNames() const override; + Q_REQUIRED_RESULT QHash roleNames() const override; - int columnCount(const QModelIndex &parent = QModelIndex()) const override; - int rowCount(const QModelIndex &parent = QModelIndex()) const override; + Q_REQUIRED_RESULT int columnCount(const QModelIndex &parent = QModelIndex()) const override; + Q_REQUIRED_RESULT int rowCount(const QModelIndex &parent = QModelIndex()) const override; - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + Q_REQUIRED_RESULT QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + Q_REQUIRED_RESULT QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; - Qt::ItemFlags flags(const QModelIndex &index) const override; - QStringList mimeTypes() const override; + Q_REQUIRED_RESULT Qt::ItemFlags flags(const QModelIndex &index) const override; + Q_REQUIRED_RESULT QStringList mimeTypes() const override; - Qt::DropActions supportedDropActions() const override; - QMimeData *mimeData(const QModelIndexList &indexes) const override; + Q_REQUIRED_RESULT Qt::DropActions supportedDropActions() const override; + Q_REQUIRED_RESULT QMimeData *mimeData(const QModelIndexList &indexes) const override; bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override; bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; - QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; - QModelIndex parent(const QModelIndex &index) const override; + Q_REQUIRED_RESULT QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; + Q_REQUIRED_RESULT QModelIndex parent(const QModelIndex &index) const override; // TODO: Review the implementations of these. I think they could be better. - bool canFetchMore(const QModelIndex &parent) const override; + Q_REQUIRED_RESULT bool canFetchMore(const QModelIndex &parent) const override; void fetchMore(const QModelIndex &parent) override; - bool hasChildren(const QModelIndex &parent = QModelIndex()) const override; + Q_REQUIRED_RESULT bool hasChildren(const QModelIndex &parent = QModelIndex()) const override; /** * Returns whether the collection tree has been fetched at initialisation. * * @see collectionTreeFetched * @since 4.10 */ - bool isCollectionTreeFetched() const; + Q_REQUIRED_RESULT bool isCollectionTreeFetched() const; /** * Returns whether the collection has been populated. * * @see collectionPopulated * @since 4.12 */ - bool isCollectionPopulated(Akonadi::Collection::Id) const; + Q_REQUIRED_RESULT bool isCollectionPopulated(Akonadi::Collection::Id) const; /** * Returns whether the model is fully populated. @@ -565,12 +565,12 @@ * @see isCollectionTreeFetched * @since 4.14 */ - bool isFullyPopulated() const; + Q_REQUIRED_RESULT bool isFullyPopulated() const; /** * Reimplemented to handle the AmazingCompletionRole. */ - QModelIndexList match(const QModelIndex &start, int role, const QVariant &value, int hits = 1, Qt::MatchFlags flags = Qt::MatchFlags(Qt::MatchStartsWith | Qt::MatchWrap)) const override; + Q_REQUIRED_RESULT QModelIndexList match(const QModelIndex &start, int role, const QVariant &value, int hits = 1, Qt::MatchFlags flags = Qt::MatchFlags(Qt::MatchStartsWith | Qt::MatchWrap)) const override; /** * Returns a QModelIndex in @p model which points to @p collection. diff --git a/src/core/models/entitytreemodel.cpp b/src/core/models/entitytreemodel.cpp --- a/src/core/models/entitytreemodel.cpp +++ b/src/core/models/entitytreemodel.cpp @@ -179,7 +179,7 @@ if (!item.remoteId().isEmpty()) { return item.remoteId(); } - return QString(QStringLiteral("<") + QString::number(item.id()) + QStringLiteral(">")); + return QString(QLatin1Char('<') + QString::number(item.id()) + QLatin1Char('>')); } break; case Qt::DecorationRole: @@ -315,6 +315,8 @@ case OriginalCollectionNameRole: { return entityData(collection, index.column(), Qt::DisplayRole); } + case PendingCutRole: + return d->m_pendingCutCollections.contains(node->id); case Qt::BackgroundRole: { if (collection.hasAttribute()) { EntityDisplayAttribute *eda = collection.attribute(); @@ -364,6 +366,8 @@ case EntityUrlRole: return item.url(Akonadi::Item::UrlWithMimeType).url(); break; + case PendingCutRole: + return d->m_pendingCutItems.contains(node->id); case Qt::BackgroundRole: { if (item.hasAttribute()) { EntityDisplayAttribute *eda = item.attribute(); @@ -397,11 +401,6 @@ const Node *node = reinterpret_cast(index.internalPointer()); if (Node::Collection == node->type) { - // cut out entities will be shown as inactive - if (d->m_pendingCutCollections.contains(node->id)) { - return Qt::ItemIsSelectable; - } - const Collection collection = d->m_collections.value(node->id); if (collection.isValid()) { @@ -430,6 +429,9 @@ } } else if (Node::Item == node->type) { + // cut out entities are shown as disabled + // TODO: Not sure this is wanted, it prevents any interaction with them, better + // solution would be to move this to the delegate, as was done for collections. if (d->m_pendingCutItems.contains(node->id)) { return Qt::ItemIsSelectable; } diff --git a/src/core/models/entitytreemodel_p.cpp b/src/core/models/entitytreemodel_p.cpp --- a/src/core/models/entitytreemodel_p.cpp +++ b/src/core/models/entitytreemodel_p.cpp @@ -181,7 +181,7 @@ void EntityTreeModelPrivate::agentInstanceRemoved(const Akonadi::AgentInstance &instance) { Q_Q(EntityTreeModel); - if (!instance.type().capabilities().contains(QStringLiteral("Resource"))) { + if (!instance.type().capabilities().contains(QLatin1String("Resource"))) { return; } @@ -1274,7 +1274,7 @@ QList &collectionEntities = m_childEntities[collectionId]; - int existingPosition = indexOf(collectionEntities, itemId); + const int existingPosition = indexOf(collectionEntities, itemId); if (existingPosition > 0) { qCWarning(AKONADICORE_LOG) << "Item with id " << itemId << " already in virtual collection with id " << collectionId; @@ -1528,7 +1528,8 @@ q, SLOT(topLevelCollectionsFetched(Akonadi::Collection::List))); q->connect(job, SIGNAL(result(KJob*)), q, SLOT(collectionFetchJobDone(KJob*))); - qCDebug(DebugETM) << ""; jobTimeTracker[job].start(); + qCDebug(DebugETM) << "EntityTreeModelPrivate::fetchTopLevelCollections"; + jobTimeTracker[job].start(); } void EntityTreeModelPrivate::topLevelCollectionsFetched(const Akonadi::Collection::List &list) diff --git a/src/core/models/favoritecollectionsmodel.h b/src/core/models/favoritecollectionsmodel.h --- a/src/core/models/favoritecollectionsmodel.h +++ b/src/core/models/favoritecollectionsmodel.h @@ -80,13 +80,13 @@ /** * Destroys the favorite collections model. */ - virtual ~FavoriteCollectionsModel(); + ~FavoriteCollectionsModel() override; /** * Returns the list of favorite collections. * @deprecated Use collectionIds instead. */ - AKONADICORE_DEPRECATED Collection::List collections() const; + Q_REQUIRED_RESULT AKONADICORE_DEPRECATED Collection::List collections() const; /** * Returns the list of ids of favorite collections set on the FavoriteCollectionsModel. @@ -110,19 +110,19 @@ * of collections which may not be in the model yet. If you want the ids of the collections * that are actually in the model, use a loop similar to above with the CollectionIdRole. */ - QList collectionIds() const; + Q_REQUIRED_RESULT QList collectionIds() const; /** * Return associate label for collection */ - QString favoriteLabel(const Akonadi::Collection &col); + Q_REQUIRED_RESULT QString favoriteLabel(const Akonadi::Collection &col); - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; - QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; - bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override; - QStringList mimeTypes() const override; - Qt::ItemFlags flags(const QModelIndex &index) const override; + Q_REQUIRED_RESULT QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + Q_REQUIRED_RESULT bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; + Q_REQUIRED_RESULT QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + Q_REQUIRED_RESULT bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override; + Q_REQUIRED_RESULT QStringList mimeTypes() const override; + Q_REQUIRED_RESULT Qt::ItemFlags flags(const QModelIndex &index) const override; public Q_SLOTS: /** @@ -155,8 +155,6 @@ class Private; Private *const d; - - Q_PRIVATE_SLOT(d, void reload()) //@endcond }; diff --git a/src/core/models/favoritecollectionsmodel.cpp b/src/core/models/favoritecollectionsmodel.cpp --- a/src/core/models/favoritecollectionsmodel.cpp +++ b/src/core/models/favoritecollectionsmodel.cpp @@ -32,6 +32,8 @@ #include "entitytreemodel.h" #include "mimetypechecker.h" #include "pastehelper_p.h" +#include "favoritecollectionattribute.h" +#include "collectionmodifyjob.h" using namespace Akonadi; @@ -78,6 +80,14 @@ if (!referencedCollections.contains(col)) { reference(col); } + auto idx = EntityTreeModel::modelIndexForCollection(q, Collection{ col }); + if (idx.isValid()) { + auto c = q->data(idx, EntityTreeModel::CollectionRole).value(); + if (c.isValid() && !c.hasAttribute()) { + c.addAttribute(new FavoriteCollectionAttribute()); + new CollectionModifyJob(c, q); + } + } } } @@ -189,14 +199,30 @@ collectionIds << collectionId; reference(collectionId); select(collectionId); + const auto idx = EntityTreeModel::modelIndexForCollection(q, Collection{ collectionId }); + if (idx.isValid()) { + auto col = q->data(idx, EntityTreeModel::CollectionRole).value(); + if (col.isValid() && !col.hasAttribute()) { + col.addAttribute(new FavoriteCollectionAttribute()); + new CollectionModifyJob(col, q); + } + } } void remove(const Collection::Id &collectionId) { collectionIds.removeAll(collectionId); labelMap.remove(collectionId); dereference(collectionId); deselect(collectionId); + const auto idx = EntityTreeModel::modelIndexForCollection(q, Collection{ collectionId }); + if (idx.isValid()) { + auto col = q->data(idx, EntityTreeModel::CollectionRole).value(); + if (col.isValid() && col.hasAttribute()) { + col.removeAttribute(); + new CollectionModifyJob(col, q); + } + } } void set(const QList &collections) @@ -277,8 +303,8 @@ d->loadConfig(); //React to various changes in the source model - connect(source, SIGNAL(modelReset()), this, SLOT(reload())); - connect(source, SIGNAL(layoutChanged()), this, SLOT(reload())); + connect(source, &QAbstractItemModel::modelReset, this, [this]() { d->reload(); }); + connect(source, &QAbstractItemModel::layoutChanged, this, [this]() { d->reload(); }); connect(source, &QAbstractItemModel::rowsInserted, this, [this](const QModelIndex &parent, int begin, int end) { d->rowsInserted(parent, begin, end); }); connect(source, &QAbstractItemModel::dataChanged, this, [this](const QModelIndex &tl, const QModelIndex &br) { d->dataChanged(tl, br); }); } @@ -440,7 +466,7 @@ QStringList FavoriteCollectionsModel::mimeTypes() const { QStringList mts = KSelectionProxyModel::mimeTypes(); - if (!mts.contains(QStringLiteral("text/uri-list"))) { + if (!mts.contains(QLatin1String("text/uri-list"))) { mts.append(QStringLiteral("text/uri-list")); } return mts; diff --git a/src/core/models/itemmodel.h b/src/core/models/itemmodel.h --- a/src/core/models/itemmodel.h +++ b/src/core/models/itemmodel.h @@ -88,23 +88,23 @@ /** * Destroys the item model. */ - virtual ~ItemModel(); + ~ItemModel() override; - int columnCount(const QModelIndex &parent = QModelIndex()) const override; + Q_REQUIRED_RESULT int columnCount(const QModelIndex &parent = QModelIndex()) const override; - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + Q_REQUIRED_RESULT QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - int rowCount(const QModelIndex &parent = QModelIndex()) const override; + Q_REQUIRED_RESULT int rowCount(const QModelIndex &parent = QModelIndex()) const override; - QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + Q_REQUIRED_RESULT QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; - Qt::ItemFlags flags(const QModelIndex &index) const override; + Q_REQUIRED_RESULT Qt::ItemFlags flags(const QModelIndex &index) const override; - QMimeData *mimeData(const QModelIndexList &indexes) const override; + Q_REQUIRED_RESULT QMimeData *mimeData(const QModelIndexList &indexes) const override; - QStringList mimeTypes() const override; + Q_REQUIRED_RESULT QStringList mimeTypes() const override; - Qt::DropActions supportedDropActions() const override; + Q_REQUIRED_RESULT Qt::DropActions supportedDropActions() const override; /** * Sets the item fetch scope. @@ -135,22 +135,22 @@ /** * Returns the item at the given @p index. */ - Item itemForIndex(const QModelIndex &index) const; + Q_REQUIRED_RESULT Item itemForIndex(const QModelIndex &index) const; /** * Returns the model index for the given item, with the given column. * * @param item The item to find. * @param column The column for the returned index. */ - QModelIndex indexForItem(const Akonadi::Item &item, const int column) const; + Q_REQUIRED_RESULT QModelIndex indexForItem(const Akonadi::Item &item, const int column) const; - bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override; + Q_REQUIRED_RESULT bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override; /** * Returns the collection being displayed in the model. */ - Collection collection() const; + Q_REQUIRED_RESULT Collection collection() const; public Q_SLOTS: /** diff --git a/src/core/models/quotacolorproxymodel.h b/src/core/models/quotacolorproxymodel.h --- a/src/core/models/quotacolorproxymodel.h +++ b/src/core/models/quotacolorproxymodel.h @@ -61,17 +61,15 @@ /** * Destroys the statistics tooltip proxy model. */ - virtual ~QuotaColorProxyModel(); + ~QuotaColorProxyModel() override; void setWarningThreshold(qreal threshold); - qreal warningThreshold() const; + Q_REQUIRED_RESULT qreal warningThreshold() const; void setWarningColor(const QColor &color); - QColor warningColor() const; + Q_REQUIRED_RESULT QColor warningColor() const; - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - - QHash roleNames() const override; + Q_REQUIRED_RESULT QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; private: //@cond PRIVATE class Private; diff --git a/src/core/models/quotacolorproxymodel.cpp b/src/core/models/quotacolorproxymodel.cpp --- a/src/core/models/quotacolorproxymodel.cpp +++ b/src/core/models/quotacolorproxymodel.cpp @@ -26,8 +26,6 @@ using namespace Akonadi; -static const int qmlForegroundRole = 1984; - /** * @internal */ @@ -78,7 +76,7 @@ QVariant QuotaColorProxyModel::data(const QModelIndex &index, int role) const { - if (role == Qt::ForegroundRole || role == qmlForegroundRole) { + if (role == Qt::ForegroundRole) { const QModelIndex sourceIndex = mapToSource(index); const QModelIndex rowIndex = sourceIndex.sibling(sourceIndex.row(), 0); const Akonadi::Collection collection = sourceModel()->data(rowIndex, Akonadi::EntityTreeModel::CollectionRole).value(); @@ -98,11 +96,3 @@ return QIdentityProxyModel::data(index, role); } - -QHash QuotaColorProxyModel::roleNames() const -{ - QHash names = QIdentityProxyModel::roleNames(); - names.insert(qmlForegroundRole, "foreground"); - return names; -} - diff --git a/src/core/models/recursivecollectionfilterproxymodel.h b/src/core/models/recursivecollectionfilterproxymodel.h --- a/src/core/models/recursivecollectionfilterproxymodel.h +++ b/src/core/models/recursivecollectionfilterproxymodel.h @@ -1,6 +1,6 @@ /* Copyright (c) 2009 Stephen Kelly - Copyright (C) 2012-2017 Laurent Montel + Copyright (C) 2012-2018 Laurent Montel 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 @@ -51,7 +51,7 @@ /** * Destroys the recursive collection filter proxy model. */ - virtual ~RecursiveCollectionFilterProxyModel(); + ~RecursiveCollectionFilterProxyModel() override; /** * Add content mime type to be shown by the filter. @@ -82,7 +82,7 @@ /** * Returns the currently included mimetypes in the filter. */ - QStringList contentMimeTypeInclusionFilters() const; + Q_REQUIRED_RESULT QStringList contentMimeTypeInclusionFilters() const; /** * Add search pattern diff --git a/src/core/models/recursivecollectionfilterproxymodel.cpp b/src/core/models/recursivecollectionfilterproxymodel.cpp --- a/src/core/models/recursivecollectionfilterproxymodel.cpp +++ b/src/core/models/recursivecollectionfilterproxymodel.cpp @@ -1,6 +1,6 @@ /* Copyright (c) 2009 Stephen Kelly - Copyright (C) 2012-2017 Laurent Montel + Copyright (C) 2012-2018 Laurent Montel 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 @@ -37,15 +37,14 @@ public: RecursiveCollectionFilterProxyModelPrivate(RecursiveCollectionFilterProxyModel *model) : q_ptr(model) - , checkOnlyChecked(false) { } QSet includedMimeTypes; Akonadi::MimeTypeChecker checker; QString pattern; - bool checkOnlyChecked; + bool checkOnlyChecked = false; }; } @@ -134,13 +133,17 @@ void Akonadi::RecursiveCollectionFilterProxyModel::setSearchPattern(const QString &pattern) { Q_D(RecursiveCollectionFilterProxyModel); - d->pattern = pattern; - invalidate(); + if (d->pattern != pattern) { + d->pattern = pattern; + invalidate(); + } } void Akonadi::RecursiveCollectionFilterProxyModel::setIncludeCheckedOnly(bool checked) { Q_D(RecursiveCollectionFilterProxyModel); - d->checkOnlyChecked = checked; - invalidate(); + if (d->checkOnlyChecked != checked) { + d->checkOnlyChecked = checked; + invalidate(); + } } diff --git a/src/core/models/statisticsproxymodel.h b/src/core/models/statisticsproxymodel.h --- a/src/core/models/statisticsproxymodel.h +++ b/src/core/models/statisticsproxymodel.h @@ -64,7 +64,7 @@ /** * Destroys the statistics proxy model. */ - virtual ~StatisticsProxyModel(); + ~StatisticsProxyModel() override; /** * @param enable Display tooltips @@ -75,7 +75,7 @@ /** * Return true if we display tooltips, otherwise false */ - bool isToolTipEnabled() const; + Q_REQUIRED_RESULT bool isToolTipEnabled() const; /** * @param enable Display extra statistics columns @@ -86,13 +86,13 @@ /** * Return true if we display extra statistics columns, otherwise false */ - bool isExtraColumnsEnabled() const; + Q_REQUIRED_RESULT bool isExtraColumnsEnabled() const; - QVariant extraColumnData(const QModelIndex &parent, int row, int extraColumn, int role) const override; - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - Qt::ItemFlags flags(const QModelIndex &index) const override; + Q_REQUIRED_RESULT QVariant extraColumnData(const QModelIndex &parent, int row, int extraColumn, int role) const override; + Q_REQUIRED_RESULT QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + Q_REQUIRED_RESULT Qt::ItemFlags flags(const QModelIndex &index) const override; - virtual QModelIndexList match(const QModelIndex &start, int role, const QVariant &value, int hits = 1, + Q_REQUIRED_RESULT virtual QModelIndexList match(const QModelIndex &start, int role, const QVariant &value, int hits = 1, Qt::MatchFlags flags = Qt::MatchFlags(Qt::MatchStartsWith | Qt::MatchWrap)) const override; void setSourceModel(QAbstractItemModel *model) override; diff --git a/src/core/models/statisticsproxymodel.cpp b/src/core/models/statisticsproxymodel.cpp --- a/src/core/models/statisticsproxymodel.cpp +++ b/src/core/models/statisticsproxymodel.cpp @@ -74,8 +74,8 @@ QString toolTipForCollection(const QModelIndex &index, const Collection &collection) { - QString bckColor = QApplication::palette().color(QPalette::ToolTipBase).name(); - QString txtColor = QApplication::palette().color(QPalette::ToolTipText).name(); + const QString bckColor = QApplication::palette().color(QPalette::ToolTipBase).name(); + const QString txtColor = QApplication::palette().color(QPalette::ToolTipText).name(); QString tip = QStringLiteral( "\n" @@ -96,8 +96,7 @@ " ") .arg(name, textToHTML(leftValue), textToHTML(rightValue))); + mTextContent.append(QStringLiteral("%1:\n%2\n%3\n\n").arg(name, leftValue, rightValue)); break; case AdditionalLeftMode: mContent.append(QStringLiteral("") .arg(name, textToHTML(leftValue))); + mTextContent.append(QStringLiteral("%1:\n%2\n\n").arg(name, leftValue)); break; case AdditionalRightMode: mContent.append(QStringLiteral("") .arg(name, textToHTML(rightValue))); + mTextContent.append(QStringLiteral("%1:\n%2\n\n").arg(name, rightValue)); break; } } @@ -121,6 +133,7 @@ QString mNameTitle; QString mLeftTitle; QString mRightTitle; + QString mTextContent; }; static void compareItems(AbstractDifferencesReporter *reporter, const Akonadi::Item &localItem, const Akonadi::Item &otherItem) @@ -190,35 +203,47 @@ : QDialog(parent), mResolveStrategy(ConflictHandler::UseBothItems) { setWindowTitle(i18nc("@title:window", "Conflict Resolution")); - QDialogButtonBox *buttonBox = new QDialogButtonBox(this); - QWidget *mainWidget = new QWidget(this); + QVBoxLayout *mainLayout = new QVBoxLayout(this); - mainLayout->addWidget(mainWidget); - QPushButton *user1Button = new QPushButton(this); - buttonBox->addButton(user1Button, QDialogButtonBox::ActionRole); - QPushButton *user2Button = new QPushButton(this); - buttonBox->addButton(user2Button, QDialogButtonBox::ActionRole); - QPushButton *user3Button = new QPushButton(this); - buttonBox->addButton(user3Button, QDialogButtonBox::ActionRole); - connect(buttonBox, &QDialogButtonBox::accepted, this, &ConflictResolveDialog::accept); - connect(buttonBox, &QDialogButtonBox::rejected, this, &ConflictResolveDialog::reject); - user3Button->setDefault(true); - - user3Button->setText(i18n("Take left one")); - user2Button->setText(i18n("Take right one")); - user1Button->setText(i18n("Keep both")); - - connect(user1Button, &QPushButton::clicked, this, &ConflictResolveDialog::slotUseBothItemsChoosen); - connect(user2Button, &QPushButton::clicked, this, &ConflictResolveDialog::slotUseOtherItemChoosen); - connect(user3Button, &QPushButton::clicked, this, &ConflictResolveDialog::slotUseLocalItemChoosen); - - QLabel *label = new QLabel(xi18nc("@label", "Two updates conflict with each other.Please choose which update(s) to apply."), mainWidget); - mainLayout->addWidget(label); + // Don't use QDialogButtonBox, order is very important (left on the left, right on the right) + QHBoxLayout *buttonLayout = new QHBoxLayout(); + QPushButton *takeLeftButton = new QPushButton(this); + takeLeftButton->setText(i18nc("@action:button", "Take my version")); + connect(takeLeftButton, &QPushButton::clicked, this, &ConflictResolveDialog::slotUseLocalItemChoosen); + buttonLayout->addWidget(takeLeftButton); + takeLeftButton->setObjectName(QStringLiteral("takeLeftButton")); + + QPushButton *takeRightButton = new QPushButton(this); + takeRightButton->setText(i18nc("@action:button", "Take their version")); + takeRightButton->setObjectName(QStringLiteral("takeRightButton")); + connect(takeRightButton, &QPushButton::clicked, this, &ConflictResolveDialog::slotUseOtherItemChoosen); + buttonLayout->addWidget(takeRightButton); + + QPushButton *keepBothButton = new QPushButton(this); + keepBothButton->setText(i18nc("@action:button", "Keep both versions")); + keepBothButton->setObjectName(QStringLiteral("keepBothButton")); + buttonLayout->addWidget(keepBothButton); + connect(keepBothButton, &QPushButton::clicked, this, &ConflictResolveDialog::slotUseBothItemsChoosen); + + keepBothButton->setDefault(true); + mView = new QTextBrowser(this); + mView->setObjectName(QStringLiteral("view")); + mView->setOpenLinks(false); + + QLabel *docuLabel = new QLabel(i18n("Your changes conflict with those made by someone else meanwhile.
" + "Unless one version can just be thrown away, you will have to integrate those changes manually.
" + "Click on \"Open text editor\" to keep a copy of the texts, then select which version is most correct, then re-open it and modify it again to add what's missing.")); + connect(docuLabel, &QLabel::linkActivated, this, &ConflictResolveDialog::slotOpenEditor); + docuLabel->setContextMenuPolicy(Qt::NoContextMenu); + + docuLabel->setWordWrap(true); + docuLabel->setObjectName(QStringLiteral("doculabel")); mainLayout->addWidget(mView); - mainLayout->addWidget(buttonBox); + mainLayout->addWidget(docuLabel); + mainLayout->addLayout(buttonLayout); // default size is tiny, and there's usually lots of text, so make it much bigger create(); // ensure a window is created @@ -250,6 +275,7 @@ if (algorithm) { algorithm->compare(&reporter, localItem, otherItem); mView->setHtml(reporter.toHtml()); + mTextContent = reporter.plainText(); return; } } @@ -260,6 +286,19 @@ } mView->setHtml(reporter.toHtml()); + mTextContent = reporter.plainText(); +} + +void ConflictResolveDialog::slotOpenEditor() +{ + QTemporaryFile file(QDir::tempPath() + QStringLiteral("/akonadi-XXXXXX.txt")); + if (file.open()) { + file.setAutoRemove(false); + file.write(mTextContent.toLocal8Bit()); + const QString fileName = file.fileName(); + file.close(); + QDesktopServices::openUrl(QUrl::fromLocalFile(fileName)); + } } ConflictHandler::ResolveStrategy ConflictResolveDialog::resolveStrategy() const diff --git a/src/widgets/conflictresolvedialog_p.h b/src/widgets/conflictresolvedialog_p.h --- a/src/widgets/conflictresolvedialog_p.h +++ b/src/widgets/conflictresolvedialog_p.h @@ -24,6 +24,7 @@ #include #include "conflicthandler_p.h" +#include "akonadiwidgetstests_export.h" class QTextBrowser; @@ -35,7 +36,7 @@ * * @author Tobias Koenig */ -class ConflictResolveDialog : public QDialog +class AKONADIWIDGET_TESTS_EXPORT ConflictResolveDialog : public QDialog { Q_OBJECT @@ -62,20 +63,22 @@ /** * Returns the resolve strategy the user choose. */ - ConflictHandler::ResolveStrategy resolveStrategy() const; + Q_REQUIRED_RESULT ConflictHandler::ResolveStrategy resolveStrategy() const; private Q_SLOTS: void slotUseLocalItemChoosen(); void slotUseOtherItemChoosen(); void slotUseBothItemsChoosen(); + void slotOpenEditor(); private: ConflictHandler::ResolveStrategy mResolveStrategy; Akonadi::Item mLocalItem; Akonadi::Item mOtherItem; QTextBrowser *mView = nullptr; + QString mTextContent; }; } diff --git a/src/widgets/controlgui.h b/src/widgets/controlgui.h --- a/src/widgets/controlgui.h +++ b/src/widgets/controlgui.h @@ -136,7 +136,6 @@ Private *const d; Q_PRIVATE_SLOT(d, void createErrorOverlays()) - Q_PRIVATE_SLOT(d, void cleanup()) //@endcond }; diff --git a/src/widgets/controlgui.cpp b/src/widgets/controlgui.cpp --- a/src/widgets/controlgui.cpp +++ b/src/widgets/controlgui.cpp @@ -189,7 +189,7 @@ // mProgressIndicator is a widget, so it better be deleted before the QApplication is deleted // Otherwise we get a crash in QCursor code with Qt-4.5 if (QCoreApplication::instance()) { - connect(QCoreApplication::instance(), SIGNAL(aboutToQuit()), this, SLOT(cleanup())); + connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, [this]() {d->cleanup();}); } } diff --git a/src/widgets/entitylistview.h b/src/widgets/entitylistview.h --- a/src/widgets/entitylistview.h +++ b/src/widgets/entitylistview.h @@ -97,7 +97,7 @@ /** * Destroys the favorite collections view. */ - virtual ~EntityListView(); + ~EntityListView() override; /** * Sets the XML GUI client which the view is used in. @@ -134,7 +134,7 @@ * * @since 4.7 */ - bool isDropActionMenuEnabled() const; + Q_REQUIRED_RESULT bool isDropActionMenuEnabled() const; Q_SIGNALS: /** diff --git a/src/widgets/entitylistview.cpp b/src/widgets/entitylistview.cpp --- a/src/widgets/entitylistview.cpp +++ b/src/widgets/entitylistview.cpp @@ -22,7 +22,6 @@ #include "entitylistview.h" #include "dragdropmanager_p.h" -#include "favoritecollectionsmodel.h" #include #include @@ -50,7 +49,6 @@ #ifndef QT_NO_DRAGANDDROP , mDragDropManager(new DragDropManager(mParent)) #endif - , mXmlGuiClient(nullptr) { } @@ -172,8 +170,7 @@ #ifndef QT_NO_DRAGANDDROP void EntityListView::dragMoveEvent(QDragMoveEvent *event) { - if (d->mDragDropManager->dropAllowed(event) || - qobject_cast(model())) { + if (d->mDragDropManager->dropAllowed(event)) { // All urls are supported. process the event. QListView::dragMoveEvent(event); return; @@ -188,9 +185,6 @@ if (d->mDragDropManager->processDropEvent(event, menuCanceled) && !menuCanceled) { QListView::dropEvent(event); - } else if (qobject_cast(model()) && - !menuCanceled) { - QListView::dropEvent(event); } } #endif diff --git a/src/widgets/entitytreeview.h b/src/widgets/entitytreeview.h --- a/src/widgets/entitytreeview.h +++ b/src/widgets/entitytreeview.h @@ -1,7 +1,7 @@ /* Copyright (c) 2006 - 2007 Volker Krause Copyright (c) 2008 Stephen Kelly - Copyright (C) 2012-2017 Laurent Montel + Copyright (C) 2012-2018 Laurent Montel 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 @@ -93,7 +93,7 @@ /** * Destroys the entity tree view. */ - virtual ~EntityTreeView(); + ~EntityTreeView() override; /** * Sets the XML GUI client which the view is used in. @@ -130,16 +130,16 @@ * * @since 4.5 */ - bool isDropActionMenuEnabled() const; + Q_REQUIRED_RESULT bool isDropActionMenuEnabled() const; /** * Return true if we use an manual sorting * Necessary to fix dnd menu * We must show just move when we move item between two items * When automatic no show dnd menu between two items. * @since 4.8.1 */ - bool isManualSortingActive() const; + Q_REQUIRED_RESULT bool isManualSortingActive() const; /** * Set true if we automatic sorting diff --git a/src/widgets/entitytreeview.cpp b/src/widgets/entitytreeview.cpp --- a/src/widgets/entitytreeview.cpp +++ b/src/widgets/entitytreeview.cpp @@ -1,7 +1,7 @@ /* Copyright (c) 2006 - 2007 Volker Krause Copyright (c) 2008 Stephen Kelly - Copyright (C) 2012-2017 Laurent Montel + Copyright (C) 2012-2018 Laurent Montel 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 @@ -52,7 +52,6 @@ #ifndef QT_NO_DRAGANDDROP , mDragDropManager(new DragDropManager(mParent)) #endif - , mXmlGuiClient(nullptr) , mDefaultPopupMenu(QStringLiteral("akonadi_collectionview_contextmenu")) { } diff --git a/src/widgets/erroroverlay_p.h b/src/widgets/erroroverlay_p.h --- a/src/widgets/erroroverlay_p.h +++ b/src/widgets/erroroverlay_p.h @@ -50,7 +50,7 @@ * @p parent must not be equal to @p baseWidget */ explicit ErrorOverlay(QWidget *baseWidget, QWidget *parent = nullptr); - ~ErrorOverlay(); + ~ErrorOverlay() override; protected: bool eventFilter(QObject *object, QEvent *event) override; diff --git a/src/widgets/itemview.h b/src/widgets/itemview.h --- a/src/widgets/itemview.h +++ b/src/widgets/itemview.h @@ -84,7 +84,7 @@ /** * Destroys the item view. */ - virtual ~ItemView(); + ~ItemView() override; /** * Sets the KXMLGUIFactory which this view is used in. diff --git a/src/widgets/itemview.cpp b/src/widgets/itemview.cpp --- a/src/widgets/itemview.cpp +++ b/src/widgets/itemview.cpp @@ -37,8 +37,7 @@ { public: Private(ItemView *parent) - : xmlGuiClient(nullptr) - , mParent(parent) + : mParent(parent) { } @@ -65,7 +64,7 @@ mParent->connect(mParent, SIGNAL(activated(QModelIndex)), mParent, SLOT(itemActivated(QModelIndex))); mParent->connect(mParent, SIGNAL(clicked(QModelIndex)), mParent, SLOT(itemClicked(QModelIndex))); - mParent->connect(mParent, SIGNAL(doubleClicked(QModelIndex)), mParent, SLOT(itemDoubleClicked(QModelIndex))); + mParent->connect(mParent, QOverload::of(&QAbstractItemView::doubleClicked), mParent, [this](const QModelIndex &index) { itemDoubleClicked(index); }); ControlGui::widgetNeedsAkonadi(mParent); } diff --git a/src/widgets/manageaccountwidget.h b/src/widgets/manageaccountwidget.h --- a/src/widgets/manageaccountwidget.h +++ b/src/widgets/manageaccountwidget.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2014-2017 Montel Laurent + Copyright (c) 2014-2018 Montel Laurent 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 @@ -41,7 +41,7 @@ Q_OBJECT public: explicit ManageAccountWidget(QWidget *parent); - ~ManageAccountWidget(); + ~ManageAccountWidget() override; /** * Sets the text of the label above the list of accounts. @@ -51,20 +51,20 @@ void setSpecialCollectionIdentifier(const QString &identifier); - QStringList mimeTypeFilter() const; + Q_REQUIRED_RESULT QStringList mimeTypeFilter() const; void setMimeTypeFilter(const QStringList &mimeTypeFilter); - QStringList capabilityFilter() const; + Q_REQUIRED_RESULT QStringList capabilityFilter() const; void setCapabilityFilter(const QStringList &capabilityFilter); - QStringList excludeCapabilities() const; + Q_REQUIRED_RESULT QStringList excludeCapabilities() const; void setExcludeCapabilities(const QStringList &excludeCapabilities); void setItemDelegate(QAbstractItemDelegate *delegate); - QAbstractItemView *view() const; + Q_REQUIRED_RESULT QAbstractItemView *view() const; - QPushButton *addAccountButton() const; + Q_REQUIRED_RESULT QPushButton *addAccountButton() const; void disconnectAddAccountButton(); protected: bool eventFilter(QObject *obj, QEvent *event) override; diff --git a/src/widgets/manageaccountwidget.cpp b/src/widgets/manageaccountwidget.cpp --- a/src/widgets/manageaccountwidget.cpp +++ b/src/widgets/manageaccountwidget.cpp @@ -1,5 +1,5 @@ /* - Copyright (c) 2014-2017 Montel Laurent + Copyright (c) 2014-2018 Montel Laurent 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 @@ -32,6 +32,7 @@ #include #include #include +#include using namespace Akonadi; @@ -227,7 +228,7 @@ void ManageAccountWidget::slotAccountSelected(const Akonadi::AgentInstance ¤t) { if (current.isValid()) { - d->ui->mModifyAccountButton->setEnabled(!current.type().capabilities().contains(QStringLiteral("NoConfig"))); + d->ui->mModifyAccountButton->setEnabled(!current.type().capabilities().contains(QLatin1String("NoConfig"))); d->ui->mRemoveAccountButton->setEnabled(d->mSpecialCollectionIdentifier != current.identifier()); // Restarting an agent is not possible if it's in Running status... (see AgentProcessInstance::restartWhenIdle) d->ui->mRestartAccountButton->setEnabled((current.status() != 1)); diff --git a/src/widgets/progressspinnerdelegate.cpp b/src/widgets/progressspinnerdelegate.cpp --- a/src/widgets/progressspinnerdelegate.cpp +++ b/src/widgets/progressspinnerdelegate.cpp @@ -114,8 +114,8 @@ m_animator->push(index); - if (QStyleOptionViewItem *v4 = qstyleoption_cast(option)) { - v4->icon = m_animator->sequenceFrame(index); + if (QStyleOptionViewItem *v = qstyleoption_cast(option)) { + v->icon = m_animator->sequenceFrame(index); } } diff --git a/src/widgets/recentcollectionaction.cpp b/src/widgets/recentcollectionaction.cpp --- a/src/widgets/recentcollectionaction.cpp +++ b/src/widgets/recentcollectionaction.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2017 Laurent Montel + * Copyright (C) 2011-2018 Laurent Montel * * 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 @@ -100,7 +100,7 @@ index = index.parent(); } if (topLevelName.isEmpty()) { - return QStringLiteral("%1").arg(name); + return name; } else { topLevelName.replace(QLatin1Char('&'), QStringLiteral("&&")); return QStringLiteral("%1 - %2").arg(name, topLevelName); diff --git a/src/widgets/recentcollectionaction_p.h b/src/widgets/recentcollectionaction_p.h --- a/src/widgets/recentcollectionaction_p.h +++ b/src/widgets/recentcollectionaction_p.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2017 Laurent Montel + * Copyright (C) 2011-2018 Laurent Montel * * 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 diff --git a/src/widgets/renamefavoritedialog.h b/src/widgets/renamefavoritedialog.h --- a/src/widgets/renamefavoritedialog.h +++ b/src/widgets/renamefavoritedialog.h @@ -1,5 +1,5 @@ /* - Copyright (C) 2011-2017 Laurent Montel + Copyright (C) 2011-2018 Laurent Montel 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 @@ -34,7 +34,7 @@ explicit RenameFavoriteDialog(const QString &caption, const QString &text, const QString &value, const QString &defaultName, QWidget *parent); ~RenameFavoriteDialog(); - QString newName() const; + Q_REQUIRED_RESULT QString newName() const; protected: diff --git a/src/widgets/renamefavoritedialog.cpp b/src/widgets/renamefavoritedialog.cpp --- a/src/widgets/renamefavoritedialog.cpp +++ b/src/widgets/renamefavoritedialog.cpp @@ -1,5 +1,5 @@ /* - Copyright (C) 2011-2017 Laurent Montel + Copyright (C) 2011-2018 Laurent Montel 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 diff --git a/src/widgets/selftestdialog.cpp b/src/widgets/selftestdialog.cpp --- a/src/widgets/selftestdialog.cpp +++ b/src/widgets/selftestdialog.cpp @@ -76,9 +76,9 @@ QWidget *mainWidget = new QWidget(this); QVBoxLayout *mainLayout = new QVBoxLayout(this); mainLayout->addWidget(mainWidget); - QPushButton *user1Button = new QPushButton; + QPushButton *user1Button = new QPushButton(this); buttonBox->addButton(user1Button, QDialogButtonBox::ActionRole); - QPushButton *user2Button = new QPushButton; + QPushButton *user2Button = new QPushButton(this); buttonBox->addButton(user2Button, QDialogButtonBox::ActionRole); connect(buttonBox, &QDialogButtonBox::rejected, this, &SelfTestDialog::reject); mainLayout->addWidget(buttonBox); @@ -120,8 +120,8 @@ item->setIcon(QIcon::fromTheme(QStringLiteral("dialog-warning"))); break; case Error: - default: item->setIcon(QIcon::fromTheme(QStringLiteral("dialog-error"))); + break; } item->setEditable(false); item->setWhatsThis(details.toString()); @@ -169,7 +169,7 @@ QVariant SelfTestDialog::serverSetting(const QString &group, const char *key, const QVariant &def) const { - const QString serverConfigFile = StandardDirs::serverConfigFile(XdgBaseDirs::ReadOnly); + const QString serverConfigFile = StandardDirs::serverConfigFile(StandardDirs::ReadOnly); QSettings settings(serverConfigFile, QSettings::IniFormat); settings.beginGroup(group); return settings.value(QString::fromLatin1(key), def); @@ -216,7 +216,7 @@ } else { item = report(Error, ki18n("Database driver not found."), detailsFail); } - item->setData(StandardDirs::serverConfigFile(XdgBaseDirs::ReadOnly), FileIncludeRole); + item->setData(StandardDirs::serverConfigFile(StandardDirs::ReadOnly), FileIncludeRole); } void SelfTestDialog::testMySQLServer() @@ -242,7 +242,7 @@ report(Error, ki18n("MySQL server not readable."), details); } else if (!info.isExecutable()) { report(Error, ki18n("MySQL server not executable."), details); - } else if (!serverPath.contains(QStringLiteral("mysqld"))) { + } else if (!serverPath.contains(QLatin1String("mysqld"))) { report(Warning, ki18n("MySQL found with unexpected name."), details); } else { report(Success, ki18n("MySQL server found."), details); @@ -286,13 +286,13 @@ QStandardItem *item = nullptr; while (!logFile.atEnd()) { const QString line = QString::fromUtf8(logFile.readLine()); - if (line.contains(QStringLiteral("error"), Qt::CaseInsensitive)) { + if (line.contains(QLatin1String("error"), Qt::CaseInsensitive)) { item = report(Error, ki18n("MySQL server log contains errors."), ki18n("The MySQL server error log file '%1' contains errors.").subs(makeLink(logFileName))); item->setData(logFileName, FileIncludeRole); return; } - if (!warningsFound && line.contains(QStringLiteral("warn"), Qt::CaseInsensitive)) { + if (!warningsFound && line.contains(QLatin1String("warn"), Qt::CaseInsensitive)) { warningsFound = true; } } @@ -318,7 +318,7 @@ } QStandardItem *item = nullptr; - const QString globalConfig = XdgBaseDirs::findResourceFile("config", QStringLiteral("akonadi/mysql-global.conf")); + const QString globalConfig = StandardDirs::locateResourceFile("config", QStringLiteral("akonadi/mysql-global.conf")); const QFileInfo globalConfigInfo(globalConfig); if (!globalConfig.isEmpty() && globalConfigInfo.exists() && globalConfigInfo.isReadable()) { item = report(Success, ki18n("MySQL server default configuration found."), @@ -331,7 +331,7 @@ "Check your Akonadi installation is complete and you have all required access rights.")); } - const QString localConfig = XdgBaseDirs::findResourceFile("config", QStringLiteral("akonadi/mysql-local.conf")); + const QString localConfig = StandardDirs::locateResourceFile("config", QStringLiteral("akonadi/mysql-local.conf")); const QFileInfo localConfigInfo(localConfig); if (localConfig.isEmpty() || !localConfigInfo.exists()) { report(Skip, ki18n("MySQL server custom configuration not available."), @@ -393,7 +393,7 @@ void SelfTestDialog::testAkonadiCtl() { - const QString path = QStandardPaths::findExecutable(QStringLiteral("akonadictl")); + const QString path = Akonadi::StandardDirs::findExecutable(QStringLiteral("akonadictl")); if (path.isEmpty()) { report(Error, ki18n("akonadictl not found"), ki18n("The program 'akonadictl' needs to be accessible in $PATH. " @@ -465,13 +465,13 @@ const AgentType::List agentTypes = AgentManager::self()->types(); bool resourceFound = false; for (const AgentType &type : agentTypes) { - if (type.capabilities().contains(QStringLiteral("Resource"))) { + if (type.capabilities().contains(QLatin1String("Resource"))) { resourceFound = true; break; } } - const QStringList pathList = XdgBaseDirs::findAllResourceDirs("data", QStringLiteral("akonadi/agents")); + const auto pathList = StandardDirs::locateAllResourceDirs(QStringLiteral("akonadi/agents")); QStandardItem *item = nullptr; if (resourceFound) { item = report(Success, ki18n("Resource agents found."), ki18n("At least one resource agent has been found.")); diff --git a/src/widgets/standardactionmanager.cpp b/src/widgets/standardactionmanager.cpp --- a/src/widgets/standardactionmanager.cpp +++ b/src/widgets/standardactionmanager.cpp @@ -290,7 +290,8 @@ qRegisterMetaType("Akonadi::Item::List"); } - void enableAction(int type, bool enable) + + void enableAction(int type, bool enable) // private slot, called by ActionStateManager { enableAction(static_cast(type), enable); } @@ -366,7 +367,7 @@ } } - void updateAlternatingAction(int type) + void updateAlternatingAction(int type) // private slot, called by ActionStateManager { updateAlternatingAction(static_cast(type)); } @@ -415,20 +416,20 @@ } } - void updatePluralLabel(int type, int count) + void updatePluralLabel(int type, int count) // private slot, called by ActionStateManager { updatePluralLabel(static_cast(type), count); } - void updatePluralLabel(StandardActionManager::Type type, int count) + void updatePluralLabel(StandardActionManager::Type type, int count) // private slot, called by ActionStateManager { Q_ASSERT(type < StandardActionManager::LastType); if (actions[type] && pluralLabels.contains(type) && !pluralLabels.value(type).isEmpty()) { actions[type]->setText(pluralLabels.value(type).subs(qMax(count, 1)).toString()); } } - bool isFavoriteCollection(const Akonadi::Collection &collection) + bool isFavoriteCollection(const Akonadi::Collection &collection) // private slot, called by ActionStateManager { if (!favoritesModel) { return false; @@ -456,23 +457,38 @@ } #endif } + + static Akonadi::Collection::List collectionsForIndexes(const QModelIndexList& list) + { + Akonadi::Collection::List collectionList; + for (const QModelIndex &index : list) { + Collection collection = index.data(EntityTreeModel::CollectionRole).value(); + if (!collection.isValid()) { + continue; + } + + const Collection parentCollection = index.data(EntityTreeModel::ParentCollectionRole).value(); + collection.setParentCollection(parentCollection); + + collectionList << std::move(collection); + } + return collectionList; + } + void updateActions() { + // favorite collections + Collection::List selectedFavoriteCollectionsList; + if (favoriteSelectionModel) { + const QModelIndexList rows = safeSelectedRows(favoriteSelectionModel); + selectedFavoriteCollectionsList = collectionsForIndexes(rows); + } + // collect all selected collections Collection::List selectedCollectionsList; if (collectionSelectionModel) { const QModelIndexList rows = safeSelectedRows(collectionSelectionModel); - for (const QModelIndex &index : rows) { - Collection collection = index.data(EntityTreeModel::CollectionRole).value(); - if (!collection.isValid()) { - continue; - } - - const Collection parentCollection = index.data(EntityTreeModel::ParentCollectionRole).value(); - collection.setParentCollection(parentCollection); - - selectedCollectionsList << collection; - } + selectedCollectionsList = collectionsForIndexes(rows); } // collect all selected items @@ -492,7 +508,7 @@ } } - mActionStateManager.updateState(selectedCollectionsList, selectedItems); + mActionStateManager.updateState(selectedCollectionsList, selectedFavoriteCollectionsList, selectedItems); if (favoritesModel) { enableAction(StandardActionManager::SynchronizeFavoriteCollections, (favoritesModel->rowCount() > 0)); } @@ -584,7 +600,9 @@ collectionSelectionModel->select(selection, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); // Also set the current index. This will trigger KMMainWidget::slotFolderChanged in kmail, which we want. - collectionSelectionModel->setCurrentIndex(selection.indexes().first(), QItemSelectionModel::NoUpdate); + if (!selection.indexes().isEmpty()) { + collectionSelectionModel->setCurrentIndex(selection.indexes().first(), QItemSelectionModel::NoUpdate); + } updateActions(); } @@ -833,7 +851,7 @@ bool testAndSetOnlineResources(const Akonadi::Collection &collection) { // Shortcut for the Search resource, which is a virtual resource and thus - // is awlays online (but AgentManager does not know about it, so it returns + // is always online (but AgentManager does not know about it, so it returns // an invalid AgentInstance, which is "offline"). // // FIXME: AgentManager should return a valid AgentInstance even @@ -944,9 +962,13 @@ return; } +#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) + QMetaObject::invokeMethod(q, [this, items] {slotDeleteItemsDeferred(items); }, Qt::QueuedConnection); +#else QMetaObject::invokeMethod(q, "slotDeleteItemsDeferred", Qt::QueuedConnection, Q_ARG(Akonadi::Item::List, items)); +#endif } void slotDeleteItemsDeferred(const Akonadi::Item::List &items) @@ -994,9 +1016,9 @@ void slotRemoveFromFavorites() { - Q_ASSERT(collectionSelectionModel); + Q_ASSERT(favoriteSelectionModel); Q_ASSERT(favoritesModel); - const QModelIndexList list = safeSelectedRows(collectionSelectionModel); + const QModelIndexList list = safeSelectedRows(favoriteSelectionModel); if (list.isEmpty()) { return; } @@ -1014,9 +1036,9 @@ void slotRenameFavorite() { - Q_ASSERT(collectionSelectionModel); + Q_ASSERT(favoriteSelectionModel); Q_ASSERT(favoritesModel); - const QModelIndexList list = safeSelectedRows(collectionSelectionModel); + const QModelIndexList list = safeSelectedRows(favoriteSelectionModel); if (list.isEmpty()) { return; } @@ -1026,7 +1048,7 @@ Q_ASSERT(collection.isValid()); QPointer dlg(new RenameFavoriteDialog(contextText(StandardActionManager::RenameFavoriteCollection, StandardActionManager::DialogTitle), contextText(StandardActionManager::RenameFavoriteCollection, StandardActionManager::DialogText), favoritesModel->favoriteLabel(collection), collection.displayName(), parentWidget)); - if (dlg->exec() == QDialog::Accepted && dlg != nullptr) { + if (dlg->exec() == QDialog::Accepted) { favoritesModel->setFavoriteLabel(collection, dlg->newName()); } delete dlg; @@ -1131,7 +1153,7 @@ dlg->agentFilterProxyModel()->addCapabilityFilter(capability); } - if (dlg->exec() == QDialog::Accepted && dlg != nullptr) { + if (dlg->exec() == QDialog::Accepted) { const AgentType agentType = dlg->agentType(); if (agentType.isValid()) { diff --git a/src/widgets/tageditwidget.h b/src/widgets/tageditwidget.h --- a/src/widgets/tageditwidget.h +++ b/src/widgets/tageditwidget.h @@ -40,10 +40,10 @@ Q_OBJECT public: explicit TagEditWidget(Akonadi::TagModel *model, QWidget *parent = nullptr, bool enableSelection = false); - virtual ~TagEditWidget(); + ~TagEditWidget() override; void setSelection(const Akonadi::Tag::List &tags); - Akonadi::Tag::List selection() const; + Q_REQUIRED_RESULT Akonadi::Tag::List selection() const; protected: bool eventFilter(QObject *watched, QEvent *event) override; diff --git a/src/widgets/tageditwidget.cpp b/src/widgets/tageditwidget.cpp --- a/src/widgets/tageditwidget.cpp +++ b/src/widgets/tageditwidget.cpp @@ -22,7 +22,7 @@ #include #include -#include +#include #include #include #include "changerecorder.h" @@ -80,12 +80,6 @@ : QObject() , d(parent) , m_model(model) - , m_tagsView(nullptr) - , m_checkableProxy(nullptr) - , m_newTagButton(nullptr) - , m_newTagEdit(nullptr) - , m_deleteButton(nullptr) - , m_deleteButtonTimer(nullptr) { } @@ -182,7 +176,7 @@ "Do you really want to remove the tag %1?", tag.name()); const QString caption = i18nc("@title", "Delete tag"); - if (QMessageBox::question(d, caption, text, i18nc("@action:button", "Delete"), i18nc("@action:button", "Cancel")) == 0) { + if (KMessageBox::questionYesNo(d, text, caption, KStandardGuiItem::del(), KStandardGuiItem::cancel()) == KMessageBox::Yes) { new Akonadi::TagDeleteJob(tag, this); } } diff --git a/src/widgets/tagmanagementdialog.h b/src/widgets/tagmanagementdialog.h --- a/src/widgets/tagmanagementdialog.h +++ b/src/widgets/tagmanagementdialog.h @@ -40,9 +40,9 @@ Q_OBJECT public: explicit TagManagementDialog(QWidget *parent = nullptr); - virtual ~TagManagementDialog(); + ~TagManagementDialog() override; - QDialogButtonBox *buttons() const; + Q_REQUIRED_RESULT QDialogButtonBox *buttons() const; private: struct Private; diff --git a/src/widgets/tagmanagementdialog.cpp b/src/widgets/tagmanagementdialog.cpp --- a/src/widgets/tagmanagementdialog.cpp +++ b/src/widgets/tagmanagementdialog.cpp @@ -35,8 +35,7 @@ struct Q_DECL_HIDDEN TagManagementDialog::Private { Private(QDialog *parent) - : d(parent), - buttonBox(nullptr) + : d(parent) { } void writeConfig(); diff --git a/src/widgets/tagselectiondialog.h b/src/widgets/tagselectiondialog.h --- a/src/widgets/tagselectiondialog.h +++ b/src/widgets/tagselectiondialog.h @@ -43,12 +43,12 @@ Q_OBJECT public: explicit TagSelectionDialog(QWidget *parent = nullptr); - virtual ~TagSelectionDialog(); + ~TagSelectionDialog() override; void setSelection(const Akonadi::Tag::List &tags); - Akonadi::Tag::List selection() const; + Q_REQUIRED_RESULT Akonadi::Tag::List selection() const; - QDialogButtonBox *buttons() const; + Q_REQUIRED_RESULT QDialogButtonBox *buttons() const; Q_SIGNALS: void selectionChanged(const Akonadi::Tag::List &tags); diff --git a/src/widgets/tagselectiondialog.cpp b/src/widgets/tagselectiondialog.cpp --- a/src/widgets/tagselectiondialog.cpp +++ b/src/widgets/tagselectiondialog.cpp @@ -37,8 +37,6 @@ public: Private(QDialog *parent) : d(parent) - , mTagWidget(nullptr) - , mButtonBox(nullptr) { } void writeConfig(); diff --git a/src/widgets/tagselectwidget.h b/src/widgets/tagselectwidget.h --- a/src/widgets/tagselectwidget.h +++ b/src/widgets/tagselectwidget.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2015-2017 Montel Laurent + Copyright (c) 2015-2018 Montel Laurent 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 @@ -40,13 +40,13 @@ ~TagSelectWidget(); void setSelection(const Akonadi::Tag::List &tags); - Akonadi::Tag::List selection() const; + Q_REQUIRED_RESULT Akonadi::Tag::List selection() const; /** * @brief tagToStringList * @return QStringList from selected tag (List of Url) */ - QStringList tagToStringList() const; + Q_REQUIRED_RESULT QStringList tagToStringList() const; /** * @brief setSelectionFromStringList, convert a QStringList to Tag (converted from url) */ diff --git a/src/widgets/tagselectwidget.cpp b/src/widgets/tagselectwidget.cpp --- a/src/widgets/tagselectwidget.cpp +++ b/src/widgets/tagselectwidget.cpp @@ -1,5 +1,5 @@ /* - Copyright (c) 2015-2017 Montel Laurent + Copyright (c) 2015-2018 Montel Laurent 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 @@ -41,8 +41,7 @@ void TagSelectWidget::Private::init() { - QHBoxLayout *mainLayout = new QHBoxLayout; - mParent->setLayout(mainLayout); + QHBoxLayout *mainLayout = new QHBoxLayout(mParent); Monitor *monitor = new Monitor(mParent); monitor->setObjectName(QStringLiteral("TagSelectWidgetMonitor")); diff --git a/src/widgets/tagwidget.h b/src/widgets/tagwidget.h --- a/src/widgets/tagwidget.h +++ b/src/widgets/tagwidget.h @@ -57,7 +57,7 @@ ~TagWidget(); void setSelection(const Akonadi::Tag::List &tags); - Akonadi::Tag::List selection() const; + Q_REQUIRED_RESULT Akonadi::Tag::List selection() const; void clearTags(); void setReadOnly(bool readOnly); diff --git a/src/widgets/tagwidget.cpp b/src/widgets/tagwidget.cpp --- a/src/widgets/tagwidget.cpp +++ b/src/widgets/tagwidget.cpp @@ -3,7 +3,7 @@ Copyright (c) 2010 Tobias Koenig Copyright (c) 2014 Christian Mollekopf - Copyright (C) 2016-2017 Laurent Montel + Copyright (C) 2016-2018 Laurent Montel 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 diff --git a/src/xml/autotests/CMakeLists.txt b/src/xml/autotests/CMakeLists.txt --- a/src/xml/autotests/CMakeLists.txt +++ b/src/xml/autotests/CMakeLists.txt @@ -1,5 +1,5 @@ -set(QT_REQUIRED_VERSION "5.8.0") +set(QT_REQUIRED_VERSION "5.9.0") find_package(Qt5 ${QT_REQUIRED_VERSION} CONFIG REQUIRED COMPONENTS Test) include(ECMMarkAsTest) diff --git a/src/xml/xmldocument.h b/src/xml/xmldocument.h --- a/src/xml/xmldocument.h +++ b/src/xml/xmldocument.h @@ -69,14 +69,14 @@ Returns true if the document could be parsed successfully. @see lastError() */ - bool isValid() const; + Q_REQUIRED_RESULT bool isValid() const; /** Returns the last error occurred during file loading/parsing. Empty if isValid() returns @c true. @see isValid() */ - QString lastError() const; + Q_REQUIRED_RESULT QString lastError() const; /** Returns the DOM document for this XML document. @@ -86,47 +86,47 @@ /** Returns the DOM element representing @p collection. */ - QDomElement collectionElement(const Collection &collection) const; + Q_REQUIRED_RESULT QDomElement collectionElement(const Collection &collection) const; /** Returns the DOM element representing the item with the given remote id */ - QDomElement itemElementByRemoteId(const QString &rid) const; + Q_REQUIRED_RESULT QDomElement itemElementByRemoteId(const QString &rid) const; /** * Returns the DOM element representing the collection with the given remote id */ - QDomElement collectionElementByRemoteId(const QString &rid) const; + Q_REQUIRED_RESULT QDomElement collectionElementByRemoteId(const QString &rid) const; /** Returns the collection with the given remote id. */ - Collection collectionByRemoteId(const QString &rid) const; + Q_REQUIRED_RESULT Collection collectionByRemoteId(const QString &rid) const; /** Returns the item with the given remote id. */ - Item itemByRemoteId(const QString &rid, bool includePayload = true) const; + Q_REQUIRED_RESULT Item itemByRemoteId(const QString &rid, bool includePayload = true) const; /** Returns the collections defined in this document. */ - Collection::List collections() const; + Q_REQUIRED_RESULT Collection::List collections() const; /** Returns the tags defined in this document. */ - Tag::List tags() const; + Q_REQUIRED_RESULT Tag::List tags() const; /** Returns the immediate child collections of @p parentCollection. */ - Collection::List childCollections(const Collection &parentCollection) const; + Q_REQUIRED_RESULT Collection::List childCollections(const Collection &parentCollection) const; /** Returns the items in the given collection. */ - Item::List items(const Collection &collection, bool includePayload = true) const; + Q_REQUIRED_RESULT Item::List items(const Collection &collection, bool includePayload = true) const; private: Q_DISABLE_COPY(XmlDocument) diff --git a/src/xml/xmlreader.h b/src/xml/xmlreader.h --- a/src/xml/xmlreader.h +++ b/src/xml/xmlreader.h @@ -59,27 +59,27 @@ /** Converts a collection element. */ -AKONADI_XML_EXPORT Collection elementToCollection(const QDomElement &elem); +Q_REQUIRED_RESULT AKONADI_XML_EXPORT Collection elementToCollection(const QDomElement &elem); /** Reads recursively all collections starting from the given DOM element. */ -AKONADI_XML_EXPORT Collection::List readCollections(const QDomElement &elem); +Q_REQUIRED_RESULT AKONADI_XML_EXPORT Collection::List readCollections(const QDomElement &elem); /** Converts a tag element. */ -AKONADI_XML_EXPORT Tag elementToTag(const QDomElement &elem); +Q_REQUIRED_RESULT AKONADI_XML_EXPORT Tag elementToTag(const QDomElement &elem); /** Reads recursively all tags starting from the given DOM element. */ -AKONADI_XML_EXPORT Tag::List readTags(const QDomElement &elem); +Q_REQUIRED_RESULT AKONADI_XML_EXPORT Tag::List readTags(const QDomElement &elem); /** Converts an item element. */ -AKONADI_XML_EXPORT Item elementToItem(const QDomElement &elem, bool includePayload = true); +Q_REQUIRED_RESULT AKONADI_XML_EXPORT Item elementToItem(const QDomElement &elem, bool includePayload = true); } } diff --git a/src/xml/xmlwritejob.h b/src/xml/xmlwritejob.h --- a/src/xml/xmlwritejob.h +++ b/src/xml/xmlwritejob.h @@ -38,18 +38,15 @@ public: XmlWriteJob(const Collection &root, const QString &fileName, QObject *parent = nullptr); XmlWriteJob(const Collection::List &roots, const QString &fileName, QObject *parent = nullptr); - ~XmlWriteJob(); + ~XmlWriteJob() override; protected: /* reimpl. */ void doStart() override; private: friend class XmlWriteJobPrivate; XmlWriteJobPrivate *const d; void done(); - - Q_PRIVATE_SLOT(d, void collectionFetchResult(KJob *)) - Q_PRIVATE_SLOT(d, void itemFetchResult(KJob *)) }; } diff --git a/src/xml/xmlwriter.h b/src/xml/xmlwriter.h --- a/src/xml/xmlwriter.h +++ b/src/xml/xmlwriter.h @@ -41,7 +41,7 @@ /** Creates an attribute element for the given document. */ -AKONADI_XML_EXPORT QDomElement attributeToElement(Attribute *attr, QDomDocument &document); +Q_REQUIRED_RESULT AKONADI_XML_EXPORT QDomElement attributeToElement(Attribute *attr, QDomDocument &document); /** Serializes all attributes of the given Akonadi object into the given parent element. @@ -56,7 +56,7 @@ /** Creates a collection element for the given document, not yet attached to the DOM tree. */ -AKONADI_XML_EXPORT QDomElement collectionToElement(const Collection &collection, QDomDocument &document); +Q_REQUIRED_RESULT AKONADI_XML_EXPORT QDomElement collectionToElement(const Collection &collection, QDomDocument &document); /** Serializes the given collection into a DOM element with the given parent. @@ -66,7 +66,7 @@ /** Creates an item element for the given document, not yet attached to the DOM tree */ -AKONADI_XML_EXPORT QDomElement itemToElement(const Item &item, QDomDocument &document); +Q_REQUIRED_RESULT AKONADI_XML_EXPORT QDomElement itemToElement(const Item &item, QDomDocument &document); /** Serializes the given item into a DOM element and attaches it to the given item. diff --git a/templates/akonadiresource/CMakeLists.txt b/templates/akonadiresource/CMakeLists.txt --- a/templates/akonadiresource/CMakeLists.txt +++ b/templates/akonadiresource/CMakeLists.txt @@ -2,7 +2,9 @@ project(%{APPNAMELC}) -set(ECM_MIN_VERSION "5.22.0") +set(KF5_MIN_VERSION "5.38.0") + +set(ECM_MIN_VERSION ${KF5_MIN_VERSION}) find_package(ECM ${ECM_MIN_VERSION} CONFIG REQUIRED) set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules ${ECM_MODULE_PATH} ${CMAKE_MODULE_PATH}) @@ -13,21 +15,18 @@ include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE) include(ECMQtDeclareLoggingCategory) -set(QT_MIN_VERSION "5.6.0") +set(QT_MIN_VERSION "5.9.0") find_package(Qt5 ${QT_MIN_VERSION} REQUIRED Core DBus Gui) -set(KF5_MIN_VERSION "5.22.0") find_package(KF5Config ${KF5_MIN_VERSION} CONFIG REQUIRED) set(AKONADI_MIN_VERSION "5.2") find_package(KF5Akonadi ${AKONADI_MIN_VERSION} CONFIG REQUIRED) -find_package(Xsltproc REQUIRED) -set_package_properties(Xsltproc PROPERTIES - DESCRIPTION "XSLT processor from libxslt" - TYPE REQUIRED - PURPOSE "Required to generate a D-Bus interface for the resource." -) +find_program(XSLTPROC_EXECUTABLE xsltproc DOC "Path to the xsltproc executable") +if (NOT XSLTPROC_EXECUTABLE) + message(FATAL_ERROR "\nThe command line XSLT processor program 'xsltproc' could not be found.\nPlease install xsltproc.\n") +endif() add_subdirectory(src) diff --git a/templates/akonadiresource/akonadiresource.kdevtemplate b/templates/akonadiresource/akonadiresource.kdevtemplate --- a/templates/akonadiresource/akonadiresource.kdevtemplate +++ b/templates/akonadiresource/akonadiresource.kdevtemplate @@ -1,20 +1,25 @@ [General] Name=C++ +Name[ar]=سي++ +Name[ast]=C++ Name[ca]=C++ Name[ca@valencia]=C++ Name[cs]=C++ Name[de]=C++ Name[en_GB]=C++ Name[es]=C++ Name[fi]=C++ Name[fr]=C++ +Name[gl]=C++ Name[it]=C++ Name[ko]=C++ Name[nl]=C++ Name[pl]=C++ Name[pt]=C++ +Name[pt_BR]=C++ Name[ru]=C++ Name[sk]=C++ +Name[sl]=C++ Name[sr]=Ц++ Name[sr@ijekavian]=Ц++ Name[sr@ijekavianlatin]=C++ @@ -24,19 +29,23 @@ Name[x-test]=xxC++xx Name[zh_CN]=C++ Comment=Akonadi Resource Template. A template for an Akonadi PIM data resource +Comment[ar]=قالب موارد «أكونادي». قالب لمورد بيانات «أكونادي PIM» Comment[ca]=Una plantilla de recurs de l'Akonadi. Una plantilla per un recurs de dades PIM de l'Akonadi Comment[ca@valencia]=Una plantilla de recurs de l'Akonadi. Una plantilla per un recurs de dades PIM de l'Akonadi Comment[de]=Vorlage für Akonadi-Ressource. Vorlage für eine Datenressource für das Akonadi-PIM-Framework Comment[en_GB]=Akonadi Resource Template. A template for an Akonadi PIM data resource Comment[es]=Plantilla de recursos de Akonadi. Una plantilla para un recurso de datos PIM de Akonadi Comment[fi]=Akonadin resurssimalli Akonadin PIM-tietoresurssille Comment[fr]=Modèle de ressource Akonadi. Un modèle pour une ressource de données PIM Akonadi +Comment[gl]=Modelo de recurso de Akonadi. Un modelo para un recurso de datos de xestión de información persoal de Akonadi. Comment[it]=Modello di risorsa Akonadi. Un modello per una risorsa di dati di PIM Akonadi Comment[ko]=Akonadi 자원 템플릿. Akonadi PIM 데이터 자원을 위한 템플릿 Comment[nl]=Akonadi sjabloon voor hulpbron. Een sjabloon voor een Akonadi PIM gegevenshulpbron Comment[pl]=Szablon zasobu Akonadi. Szablon dla zasobu danych PIM Akonadi Comment[pt]=Modelo de Recursos do Akonadi. Um modelo para um recurso de dados PIM do Akonadi +Comment[pt_BR]=Modelo de recurso do Akonadi. Um modelo para um recurso de dados PIM do Akonadi Comment[sk]=Šablóna Akonadi zdroja. Šablóna pre zdroj dát Akonadi PIM +Comment[sl]=Predloga vira za Akonadi. Predloga za podatkovni vir Akonadi Comment[sr]=Шаблон за Аконадијев ресурс ПИМ података Comment[sr@ijekavian]=Шаблон за Аконадијев ресурс ПИМ података Comment[sr@ijekavianlatin]=Šablon za Akonadijev resurs PIM podataka diff --git a/templates/akonadiresource/cmake/modules/FindXsltproc.cmake b/templates/akonadiresource/cmake/modules/FindXsltproc.cmake deleted file mode 100644 --- a/templates/akonadiresource/cmake/modules/FindXsltproc.cmake +++ /dev/null @@ -1,33 +0,0 @@ -# Find xsltproc executable and provide a macro to generate D-Bus interfaces. -# -# The following variables are defined : -# XSLTPROC_EXECUTABLE - path to the xsltproc executable -# Xsltproc_FOUND - true if the program was found - -find_program(XSLTPROC_EXECUTABLE xsltproc DOC "Path to the xsltproc executable") -mark_as_advanced(XSLTPROC_EXECUTABLE) - -if(XSLTPROC_EXECUTABLE) - set(Xsltproc_FOUND TRUE) - - # We depend on Akonadi, make sure it's found - if(NOT DEFINED KF5Akonadi_DATA_DIR) - find_package(KF5Akonadi REQUIRED) - endif() - - - # Macro to generate a D-Bus interface description from a KConfigXT file - macro(kcfg_generate_dbus_interface _kcfg _name) - add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${_name}.xml - COMMAND ${XSLTPROC_EXECUTABLE} - --stringparam interfaceName ${_name} - ${KF5Akonadi_DATA_DIR}/kcfg2dbus.xsl - ${_kcfg} - > ${CMAKE_CURRENT_BINARY_DIR}/${_name}.xml - DEPENDS - ${KF5Akonadi_DATA_DIR}/kcfg2dbus.xsl - ${_kcfg} - ) - endmacro() -endif() diff --git a/templates/akonadiserializer/CMakeLists.txt b/templates/akonadiserializer/CMakeLists.txt --- a/templates/akonadiserializer/CMakeLists.txt +++ b/templates/akonadiserializer/CMakeLists.txt @@ -2,7 +2,9 @@ project(%{APPNAMELC}) -set(ECM_MIN_VERSION "5.22.0") +set(KF5_MIN_VERSION "5.38.0") + +set(ECM_MIN_VERSION ${KF5_MIN_VERSION}) find_package(ECM ${ECM_MIN_VERSION} CONFIG REQUIRED) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${CMAKE_MODULE_PATH}) @@ -12,10 +14,9 @@ include(KDECMakeSettings) include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE) -set(QT_MIN_VERSION "5.6.0") +set(QT_MIN_VERSION "5.9.0") find_package(Qt5 ${QT_MIN_VERSION} REQUIRED Core Network Gui) -set(KF5_MIN_VERSION "5.22.0") find_package(KF5Config ${KF5_MIN_VERSION} CONFIG REQUIRED) set(AKONADI_MIN_VERSION "5.2") diff --git a/templates/akonadiserializer/akonadiserializer.kdevtemplate b/templates/akonadiserializer/akonadiserializer.kdevtemplate --- a/templates/akonadiserializer/akonadiserializer.kdevtemplate +++ b/templates/akonadiserializer/akonadiserializer.kdevtemplate @@ -1,20 +1,25 @@ [General] Name=C++ +Name[ar]=سي++ +Name[ast]=C++ Name[ca]=C++ Name[ca@valencia]=C++ Name[cs]=C++ Name[de]=C++ Name[en_GB]=C++ Name[es]=C++ Name[fi]=C++ Name[fr]=C++ +Name[gl]=C++ Name[it]=C++ Name[ko]=C++ Name[nl]=C++ Name[pl]=C++ Name[pt]=C++ +Name[pt_BR]=C++ Name[ru]=C++ Name[sk]=C++ +Name[sl]=C++ Name[sr]=Ц++ Name[sr@ijekavian]=Ц++ Name[sr@ijekavianlatin]=C++ @@ -24,19 +29,23 @@ Name[x-test]=xxC++xx Name[zh_CN]=C++ Comment=Akonadi Serializer Template. A template for an Akonadi data serializer plugin +Comment[ar]=قالب سَلسَلة «أكونادي». قالب لملحقة سَلسَلة بيانات «أكونادي» Comment[ca]=Plantilla de serialitzador de l'Akonadi. Una plantilla per un connector de serialització de dades de l'Akonadi Comment[ca@valencia]=Plantilla de serialitzador de l'Akonadi. Una plantilla per un connector de serialització de dades de l'Akonadi Comment[de]=Vorlage für Akonadi-Serialisierer. Eine Vorlage für einen Datenserialisierer für das Akonadi-PIM-Framework Comment[en_GB]=Akonadi Serialiser Template. A template for an Akonadi data serialiser plugin Comment[es]=Plantilla serializadora de Akonadi. Una plantilla para un complemento serializador de Akonadi Comment[fi]=Akonadin serialisointimalli Akonadin dataserialisointiliitännäiselle Comment[fr]=Modèle de sérialiseur Akonadi. Un modèle pour un module externe sérialiseur de données pour Akonadi +Comment[gl]=Modelo de serializador de Akonadi. Un modelo para un serializador de datos de Akonadi. Comment[it]=Modello di serializzatore Akonadi. Un modello per un'estensione di serializzazione dei dati di Akonadi Comment[ko]=Akonadi 시리얼라이저 템플릿. Akonadi 데이터 시리얼라이저 플러그인을 위한 템플릿 Comment[nl]=Akonadi sjabloon voor serialisator. Een sjabloon voor een Akonadi serialisatorplug-in Comment[pl]=Szablon serializacji Akonadi. Szablon dla wtyczki serializacji danych Akonadi Comment[pt]=Modelo de Serialização do Akonadi. Um modelo para um 'plugin' de serialização de dados do Akonadi +Comment[pt_BR]=Modelo de serialização do Akonadi. Um modelo para um plugin de serialização de dados do Akonadi Comment[sk]=Šablóna Akonadi serializátora. Šablóna pre plugin serializátora dát Akonadi +Comment[sl]=Predloga razvrščevalnika za Akonadi. Predloga za vstavek razvrščevalnika podatkovnih virov Akonadi Comment[sr]=Шаблон прикључка Аконадијевог серијализатора Comment[sr@ijekavian]=Шаблон прикључка Аконадијевог серијализатора Comment[sr@ijekavianlatin]=Šablon priključka Akonadijevog serijalizatora diff --git a/tests/libs/CMakeLists.txt b/tests/libs/CMakeLists.txt --- a/tests/libs/CMakeLists.txt +++ b/tests/libs/CMakeLists.txt @@ -1,4 +1,4 @@ -set(QT_REQUIRED_VERSION "5.8.0") +set(QT_REQUIRED_VERSION "5.9.0") find_package(Qt5Test ${QT_REQUIRED_VERSION} CONFIG REQUIRED) if(${EXECUTABLE_OUTPUT_PATH}) @@ -42,5 +42,6 @@ ##REACTIVATE #add_akonadi_demo(selftester.cpp) add_akonadi_demo(collectiondialog.cpp) +add_akonadi_demo(conflictresolvedialogtest_gui.cpp) add_subdirectory(etm_test_app) diff --git a/tests/libs/agenttypewidgettest.cpp b/tests/libs/agenttypewidgettest.cpp --- a/tests/libs/agenttypewidgettest.cpp +++ b/tests/libs/agenttypewidgettest.cpp @@ -47,8 +47,8 @@ this, SLOT(filterChanged(int))); mWidget = new Akonadi::AgentTypeWidget(this); - connect(mWidget, SIGNAL(currentChanged(Akonadi::AgentType,Akonadi::AgentType)), - this, SLOT(currentChanged(Akonadi::AgentType,Akonadi::AgentType))); + connect(mWidget, &Akonadi::AgentTypeWidget::currentChanged, + this, &Dialog::currentChanged); QDialogButtonBox *box = new QDialogButtonBox(this); diff --git a/autotests/server/fakeconnection.cpp b/tests/libs/conflictresolvedialogtest_gui.cpp copy from autotests/server/fakeconnection.cpp copy to tests/libs/conflictresolvedialogtest_gui.cpp --- a/autotests/server/fakeconnection.cpp +++ b/tests/libs/conflictresolvedialogtest_gui.cpp @@ -1,5 +1,5 @@ /* - Copyright (c) 2014 Daniel Vrátil + Copyright (C) 2017-2018 Laurent Montel 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 @@ -17,39 +17,29 @@ 02110-1301, USA. */ -#include "fakeconnection.h" +#include "../src/widgets/conflictresolvedialog_p.h" -#include "fakedatastore.h" -#include "fakeakonadiserver.h" +#include +#include +#include +#include - -using namespace Akonadi::Server; - -FakeConnection::FakeConnection(quintptr socketDescriptor, QObject *parent) - : Connection(socketDescriptor, parent) -{ -} - -FakeConnection::FakeConnection(QObject *parent) - : Connection(parent) -{ -} - -FakeConnection::~FakeConnection() -{ - -} - -DataStore *FakeConnection::storageBackend() -{ - if (!m_backend) { - m_backend = static_cast(FakeDataStore::self()); - } - - return m_backend; -} - -NotificationCollector *FakeConnection::notificationCollector() +int main(int argc, char **argv) { - return storageBackend()->notificationCollector(); + QApplication app(argc, argv); + KAboutData aboutData(QStringLiteral("conflictresolvedialogtest_gui"), + QStringLiteral("conflictresolvedialogtest_gui"), + QStringLiteral("0.10")); + KAboutData::setApplicationData(aboutData); + + QCommandLineParser parser; + aboutData.setupCommandLine(&parser); + parser.process(app); + aboutData.processCommandLine(&parser); + + QStandardPaths::setTestModeEnabled(true); + Akonadi::ConflictResolveDialog dlg; + dlg.exec(); + + return 0; } diff --git a/tests/libs/etm_test_app/mainwindow.h b/tests/libs/etm_test_app/mainwindow.h --- a/tests/libs/etm_test_app/mainwindow.h +++ b/tests/libs/etm_test_app/mainwindow.h @@ -33,7 +33,7 @@ { Q_OBJECT public: - MainWindow(QWidget *parent = nullptr, Qt::WindowFlags flags = 0); + MainWindow(QWidget *parent = nullptr, Qt::WindowFlags flags = {}); private Q_SLOTS: void moveCollection();
\n" ).arg(textDirection); - QString tipInfo; - tipInfo += QStringLiteral( + QString tipInfo = QStringLiteral( " %1: %2
\n" " %3: %4

\n" ).arg(i18n("Total Messages")).arg(collection.statistics().count()) diff --git a/src/core/models/subscriptionmodel_p.h b/src/core/models/subscriptionmodel_p.h --- a/src/core/models/subscriptionmodel_p.h +++ b/src/core/models/subscriptionmodel_p.h @@ -51,7 +51,7 @@ /** Destructor. */ - ~SubscriptionModel(); + ~SubscriptionModel() override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; Qt::ItemFlags flags(const QModelIndex &index) const override; diff --git a/src/core/models/tagmodel.h b/src/core/models/tagmodel.h --- a/src/core/models/tagmodel.h +++ b/src/core/models/tagmodel.h @@ -52,23 +52,23 @@ }; explicit TagModel(Monitor *recorder, QObject *parent); - virtual ~TagModel(); + ~TagModel() override; - int columnCount(const QModelIndex &parent = QModelIndex()) const override; - int rowCount(const QModelIndex &parent = QModelIndex()) const override; + Q_REQUIRED_RESULT int columnCount(const QModelIndex &parent = QModelIndex()) const override; + Q_REQUIRED_RESULT int rowCount(const QModelIndex &parent = QModelIndex()) const override; - QVariant data(const QModelIndex &index, int role) const override; - QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + Q_REQUIRED_RESULT QVariant data(const QModelIndex &index, int role) const override; + Q_REQUIRED_RESULT QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; - Qt::ItemFlags flags(const QModelIndex &index) const override; + Q_REQUIRED_RESULT Qt::ItemFlags flags(const QModelIndex &index) const override; /* virtual Qt::DropActions supportedDropActions() const; virtual QMimeData* mimeData( const QModelIndexList &indexes ) const; virtual bool dropMimeData( const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent ); */ - QModelIndex parent(const QModelIndex &child) const override; - QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; + Q_REQUIRED_RESULT QModelIndex parent(const QModelIndex &child) const override; + Q_REQUIRED_RESULT QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; protected: Q_DECLARE_PRIVATE(TagModel) diff --git a/src/core/models/tagmodel_p.h b/src/core/models/tagmodel_p.h --- a/src/core/models/tagmodel_p.h +++ b/src/core/models/tagmodel_p.h @@ -47,8 +47,8 @@ void monitoredTagChanged(const Akonadi::Tag &tag); void monitoredTagRemoved(const Akonadi::Tag &tag); - QModelIndex indexForTag(qint64 tagId) const; - Tag tagForIndex(const QModelIndex &index) const; + Q_REQUIRED_RESULT QModelIndex indexForTag(qint64 tagId) const; + Q_REQUIRED_RESULT Tag tagForIndex(const QModelIndex &index) const; void removeTagsRecursively(qint64 parentTag); diff --git a/src/core/models/trashfilterproxymodel.h b/src/core/models/trashfilterproxymodel.h --- a/src/core/models/trashfilterproxymodel.h +++ b/src/core/models/trashfilterproxymodel.h @@ -59,10 +59,10 @@ public: explicit TrashFilterProxyModel(QObject *parent = nullptr); - virtual ~TrashFilterProxyModel(); + ~TrashFilterProxyModel() override; void showTrash(bool enable); - bool trashIsShown() const; + Q_REQUIRED_RESULT bool trashIsShown() const; protected: /** diff --git a/src/core/models/trashfilterproxymodel.cpp b/src/core/models/trashfilterproxymodel.cpp --- a/src/core/models/trashfilterproxymodel.cpp +++ b/src/core/models/trashfilterproxymodel.cpp @@ -27,10 +27,9 @@ { public: TrashFilterProxyModelPrivate() - : mTrashIsShown(false) { } - bool mTrashIsShown; + bool mTrashIsShown = false; }; TrashFilterProxyModel::TrashFilterProxyModel(QObject *parent) diff --git a/src/core/monitor.h b/src/core/monitor.h --- a/src/core/monitor.h +++ b/src/core/monitor.h @@ -124,7 +124,7 @@ /** * Destroys the monitor. */ - virtual ~Monitor(); + ~Monitor() override; /** * Sets whether the specified collection shall be monitored for changes. If @@ -214,7 +214,7 @@ void setAllMonitored(bool monitored = true); void setExclusive(bool exclusive = true); - bool exclusive() const; + Q_REQUIRED_RESULT bool exclusive() const; /** * Ignores all change notifications caused by the given session. This @@ -347,72 +347,72 @@ * * @since 4.3 */ - Collection::List collectionsMonitored() const; + Q_REQUIRED_RESULT Collection::List collectionsMonitored() const; /** * Returns the set of items being monitored. * * Faster version (at least on 32-bit systems) of itemsMonitored(). * * @since 4.6 */ - QVector itemsMonitoredEx() const; + Q_REQUIRED_RESULT QVector itemsMonitoredEx() const; /** * Returns the number of items being monitored. * Optimization. * @since 4.14.3 */ - int numItemsMonitored() const; + Q_REQUIRED_RESULT int numItemsMonitored() const; /** * Returns the set of mimetypes being monitored. * * @since 4.3 */ - QStringList mimeTypesMonitored() const; + Q_REQUIRED_RESULT QStringList mimeTypesMonitored() const; /** * Returns the number of mimetypes being monitored. * Optimization. * @since 4.14.3 */ - int numMimeTypesMonitored() const; + Q_REQUIRED_RESULT int numMimeTypesMonitored() const; /** * Returns the set of tags being monitored. * * @since 4.13 */ - QVector tagsMonitored() const; + Q_REQUIRED_RESULT QVector tagsMonitored() const; /** * Returns the set of types being monitored. * * @since 4.13 */ - QVector typesMonitored() const; + Q_REQUIRED_RESULT QVector typesMonitored() const; /** * Returns the set of identifiers for resources being monitored. * * @since 4.3 */ - QList resourcesMonitored() const; + Q_REQUIRED_RESULT QList resourcesMonitored() const; /** * Returns the number of resources being monitored. * Optimization. * @since 4.14.3 */ - int numResourcesMonitored() const; + Q_REQUIRED_RESULT int numResourcesMonitored() const; /** * Returns true if everything is being monitored. * * @since 4.3 */ - bool isAllMonitored() const; + Q_REQUIRED_RESULT bool isAllMonitored() const; /** * Sets the session used by the Monitor to communicate with the %Akonadi server. @@ -427,7 +427,7 @@ * * @since 4.4 */ - Session *session() const; + Q_REQUIRED_RESULT Session *session() const; /** * Allows to enable/disable collection move translation. If enabled (the default), move @@ -821,7 +821,7 @@ Q_PRIVATE_SLOT(d_ptr, void slotStatisticsChangedFinished(KJob *)) Q_PRIVATE_SLOT(d_ptr, void slotFlushRecentlyChangedCollections()) Q_PRIVATE_SLOT(d_ptr, void slotUpdateSubscription()) - Q_PRIVATE_SLOT(d_ptr, void commandReceived(qint64 tag, const Akonadi::Protocol::CommandPtr &)) + Q_PRIVATE_SLOT(d_ptr, void handleCommands()) Q_PRIVATE_SLOT(d_ptr, void dataAvailable()) Q_PRIVATE_SLOT(d_ptr, void serverStateChanged(Akonadi::ServerManager::State)) Q_PRIVATE_SLOT(d_ptr, void invalidateCollectionCache(qint64)) diff --git a/src/core/monitor.cpp b/src/core/monitor.cpp --- a/src/core/monitor.cpp +++ b/src/core/monitor.cpp @@ -151,11 +151,11 @@ Q_D(Monitor); if (!d->types.contains(type) && monitored) { d->types.insert(type); - d->pendingModification.startMonitoringType(static_cast(type)); + d->pendingModification.startMonitoringType(MonitorPrivate::monitorTypeToProtocol(type)); d->scheduleSubscriptionUpdate(); } else if (!monitored) { if (d->types.remove(type)) { - d->pendingModification.stopMonitoringType(static_cast(type)); + d->pendingModification.stopMonitoringType(MonitorPrivate::monitorTypeToProtocol(type)); d->scheduleSubscriptionUpdate(); } } @@ -220,11 +220,15 @@ { Q_D(Monitor); d->mItemFetchScope = fetchScope; + d->pendingModificationChanges |= Protocol::ModifySubscriptionCommand::ItemFetchScope; + d->scheduleSubscriptionUpdate(); } ItemFetchScope &Monitor::itemFetchScope() { Q_D(Monitor); + d->pendingModificationChanges |= Protocol::ModifySubscriptionCommand::ItemFetchScope; + d->scheduleSubscriptionUpdate(); return d->mItemFetchScope; } @@ -238,23 +242,31 @@ { Q_D(Monitor); d->mCollectionFetchScope = fetchScope; + d->pendingModificationChanges |= Protocol::ModifySubscriptionCommand::CollectionFetchScope; + d->scheduleSubscriptionUpdate(); } CollectionFetchScope &Monitor::collectionFetchScope() { Q_D(Monitor); + d->pendingModificationChanges |= Protocol::ModifySubscriptionCommand::CollectionFetchScope; + d->scheduleSubscriptionUpdate(); return d->mCollectionFetchScope; } void Monitor::setTagFetchScope(const TagFetchScope &fetchScope) { Q_D(Monitor); d->mTagFetchScope = fetchScope; + d->pendingModificationChanges |= Protocol::ModifySubscriptionCommand::TagFetchScope; + d->scheduleSubscriptionUpdate(); } TagFetchScope &Monitor::tagFetchScope() { Q_D(Monitor); + d->pendingModificationChanges |= Protocol::ModifySubscriptionCommand::TagFetchScope; + d->scheduleSubscriptionUpdate(); return d->mTagFetchScope; } @@ -342,6 +354,7 @@ d->itemCache->setSession(d->session); d->collectionCache->setSession(d->session); + d->tagCache->setSession(d->session); // Reconnect with a new session d->connectToNotificationManager(); diff --git a/src/core/monitor_p.h b/src/core/monitor_p.h --- a/src/core/monitor_p.h +++ b/src/core/monitor_p.h @@ -33,14 +33,16 @@ #include "servermanager.h" #include "changenotificationdependenciesfactory_p.h" #include "connection_p.h" +#include "commandbuffer_p.h" #include "private/protocol_p.h" #include #include #include #include +#include namespace Akonadi { @@ -61,7 +63,7 @@ Monitor *q_ptr; Q_DECLARE_PUBLIC(Monitor) ChangeNotificationDependenciesFactory *dependenciesFactory = nullptr; - Connection *ntfConnection = nullptr; + QPointer ntfConnection; Collection::List collections; QSet resources; QSet items; @@ -81,6 +83,9 @@ TagListCache *tagCache = nullptr; QMimeDatabase mimeDatabase; + CommandBuffer mCommandBuffer; + + Protocol::ModifySubscriptionCommand::ModifiedParts pendingModificationChanges; Protocol::ModifySubscriptionCommand pendingModification; QTimer *pendingModificationTimer; bool monitorReady; @@ -109,6 +114,8 @@ // Virtual so it can be overridden in FakeMonitor. virtual bool connectToNotificationManager(); + void disconnectFromNotificationManager(); + void dispatchNotifications(); void flushPipeline(); @@ -141,7 +148,7 @@ */ int translateAndCompress(QQueue ¬ificationQueue, const Protocol::ChangeNotificationPtr &msg); - void commandReceived(qint64 tag, const Protocol::CommandPtr &command); + void handleCommands(); virtual void slotNotify(const Protocol::ChangeNotificationPtr &msg); @@ -188,6 +195,8 @@ void scheduleSubscriptionUpdate(); void slotUpdateSubscription(); + static Protocol::ModifySubscriptionCommand::ChangeType monitorTypeToProtocol(Monitor::Type type); + /** @brief Class used to determine when to purge items in a Collection diff --git a/src/core/monitor_p.cpp b/src/core/monitor_p.cpp --- a/src/core/monitor_p.cpp +++ b/src/core/monitor_p.cpp @@ -31,6 +31,7 @@ #include "akonadicore_debug.h" #include "notificationsubscriber.h" #include "changenotification.h" +#include "protocolhelper_p.h" using namespace Akonadi; @@ -49,6 +50,8 @@ , collectionCache(nullptr) , itemCache(nullptr) , tagCache(nullptr) + , mCommandBuffer(parent, "handleCommands") + , pendingModificationChanges(Protocol::ModifySubscriptionCommand::None) , pendingModificationTimer(nullptr) , monitorReady(false) , fetchCollection(false) @@ -60,12 +63,11 @@ MonitorPrivate::~MonitorPrivate() { + disconnectFromNotificationManager(); delete dependenciesFactory; delete collectionCache; delete itemCache; delete tagCache; - ntfConnection->disconnect(q_ptr); - ntfConnection->deleteLater(); } void MonitorPrivate::init() @@ -99,43 +101,26 @@ return false; } - ntfConnection = dependenciesFactory->createNotificationConnection(session); + ntfConnection = dependenciesFactory->createNotificationConnection(session, &mCommandBuffer); if (!ntfConnection) { return false; } - q_ptr->connect(ntfConnection, SIGNAL(commandReceived(qint64,Akonadi::Protocol::CommandPtr)), - q_ptr, SLOT(commandReceived(qint64,Akonadi::Protocol::CommandPtr))); - pendingModification = Protocol::ModifySubscriptionCommand(); - for (const auto &col : qAsConst(collections)) { - pendingModification.startMonitoringCollection(col.id()); - } - for (const auto &res : qAsConst(resources)) { - pendingModification.startMonitoringResource(res); - } - for (auto itemId : qAsConst(items)) { - pendingModification.startMonitoringItem(itemId); - } - for (auto tagId : qAsConst(tags)) { - pendingModification.startMonitoringTag(tagId); - } - for (auto type : qAsConst(types)) { - pendingModification.startMonitoringType(static_cast(type)); - } - for (const auto &mimetype : qAsConst(mimetypes)) { - pendingModification.startMonitoringMimeType(mimetype); - } - for (const auto &session : qAsConst(sessions)) { - pendingModification.startIgnoringSession(session); - } - pendingModification.setAllMonitored(monitorAll); - pendingModification.setIsExclusive(exclusive); + slotUpdateSubscription(); ntfConnection->reconnect(); return true; } +void MonitorPrivate::disconnectFromNotificationManager() +{ + if (ntfConnection) { + ntfConnection->disconnect(q_ptr); + dependenciesFactory->destroyNotificationConnection(session, ntfConnection.data()); + } +} + void MonitorPrivate::serverStateChanged(ServerManager::State state) { if (state == ServerManager::Running) { @@ -151,6 +136,16 @@ void MonitorPrivate::invalidateItemCache(qint64 id) { itemCache->update(QList() << id, mItemFetchScope); + // Also invalidate content of all any pending notification for given item + for (auto it = pendingNotifications.begin(), end = pendingNotifications.end(); it != end; ++it) { + if ((*it)->type() == Protocol::Command::ItemChangeNotification) { + auto &ntf = Protocol::cmdCast(*it); + const auto items = ntf.items(); + if (std::any_of(items.cbegin(), items.cend(), [id](const Protocol::FetchItemsResponse &r) { return r.id() == id; })) { + ntf.setMustRetrieve(true); + } + } + } } void MonitorPrivate::invalidateTagCache(qint64 id) @@ -179,9 +174,22 @@ void MonitorPrivate::slotUpdateSubscription() { - delete pendingModificationTimer; + Q_Q(Monitor); + if (pendingModificationTimer) + pendingModificationTimer->deleteLater(); pendingModificationTimer = nullptr; + if (pendingModificationChanges & Protocol::ModifySubscriptionCommand::ItemFetchScope) { + pendingModification.setItemFetchScope(ProtocolHelper::itemFetchScopeToProtocol(mItemFetchScope)); + } + if (pendingModificationChanges & Protocol::ModifySubscriptionCommand::CollectionFetchScope) { + pendingModification.setCollectionFetchScope(ProtocolHelper::collectionFetchScopeToProtocol(mCollectionFetchScope)); + } + if (pendingModificationChanges & Protocol::ModifySubscriptionCommand::TagFetchScope) { + pendingModification.setTagFetchScope(ProtocolHelper::tagFetchScopeToProtocol(mTagFetchScope)); + } + pendingModificationChanges = Protocol::ModifySubscriptionCommand::None; + if (ntfConnection) { ntfConnection->sendCommand(3, Protocol::ModifySubscriptionCommandPtr::create(pendingModification)); pendingModification = Protocol::ModifySubscriptionCommand(); @@ -337,10 +345,10 @@ const auto items = msg.items(); list.reserve(items.count()); - for (const Protocol::ItemChangeNotification::Item &item : items) { + for (const auto &item : items) { auto copy = Protocol::ItemChangeNotificationPtr::create(baseMsg); - copy->setItems({ { item.id, item.remoteId, item.remoteRevision, item.mimeType } }); - list << copy; + copy->setItems({std::move(Protocol::FetchItemsResponse(item))}); + list.push_back(std::move(copy)); } return list; @@ -358,11 +366,16 @@ bool MonitorPrivate::ensureDataAvailable(const Protocol::ChangeNotificationPtr &msg) { - bool allCached = true; - if (msg->type() == Protocol::Command::TagChangeNotification) { - return tagCache->ensureCached({ Protocol::cmdCast(msg).id() }, mTagFetchScope); + const auto tagMsg = Protocol::cmdCast(msg); + if (tagMsg.metadata().contains("FETCH_TAG")) { + if (!tagCache->ensureCached({ tagMsg.tag().id() }, mTagFetchScope)) { + return false; + } + } + return true; } + if (msg->type() == Protocol::Command::RelationChangeNotification) { return true; } @@ -383,6 +396,7 @@ return parentCollection <= -1 || collectionCache->ensureCached(parentCollection, mCollectionFetchScope); } + bool allCached = true; if (fetchCollections()) { const qint64 parentCollection = (msg->type() == Protocol::Command::ItemChangeNotification) ? Protocol::cmdCast(msg).parentCollection() : @@ -413,53 +427,73 @@ } if (msg->type() == Protocol::Command::ItemChangeNotification && fetchItems()) { - ItemFetchScope scope(mItemFetchScope); const auto &itemNtf = Protocol::cmdCast(msg); if (mFetchChangedOnly && (itemNtf.operation() == Protocol::ItemChangeNotification::Modify - || itemNtf.operation() == Protocol::ItemChangeNotification::ModifyFlags)) { - bool fullPayloadWasRequested = scope.fullPayload(); - scope.fetchFullPayload(false); - const QSet requestedPayloadParts = scope.payloadParts(); - for (const QByteArray &part : requestedPayloadParts) { - scope.fetchPayloadPart(part, false); - } - - bool allAttributesWereRequested = scope.allAttributes(); - const QSet requestedAttrParts = scope.attributes(); - for (const QByteArray &part : requestedAttrParts) { - scope.fetchAttribute(part, false); - } + || itemNtf.operation() == Protocol::ItemChangeNotification::ModifyFlags)) { - const QSet changedParts = itemNtf.itemParts(); + const auto changedParts = itemNtf.itemParts(); + const auto requestedParts = mItemFetchScope.payloadParts(); + const auto requestedAttrs = mItemFetchScope.attributes(); + QSet missingParts, missingAttributes; for (const QByteArray &part : changedParts) { + const auto partName = part.mid(4); if (part.startsWith("PLD:") && //krazy:exclude=strings since QByteArray - (fullPayloadWasRequested || requestedPayloadParts.contains(part))) { - scope.fetchPayloadPart(part.mid(4), true);; - } - if (part.startsWith("ATR:") && //krazy:exclude=strings since QByteArray - (allAttributesWereRequested || requestedAttrParts.contains(part))) { - scope.fetchAttribute(part.mid(4), true); + (!mItemFetchScope.fullPayload() || !requestedParts.contains(partName))) { + missingParts.insert(partName); + } else if (part.startsWith("ATR:") && //krazy:exclude=strings since QByteArray + (!mItemFetchScope.allAttributes() || !requestedAttrs.contains(partName))) { + missingAttributes.insert(partName); } } - } - if (!itemCache->ensureCached(Protocol::ChangeNotification::itemsToUids(itemNtf.items()), scope)) { - allCached = false; + if (!missingParts.isEmpty() || !missingAttributes.isEmpty()) { + ItemFetchScope scope(mItemFetchScope); + scope.fetchFullPayload(false); + for (const auto &part : requestedParts) { + scope.fetchPayloadPart(part, false); + } + for (const auto &attr : requestedAttrs) { + scope.fetchAttribute(attr, false); + } + for (const auto &part : missingParts) { + scope.fetchPayloadPart(part, true); + } + for (const auto &attr : missingAttributes) { + scope.fetchAttribute(attr, true); + } + + if (!itemCache->ensureCached(Protocol::ChangeNotification::itemsToUids(itemNtf.items()), scope)) { + return false; + } + } + return allCached; } // Make sure all tags for ModifyTags operation are in cache too if (itemNtf.operation() == Protocol::ItemChangeNotification::ModifyTags) { if (!tagCache->ensureCached((itemNtf.addedTags() + itemNtf.removedTags()).toList(), mTagFetchScope)) { - allCached = false; + return false; + } + } + + if (itemNtf.metadata().contains("FETCH_ITEM") || itemNtf.mustRetrieve()) { + if (!itemCache->ensureCached(Protocol::ChangeNotification::itemsToUids(itemNtf.items()), mItemFetchScope)) { + return false; } } + return allCached; + } else if (msg->type() == Protocol::Command::CollectionChangeNotification && fetchCollections()) { - const qint64 colId = Protocol::cmdCast(msg).id(); - if (!collectionCache->ensureCached(colId, mCollectionFetchScope)) { - allCached = false; + const auto &colMsg = Protocol::cmdCast(msg); + if (colMsg.metadata().contains("FETCH_COLLECTION")) { + if (!collectionCache->ensureCached(colMsg.collection().id(), mCollectionFetchScope)) { + return false; + } } + + return allCached; } return allCached; @@ -470,16 +504,18 @@ bool someoneWasListening = false; if (msg->type() == Protocol::Command::TagChangeNotification) { const auto &tagNtf = Protocol::cmdCast(msg); - //In case of a Remove notification this will return a list of invalid entities (we'll deal later with them) - const Tag::List tags = tagCache->retrieve({ tagNtf.id() }); - someoneWasListening = emitTagNotification(tagNtf, tags.isEmpty() ? Tag() : tags[0]); + const bool fetched = tagNtf.metadata().contains("FETCH_TAG"); + Tag tag; + if (fetched) { + const auto tags = tagCache->retrieve({ tagNtf.tag().id() }); + tag = tags.isEmpty() ? Tag() : tags.at(0); + } else { + tag = ProtocolHelper::parseTag(tagNtf.tag()); + } + someoneWasListening = emitTagNotification(tagNtf, tag); } else if (msg->type() == Protocol::Command::RelationChangeNotification) { const auto &relNtf = Protocol::cmdCast(msg); - Relation rel; - rel.setLeft(Akonadi::Item(relNtf.leftItem())); - rel.setRight(Akonadi::Item(relNtf.rightItem())); - rel.setType(relNtf.type().toLatin1()); - rel.setRemoteId(relNtf.remoteId().toLatin1()); + const Relation rel = ProtocolHelper::parseRelationFetchResult(relNtf.relation()); someoneWasListening = emitRelationNotification(relNtf, rel); } else if (msg->type() == Protocol::Command::CollectionChangeNotification) { const auto &colNtf = Protocol::cmdCast(msg); @@ -489,8 +525,9 @@ destParent = collectionCache->retrieve(colNtf.parentDestCollection()); } + const bool fetched = colNtf.metadata().contains("FETCH_COLLECTION"); //For removals this will retrieve an invalid collection. We'll deal with that in emitCollectionNotification - const Collection col = collectionCache->retrieve(colNtf.id()); + const Collection col = fetched ? collectionCache->retrieve(colNtf.collection().id()) : ProtocolHelper::parseCollection(colNtf.collection(), true); //It is possible that the retrieval fails also in the non-removal case (e.g. because the item was meanwhile removed while //the changerecorder stored the notification or the notification was in the queue). In order to drop such invalid notifications we have to ignore them. if (col.isValid() || colNtf.operation() == Protocol::CollectionChangeNotification::Remove || !fetchCollections()) { @@ -503,8 +540,18 @@ if (itemNtf.operation() == Protocol::ItemChangeNotification::Move) { destParent = collectionCache->retrieve(itemNtf.parentDestCollection()); } + const bool fetched = itemNtf.metadata().contains("FETCH_ITEM") || itemNtf.mustRetrieve(); //For removals this will retrieve an empty set. We'll deal with that in emitItemNotification - const Item::List items = itemCache->retrieve(Protocol::ChangeNotification::itemsToUids(itemNtf.items())); + Item::List items; + if (fetched && fetchItems()) { + items = itemCache->retrieve(Protocol::ChangeNotification::itemsToUids(itemNtf.items())); + } else { + const auto ntfItems = itemNtf.items(); + items.reserve(ntfItems.size()); + for (const auto &ntfItem : ntfItems) { + items.push_back(ProtocolHelper::parseItemFetchResult(ntfItem)); + } + } //It is possible that the retrieval fails also in the non-removal case (e.g. because the item was meanwhile removed while //the changerecorder stored the notification or the notification was in the queue). In order to drop such invalid notifications we have to ignore them. if (!items.isEmpty() || itemNtf.operation() == Protocol::ItemChangeNotification::Remove || !fetchItems()) { @@ -520,14 +567,29 @@ subscriber.setMonitoredTags(subNtf.tags()); QSet monitorTypes; Q_FOREACH (auto type, subNtf.types()) { - monitorTypes.insert(static_cast(type)); + if (type == Protocol::ModifySubscriptionCommand::NoType) { + continue; + } + monitorTypes.insert([](Protocol::ModifySubscriptionCommand::ChangeType type) { + switch (type) { + case Protocol::ModifySubscriptionCommand::ItemChanges: return Monitor::Items; + case Protocol::ModifySubscriptionCommand::CollectionChanges: return Monitor::Collections; + case Protocol::ModifySubscriptionCommand::TagChanges: return Monitor::Tags; + case Protocol::ModifySubscriptionCommand::RelationChanges: return Monitor::Relations; + case Protocol::ModifySubscriptionCommand::SubscriptionChanges: return Monitor::Subscribers; + case Protocol::ModifySubscriptionCommand::ChangeNotifications: return Monitor::Notifications; + default: Q_ASSERT(false); return Monitor::Items; //unreachable + } + }(type)); } subscriber.setMonitoredTypes(monitorTypes); subscriber.setMonitoredMimeTypes(subNtf.mimeTypes()); subscriber.setMonitoredResources(subNtf.resources()); subscriber.setIgnoredSessions(subNtf.ignoredSessions()); subscriber.setIsAllMonitored(subNtf.allMonitored()); subscriber.setIsExclusive(subNtf.exclusive()); + subscriber.setItemFetchScope(ProtocolHelper::parseItemFetchScope(subNtf.itemFetchScope())); + subscriber.setCollectionFetchScope(ProtocolHelper::parseCollectionFetchScope(subNtf.collectionFetchScope())); someoneWasListening = emitSubscriptionChangeNotification(subNtf, subscriber); } else if (msg->type() == Protocol::Command::DebugChangeNotification) { const auto &changeNtf = Protocol::cmdCast(msg); @@ -573,7 +635,7 @@ const auto &colNtf = Protocol::cmdCast(msg); if (colNtf.operation() == Protocol::CollectionChangeNotification::Remove) { // no need for statistics updates anymore - recentlyChangedCollections.remove(colNtf.id()); + recentlyChangedCollections.remove(colNtf.collection().id()); } } } @@ -721,61 +783,99 @@ return 0; } -void MonitorPrivate::commandReceived(qint64 tag, const Protocol::CommandPtr &command) +void MonitorPrivate::handleCommands() { Q_Q(Monitor); - Q_UNUSED(tag); - if (command->isResponse()) { - switch (command->type()) { - case Protocol::Command::Hello: { - qCDebug(AKONADICORE_LOG) << q_ptr << "Connected to notification bus"; - QByteArray subname; - if (!q->objectName().isEmpty()) { - subname = q->objectName().toLatin1(); - } else { - subname = session->sessionId(); - } - subname += " - " + QByteArray::number(quintptr(q)); - qCDebug(AKONADICORE_LOG) << q_ptr << "Subscribing as \"" << subname << "\""; - auto subCmd = Protocol::CreateSubscriptionCommandPtr::create(subname, session->sessionId()); - ntfConnection->sendCommand(2, subCmd); - break; - } - case Protocol::Command::CreateSubscription: { - auto msubCmd = Protocol::ModifySubscriptionCommandPtr::create(pendingModification); - pendingModification = Protocol::ModifySubscriptionCommand(); - ntfConnection->sendCommand(3, msubCmd); - break; - } + CommandBufferLocker lock(&mCommandBuffer); + CommandBufferNotifyBlocker notify(&mCommandBuffer); + while (!mCommandBuffer.isEmpty()) { + const auto cmd = mCommandBuffer.dequeue(); + lock.unlock(); + const auto command = cmd.command; + + if (command->isResponse()) { + switch (command->type()) { + case Protocol::Command::Hello: { + qCDebug(AKONADICORE_LOG) << q_ptr << "Connected to notification bus"; + QByteArray subname; + if (!q->objectName().isEmpty()) { + subname = q->objectName().toLatin1(); + } else { + subname = session->sessionId(); + } + subname += " - " + QByteArray::number(quintptr(q)); + qCDebug(AKONADICORE_LOG) << q_ptr << "Subscribing as \"" << subname << "\""; + auto subCmd = Protocol::CreateSubscriptionCommandPtr::create(subname, session->sessionId()); + ntfConnection->sendCommand(2, subCmd); + break; + } - case Protocol::Command::ModifySubscription: - // TODO: Handle errors - if (!monitorReady) { - monitorReady = true; - Q_EMIT q_ptr->monitorReady(); + case Protocol::Command::CreateSubscription: { + auto msubCmd = Protocol::ModifySubscriptionCommandPtr::create(); + for (const auto &col : qAsConst(collections)) { + msubCmd->startMonitoringCollection(col.id()); + } + for (const auto &res : qAsConst(resources)) { + msubCmd->startMonitoringResource(res); + } + for (auto itemId : qAsConst(items)) { + msubCmd->startMonitoringItem(itemId); + } + for (auto tagId : qAsConst(tags)) { + msubCmd->startMonitoringTag(tagId); + } + for (auto type : qAsConst(types)) { + msubCmd->startMonitoringType(monitorTypeToProtocol(type)); + } + for (const auto &mimetype : qAsConst(mimetypes)) { + msubCmd->startMonitoringMimeType(mimetype); + } + for (const auto &session : qAsConst(sessions)) { + msubCmd->startIgnoringSession(session); + } + msubCmd->setAllMonitored(monitorAll); + msubCmd->setIsExclusive(exclusive); + msubCmd->setItemFetchScope(ProtocolHelper::itemFetchScopeToProtocol(mItemFetchScope)); + msubCmd->setCollectionFetchScope(ProtocolHelper::collectionFetchScopeToProtocol(mCollectionFetchScope)); + msubCmd->setTagFetchScope(ProtocolHelper::tagFetchScopeToProtocol(mTagFetchScope)); + pendingModification = Protocol::ModifySubscriptionCommand(); + ntfConnection->sendCommand(3, msubCmd); + break; } - break; - default: - qCWarning(AKONADICORE_LOG) << "Received an unexpected response on Notification stream: " << Protocol::debugString(command); - break; - } - } else { - switch (command->type()) { - case Protocol::Command::ItemChangeNotification: - case Protocol::Command::CollectionChangeNotification: - case Protocol::Command::TagChangeNotification: - case Protocol::Command::RelationChangeNotification: - case Protocol::Command::SubscriptionChangeNotification: - case Protocol::Command::DebugChangeNotification: - slotNotify(command.staticCast()); - break; - default: - qCWarning(AKONADICORE_LOG) << "Received an unexpected message on Notification stream:" << Protocol::debugString(command); - break; + case Protocol::Command::ModifySubscription: + // TODO: Handle errors + if (!monitorReady) { + monitorReady = true; + Q_EMIT q_ptr->monitorReady(); + } + break; + + default: + qCWarning(AKONADICORE_LOG) << "Received an unexpected response on Notification stream: " << Protocol::debugString(command); + break; + } + } else { + switch (command->type()) { + case Protocol::Command::ItemChangeNotification: + case Protocol::Command::CollectionChangeNotification: + case Protocol::Command::TagChangeNotification: + case Protocol::Command::RelationChangeNotification: + case Protocol::Command::SubscriptionChangeNotification: + case Protocol::Command::DebugChangeNotification: + slotNotify(command.staticCast()); + break; + default: + qCWarning(AKONADICORE_LOG) << "Received an unexpected message on Notification stream:" << Protocol::debugString(command); + break; + } } + + lock.relock(); } + notify.unblock(); + lock.unlock(); } /* @@ -898,7 +998,8 @@ // Note that this code is not used in a ChangeRecorder (pipelineSize==0) while (pipeline.size() < pipelineSize() && !pendingNotifications.isEmpty()) { const auto msg = pendingNotifications.dequeue(); - if (ensureDataAvailable(msg) && pipeline.isEmpty()) { + const bool avail = ensureDataAvailable(msg); + if (avail && pipeline.isEmpty()) { emitNotification(msg); } else { pipeline.enqueue(msg); @@ -952,78 +1053,14 @@ removedTags = tagCache->retrieve(msg.removedTags().toList()); } - auto msgItems = msg.items(); Item::List its = items; - QMutableVectorIterator iter(its); - while (iter.hasNext()) { - Item it = iter.next(); - if (it.isValid()) { - const auto msgItem = std::find_if(msgItems.begin(), msgItems.end(), - [&it](const Protocol::ChangeNotification::Item &i) { - return i.id == it.id(); - }); - if (msg.operation() == Protocol::ItemChangeNotification::Remove) { - it.setRemoteId(msgItem->remoteId); - it.setRemoteRevision(msgItem->remoteRevision); - it.setMimeType(msgItem->mimeType); - } else if (msg.operation() == Protocol::ItemChangeNotification::Move) { - // For moves we remove the RID from the PimItemTable to prevent - // RID conflict during merge (see T3904 in Phab), so restore the - // RID from notification. - // TODO: Should we do this for all items with empty RID? Right now - // I only know about this usecase. - it.setRemoteId(msgItem->remoteId); - } - - if (!it.parentCollection().isValid()) { - if (msg.operation() == Protocol::ItemChangeNotification::Move) { - it.setParentCollection(colDest); - } else { - it.setParentCollection(col); - } - } else { - // item has a valid parent collection, most likely due to retrieved ancestors - // still, collection might contain extra info, so inject that - if (it.parentCollection() == col) { - const Collection oldParent = it.parentCollection(); - if (oldParent.parentCollection().isValid() && !col.parentCollection().isValid()) { - col.setParentCollection(oldParent.parentCollection()); // preserve ancestor chain - } - it.setParentCollection(col); - } else { - // If one client does a modify followed by a move we have to make sure that the - // AgentBase::itemChanged() in another client always sees the parent collection - // of the item before it has been moved. - if (msg.operation() != Protocol::ItemChangeNotification::Move) { - it.setParentCollection(col); - } else { - it.setParentCollection(colDest); - } - } - } - iter.setValue(it); - msgItems.erase(msgItem); - } else { - // remove the invalid item - iter.remove(); - } - } - - its.reserve(its.size() + msgItems.size()); - // Now reconstruct any items there were left in msgItems - Q_FOREACH (const Protocol::ItemChangeNotification::Item &msgItem, msgItems) { - Item it(msgItem.id); - it.setRemoteId(msgItem.remoteId); - it.setRemoteRevision(msgItem.remoteRevision); - it.setMimeType(msgItem.mimeType); + for (auto it = its.begin(), end = its.end(); it != end; ++it) { if (msg.operation() == Protocol::ItemChangeNotification::Move) { - it.setParentCollection(colDest); + it->setParentCollection(colDest); } else { - it.setParentCollection(col); + it->setParentCollection(col); } - its << it; } - bool handled = false; switch (msg.operation()) { case Protocol::ItemChangeNotification::Add: @@ -1121,18 +1158,16 @@ } Collection collection = col; - if (!collection.isValid() || msg.operation() == Protocol::CollectionChangeNotification::Remove) { - collection = Collection(msg.id()); - collection.setResource(QString::fromUtf8(msg.resource())); - collection.setRemoteId(msg.remoteId()); + Q_ASSERT(collection.isValid()); + if (!collection.isValid()) { + qCWarning(AKONADICORE_LOG) << "Failed to get valid Collection for a Collection change!"; + return true; // prevent Monitor disconnecting from a signal } - if (!collection.parentCollection().isValid()) { - if (msg.operation() == Protocol::CollectionChangeNotification::Move) { - collection.setParentCollection(destination); - } else { - collection.setParentCollection(parent); - } + if (msg.operation() == Protocol::CollectionChangeNotification::Move) { + collection.setParentCollection(destination); + } else { + collection.setParentCollection(parent); } switch (msg.operation()) { @@ -1187,37 +1222,25 @@ bool MonitorPrivate::emitTagNotification(const Protocol::TagChangeNotification &msg, const Tag &tag) { - Tag validTag; - if (msg.operation() == Protocol::TagChangeNotification::Remove) { - //In case of a removed signal the cache entry was already invalidated, and we therefore received an empty list of tags - validTag = Tag(msg.id()); - validTag.setRemoteId(msg.remoteId().toLatin1()); - } else { - validTag = tag; - } - - if (!validTag.isValid()) { - return false; - } - + Q_UNUSED(msg); switch (msg.operation()) { case Protocol::TagChangeNotification::Add: if (q_ptr->receivers(SIGNAL(tagAdded(Akonadi::Tag))) == 0) { return false; } - Q_EMIT q_ptr->tagAdded(validTag); + Q_EMIT q_ptr->tagAdded(tag); return true; case Protocol::TagChangeNotification::Modify: if (q_ptr->receivers(SIGNAL(tagChanged(Akonadi::Tag))) == 0) { return false; } - Q_EMIT q_ptr->tagChanged(validTag); + Q_EMIT q_ptr->tagChanged(tag); return true; case Protocol::TagChangeNotification::Remove: if (q_ptr->receivers(SIGNAL(tagRemoved(Akonadi::Tag))) == 0) { return false; } - Q_EMIT q_ptr->tagRemoved(validTag); + Q_EMIT q_ptr->tagRemoved(tag); return true; default: qCDebug(AKONADICORE_LOG) << "Unknown operation type" << msg.operation() << "in tag change notification"; @@ -1313,10 +1336,10 @@ case Protocol::CollectionChangeNotification::Modify: case Protocol::CollectionChangeNotification::Move: case Protocol::CollectionChangeNotification::Subscribe: - collectionCache->update(colNtf.id(), mCollectionFetchScope); + collectionCache->update(colNtf.collection().id(), mCollectionFetchScope); break; case Protocol::CollectionChangeNotification::Remove: - collectionCache->invalidate(colNtf.id()); + collectionCache->invalidate(colNtf.collection().id()); break; default: break; @@ -1343,10 +1366,10 @@ const auto &tagNtf = Protocol::cmdCast(msg); switch (tagNtf.operation()) { case Protocol::TagChangeNotification::Modify: - tagCache->update({ tagNtf.id() }, mTagFetchScope); + tagCache->update({ tagNtf.tag().id() }, mTagFetchScope); break; case Protocol::TagChangeNotification::Remove: - tagCache->invalidate({ tagNtf.id() }); + tagCache->invalidate({ tagNtf.tag().id() }); break; default: break; @@ -1428,4 +1451,26 @@ } } +Protocol::ModifySubscriptionCommand::ChangeType MonitorPrivate::monitorTypeToProtocol(Monitor::Type type) +{ + switch (type) { + case Monitor::Collections: + return Protocol::ModifySubscriptionCommand::CollectionChanges; + case Monitor::Items: + return Protocol::ModifySubscriptionCommand::ItemChanges; + case Monitor::Tags: + return Protocol::ModifySubscriptionCommand::TagChanges; + case Monitor::Relations: + return Protocol::ModifySubscriptionCommand::RelationChanges; + case Monitor::Subscribers: + return Protocol::ModifySubscriptionCommand::SubscriptionChanges; + case Monitor::Notifications: + return Protocol::ModifySubscriptionCommand::ChangeNotifications; + default: + Q_ASSERT(false); + return Protocol::ModifySubscriptionCommand::NoType; + } +} + + // @endcond diff --git a/src/core/newmailnotifierattribute.h b/src/core/newmailnotifierattribute.h --- a/src/core/newmailnotifierattribute.h +++ b/src/core/newmailnotifierattribute.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2013-2017 Montel Laurent + Copyright (c) 2013-2018 Montel Laurent 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 @@ -31,7 +31,7 @@ { public: NewMailNotifierAttribute(); - ~NewMailNotifierAttribute(); + ~NewMailNotifierAttribute() override; /* reimpl */ NewMailNotifierAttribute *clone() const override; diff --git a/src/core/newmailnotifierattribute.cpp b/src/core/newmailnotifierattribute.cpp --- a/src/core/newmailnotifierattribute.cpp +++ b/src/core/newmailnotifierattribute.cpp @@ -1,5 +1,5 @@ /* - Copyright (c) 2013-2017 Montel Laurent + Copyright (c) 2013-2018 Montel Laurent 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 diff --git a/src/core/notificationsubscriber.h b/src/core/notificationsubscriber.h --- a/src/core/notificationsubscriber.h +++ b/src/core/notificationsubscriber.h @@ -72,6 +72,15 @@ bool isExclusive() const; void setIsExclusive(bool isExclusive); + ItemFetchScope itemFetchScope() const; + void setItemFetchScope(const ItemFetchScope &itemFetchScope); + + CollectionFetchScope collectionFetchScope() const; + void setCollectionFetchScope(const CollectionFetchScope &collectionFetchScope); + + TagFetchScope tagFetchScope() const; + void setTagFetchScope(const TagFetchScope &tagFetchScope); + private: class Private; QSharedDataPointer d; diff --git a/src/core/notificationsubscriber.cpp b/src/core/notificationsubscriber.cpp --- a/src/core/notificationsubscriber.cpp +++ b/src/core/notificationsubscriber.cpp @@ -18,6 +18,9 @@ */ #include "notificationsubscriber.h" +#include "itemfetchscope.h" +#include "collectionfetchscope.h" +#include "tagfetchscope.h" namespace Akonadi { @@ -41,6 +44,9 @@ , mimeTypes(other.mimeTypes) , resources(other.resources) , ignoredSessions(other.ignoredSessions) + , itemFetchScope(other.itemFetchScope) + , collectionFetchScope(other.collectionFetchScope) + , tagFetchScope(other.tagFetchScope) , isAllMonitored(other.isAllMonitored) , isExclusive(other.isExclusive) {} @@ -54,6 +60,9 @@ QSet mimeTypes; QSet resources; QSet ignoredSessions; + ItemFetchScope itemFetchScope; + CollectionFetchScope collectionFetchScope; + TagFetchScope tagFetchScope; bool isAllMonitored; bool isExclusive; }; @@ -196,3 +205,33 @@ { d->isExclusive = isExclusive; } + +ItemFetchScope NotificationSubscriber::itemFetchScope() const +{ + return d->itemFetchScope; +} + +void NotificationSubscriber::setItemFetchScope(const ItemFetchScope &itemFetchScope) +{ + d->itemFetchScope = itemFetchScope; +} + +CollectionFetchScope NotificationSubscriber::collectionFetchScope() const +{ + return d->collectionFetchScope; +} + +void NotificationSubscriber::setCollectionFetchScope(const CollectionFetchScope &fetchScope) +{ + d->collectionFetchScope = fetchScope; +} + +TagFetchScope NotificationSubscriber::tagFetchScope() const +{ + return d->tagFetchScope; +} + +void NotificationSubscriber::setTagFetchScope(const TagFetchScope &tagFetchScope) +{ + d->tagFetchScope = tagFetchScope; +} diff --git a/src/core/partfetcher.h b/src/core/partfetcher.h --- a/src/core/partfetcher.h +++ b/src/core/partfetcher.h @@ -87,7 +87,7 @@ /** * Destroys the part fetcher. */ - virtual ~PartFetcher(); + ~PartFetcher() override; /** * Starts the fetch operation. diff --git a/src/core/pastehelper.cpp b/src/core/pastehelper.cpp --- a/src/core/pastehelper.cpp +++ b/src/core/pastehelper.cpp @@ -65,21 +65,21 @@ void runCollectionsActions(); private: - Qt::DropAction mAction; Akonadi::Item::List mItems; Akonadi::Collection::List mCollections; Akonadi::Collection mDestCollection; + Qt::DropAction mAction; }; PasteHelperJob::PasteHelperJob(Qt::DropAction action, const Item::List &items, const Collection::List &collections, const Collection &destination, QObject *parent) : TransactionSequence(parent) - , mAction(action) , mItems(items) , mCollections(collections) , mDestCollection(destination) + , mAction(action) { //FIXME: The below code disables transactions in otder to avoid data loss due to nested //transactions (copy and colcopy in the server doesn't see the items retrieved into the cache and copies empty payloads). diff --git a/src/core/persistentsearchattribute.h b/src/core/persistentsearchattribute.h --- a/src/core/persistentsearchattribute.h +++ b/src/core/persistentsearchattribute.h @@ -83,7 +83,7 @@ /** * Destroys the persistent search attribute. */ - ~PersistentSearchAttribute(); + ~PersistentSearchAttribute() override; /** * Returns the query string used for this search. diff --git a/src/core/persistentsearchattribute.cpp b/src/core/persistentsearchattribute.cpp --- a/src/core/persistentsearchattribute.cpp +++ b/src/core/persistentsearchattribute.cpp @@ -148,24 +148,24 @@ const int listSize(l.size()); for (int i = 0; i < listSize; ++i) { const QByteArray key = l.at(i); - if (key == "QUERYLANGUAGE") { + if (key == QByteArrayLiteral("QUERYLANGUAGE")) { // Skip the value ++i; - } else if (key == "QUERYSTRING") { + } else if (key == QByteArrayLiteral("QUERYSTRING")) { d->queryString = QString::fromUtf8(l.at(i + 1)); ++i; - } else if (key == "QUERYCOLLECTIONS") { + } else if (key == QByteArrayLiteral("QUERYCOLLECTIONS")) { QList ids; ImapParser::parseParenthesizedList(l.at(i + 1), ids); d->queryCollections.clear(); d->queryCollections.reserve(ids.count()); for (const QByteArray &id : qAsConst(ids)) { d->queryCollections << id.toLongLong(); } ++i; - } else if (key == "REMOTE") { + } else if (key == QByteArrayLiteral("REMOTE")) { d->remote = true; - } else if (key == "RECURSIVE") { + } else if (key == QByteArrayLiteral("RECURSIVE")) { d->recursive = true; } } diff --git a/src/core/pluginloader.cpp b/src/core/pluginloader.cpp --- a/src/core/pluginloader.cpp +++ b/src/core/pluginloader.cpp @@ -19,6 +19,7 @@ #include "pluginloader_p.h" #include "akonadicore_debug.h" +#include #include #include #include @@ -118,7 +119,7 @@ void PluginLoader::scan() { - const QStringList dirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("akonadi/plugins/serializer/"), QStandardPaths::LocateDirectory); + const auto dirs = StandardDirs::locateAllResourceDirs(QStringLiteral("akonadi/plugins/serializer/")); for (const QString &dir : dirs) { const QStringList fileNames = QDir(dir).entryList(QStringList() << QStringLiteral("*.desktop")); for (const QString &file : fileNames) { diff --git a/src/core/pop3resourceattribute.h b/src/core/pop3resourceattribute.h --- a/src/core/pop3resourceattribute.h +++ b/src/core/pop3resourceattribute.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2013-2017 Montel Laurent + Copyright (c) 2013-2018 Montel Laurent 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 @@ -31,7 +31,7 @@ { public: Pop3ResourceAttribute(); - ~Pop3ResourceAttribute(); + ~Pop3ResourceAttribute() override; /* reimpl */ Pop3ResourceAttribute *clone() const override; diff --git a/src/core/pop3resourceattribute.cpp b/src/core/pop3resourceattribute.cpp --- a/src/core/pop3resourceattribute.cpp +++ b/src/core/pop3resourceattribute.cpp @@ -1,5 +1,5 @@ /* - Copyright (c) 2013-2017 Montel Laurent + Copyright (c) 2013-2018 Montel Laurent 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 diff --git a/src/core/protocolhelper.cpp b/src/core/protocolhelper.cpp --- a/src/core/protocolhelper.cpp +++ b/src/core/protocolhelper.cpp @@ -31,7 +31,6 @@ #include "persistentsearchattribute.h" #include "private/protocol_p.h" -#include "private/xdgbasedirs_p.h" #include "private/externalpartstorage_p.h" #include @@ -246,6 +245,18 @@ return collection; } +Tag ProtocolHelper::parseTag(const Protocol::FetchTagsResponse &data) +{ + Tag tag(data.id()); + tag.setRemoteId(data.remoteId()); + tag.setGid(data.gid()); + tag.setType(data.type()); + tag.setParent(Tag(data.parentId())); + parseAttributes(data.attributes(), &tag); + + return tag; +} + QByteArray ProtocolHelper::encodePartIdentifier(PartNamespace ns, const QByteArray &label) { switch (ns) { @@ -322,9 +333,9 @@ return Scope(QVector({ Scope::HRID(item.id(), item.remoteId()) }) + hierarchicalRidToScope(item.parentCollection()).hridChain()); } -Protocol::FetchScope ProtocolHelper::itemFetchScopeToProtocol(const ItemFetchScope &fetchScope) +Protocol::ItemFetchScope ProtocolHelper::itemFetchScopeToProtocol(const ItemFetchScope &fetchScope) { - Protocol::FetchScope fs; + Protocol::ItemFetchScope fs; QVector parts; parts.reserve(fetchScope.payloadParts().size() + fetchScope.attributes().size()); Q_FOREACH (const QByteArray &part, fetchScope.payloadParts()) { @@ -336,41 +347,41 @@ fs.setRequestedParts(parts); // The default scope - fs.setFetch(Protocol::FetchScope::Flags | - Protocol::FetchScope::Size | - Protocol::FetchScope::RemoteID | - Protocol::FetchScope::RemoteRevision | - Protocol::FetchScope::MTime); - - fs.setFetch(Protocol::FetchScope::FullPayload, fetchScope.fullPayload()); - fs.setFetch(Protocol::FetchScope::AllAttributes, fetchScope.allAttributes()); - fs.setFetch(Protocol::FetchScope::CacheOnly, fetchScope.cacheOnly()); - fs.setFetch(Protocol::FetchScope::CheckCachedPayloadPartsOnly, fetchScope.checkForCachedPayloadPartsOnly()); - fs.setFetch(Protocol::FetchScope::IgnoreErrors, fetchScope.ignoreRetrievalErrors()); - if (fetchScope.ancestorRetrieval() != ItemFetchScope::None) { - switch (fetchScope.ancestorRetrieval()) { - case ItemFetchScope::Parent: - fs.setAncestorDepth(Protocol::FetchScope::ParentAncestor); - break; - case ItemFetchScope::All: - fs.setAncestorDepth(Protocol::FetchScope::AllAncestors); - break; - default: - Q_ASSERT(false); - } - } else { - fs.setAncestorDepth(Protocol::FetchScope::NoAncestor); + fs.setFetch(Protocol::ItemFetchScope::Flags | + Protocol::ItemFetchScope::Size | + Protocol::ItemFetchScope::RemoteID | + Protocol::ItemFetchScope::RemoteRevision | + Protocol::ItemFetchScope::MTime); + + fs.setFetch(Protocol::ItemFetchScope::FullPayload, fetchScope.fullPayload()); + fs.setFetch(Protocol::ItemFetchScope::AllAttributes, fetchScope.allAttributes()); + fs.setFetch(Protocol::ItemFetchScope::CacheOnly, fetchScope.cacheOnly()); + fs.setFetch(Protocol::ItemFetchScope::CheckCachedPayloadPartsOnly, fetchScope.checkForCachedPayloadPartsOnly()); + fs.setFetch(Protocol::ItemFetchScope::IgnoreErrors, fetchScope.ignoreRetrievalErrors()); + switch (fetchScope.ancestorRetrieval()) { + case ItemFetchScope::Parent: + fs.setAncestorDepth(Protocol::ItemFetchScope::ParentAncestor); + break; + case ItemFetchScope::All: + fs.setAncestorDepth(Protocol::ItemFetchScope::AllAncestors); + break; + case ItemFetchScope::None: + fs.setAncestorDepth(Protocol::ItemFetchScope::NoAncestor); + break; + default: + Q_ASSERT(false); + break; } if (fetchScope.fetchChangedSince().isValid()) { fs.setChangedSince(fetchScope.fetchChangedSince()); } - fs.setFetch(Protocol::FetchScope::RemoteID, fetchScope.fetchRemoteIdentification()); - fs.setFetch(Protocol::FetchScope::RemoteRevision, fetchScope.fetchRemoteIdentification()); - fs.setFetch(Protocol::FetchScope::GID, fetchScope.fetchGid()); + fs.setFetch(Protocol::ItemFetchScope::RemoteID, fetchScope.fetchRemoteIdentification()); + fs.setFetch(Protocol::ItemFetchScope::RemoteRevision, fetchScope.fetchRemoteIdentification()); + fs.setFetch(Protocol::ItemFetchScope::GID, fetchScope.fetchGid()); if (fetchScope.fetchTags()) { - fs.setFetch(Protocol::FetchScope::Tags); + fs.setFetch(Protocol::ItemFetchScope::Tags); if (!fetchScope.tagFetchScope().fetchIdOnly()) { if (fetchScope.tagFetchScope().attributes().isEmpty()) { fs.setTagFetchScope({ "ALL" }); @@ -380,13 +391,198 @@ } } - fs.setFetch(Protocol::FetchScope::VirtReferences, fetchScope.fetchVirtualReferences()); - fs.setFetch(Protocol::FetchScope::MTime, fetchScope.fetchModificationTime()); - fs.setFetch(Protocol::FetchScope::Relations, fetchScope.fetchRelations()); + fs.setFetch(Protocol::ItemFetchScope::VirtReferences, fetchScope.fetchVirtualReferences()); + fs.setFetch(Protocol::ItemFetchScope::MTime, fetchScope.fetchModificationTime()); + fs.setFetch(Protocol::ItemFetchScope::Relations, fetchScope.fetchRelations()); return fs; } +ItemFetchScope ProtocolHelper::parseItemFetchScope(const Protocol::ItemFetchScope &fetchScope) +{ + ItemFetchScope ifs; + Q_FOREACH (const auto &part, fetchScope.requestedParts()) { + if (part.startsWith("PLD:")) { + ifs.fetchPayloadPart(part.mid(4), true); + } else if (part.startsWith("ATR:")) { + ifs.fetchAttribute(part.mid(4), true); + } + } + + if (fetchScope.fetch(Protocol::ItemFetchScope::FullPayload)) { + ifs.fetchFullPayload(true); + } + if (fetchScope.fetch(Protocol::ItemFetchScope::AllAttributes)) { + ifs.fetchAllAttributes(true); + } + if (fetchScope.fetch(Protocol::ItemFetchScope::CacheOnly)) { + ifs.setCacheOnly(true); + } + if (fetchScope.fetch(Protocol::ItemFetchScope::CheckCachedPayloadPartsOnly)) { + ifs.setCheckForCachedPayloadPartsOnly(true); + } + if (fetchScope.fetch(Protocol::ItemFetchScope::IgnoreErrors)) { + ifs.setIgnoreRetrievalErrors(true); + } + switch (fetchScope.ancestorDepth()) { + case Protocol::Ancestor::ParentAncestor: + ifs.setAncestorRetrieval(ItemFetchScope::Parent); + break; + case Protocol::Ancestor::AllAncestors: + ifs.setAncestorRetrieval(ItemFetchScope::All); + break; + default: + ifs.setAncestorRetrieval(ItemFetchScope::None); + break; + } + if (fetchScope.changedSince().isValid()) { + ifs.setFetchChangedSince(fetchScope.changedSince()); + } + if (fetchScope.fetch(Protocol::ItemFetchScope::RemoteID) || fetchScope.fetch(Protocol::ItemFetchScope::RemoteRevision)) { + ifs.setFetchRemoteIdentification(true); + } + if (fetchScope.fetch(Protocol::ItemFetchScope::GID)) { + ifs.setFetchGid(true); + } + if (fetchScope.fetch(Protocol::ItemFetchScope::Tags)) { + ifs.setFetchTags(true); + const auto tfs = fetchScope.tagFetchScope(); + if (tfs.isEmpty()) { + ifs.tagFetchScope().setFetchIdOnly(true); + } else if (QSet({ "ALL" }) != tfs) { + for (const auto &attr : tfs) { + ifs.tagFetchScope().fetchAttribute(attr, true); + } + } + } + if (fetchScope.fetch(Protocol::ItemFetchScope::VirtReferences)) { + ifs.setFetchVirtualReferences(true); + } + if (fetchScope.fetch(Protocol::ItemFetchScope::MTime)) { + ifs.setFetchModificationTime(true); + } + if (fetchScope.fetch(Protocol::ItemFetchScope::Relations)) { + ifs.setFetchRelations(true); + } + + return ifs; +} + +Protocol::CollectionFetchScope ProtocolHelper::collectionFetchScopeToProtocol(const CollectionFetchScope &fetchScope) +{ + Protocol::CollectionFetchScope cfs; + switch (fetchScope.listFilter()) { + case CollectionFetchScope::NoFilter: + cfs.setListFilter(Protocol::CollectionFetchScope::NoFilter); + break; + case CollectionFetchScope::Display: + cfs.setListFilter(Protocol::CollectionFetchScope::Display); + break; + case CollectionFetchScope::Sync: + cfs.setListFilter(Protocol::CollectionFetchScope::Sync); + break; + case CollectionFetchScope::Index: + cfs.setListFilter(Protocol::CollectionFetchScope::Index); + break; + case CollectionFetchScope::Enabled: + cfs.setListFilter(Protocol::CollectionFetchScope::Enabled); + break; + } + cfs.setIncludeStatistics(fetchScope.includeStatistics()); + cfs.setResource(fetchScope.resource()); + cfs.setContentMimeTypes(fetchScope.contentMimeTypes()); + cfs.setAttributes(fetchScope.attributes()); + cfs.setFetchIdOnly(fetchScope.fetchIdOnly()); + switch (fetchScope.ancestorRetrieval()) { + case CollectionFetchScope::None: + cfs.setAncestorRetrieval(Protocol::CollectionFetchScope::None); + break; + case CollectionFetchScope::Parent: + cfs.setAncestorRetrieval(Protocol::CollectionFetchScope::Parent); + break; + case CollectionFetchScope::All: + cfs.setAncestorRetrieval(Protocol::CollectionFetchScope::All); + break; + } + if (cfs.ancestorRetrieval() != Protocol::CollectionFetchScope::None) { + cfs.setAncestorAttributes(fetchScope.ancestorFetchScope().attributes()); + cfs.setAncestorFetchIdOnly(fetchScope.ancestorFetchScope().fetchIdOnly()); + } + cfs.setIgnoreRetrievalErrors(fetchScope.ignoreRetrievalErrors()); + + return cfs; +} + +CollectionFetchScope ProtocolHelper::parseCollectionFetchScope(const Protocol::CollectionFetchScope &fetchScope) +{ + CollectionFetchScope cfs; + switch (fetchScope.listFilter()) { + case Protocol::CollectionFetchScope::NoFilter: + cfs.setListFilter(CollectionFetchScope::NoFilter); + break; + case Protocol::CollectionFetchScope::Display: + cfs.setListFilter(CollectionFetchScope::Display); + break; + case Protocol::CollectionFetchScope::Sync: + cfs.setListFilter(CollectionFetchScope::Sync); + break; + case Protocol::CollectionFetchScope::Index: + cfs.setListFilter(CollectionFetchScope::Index); + break; + case Protocol::CollectionFetchScope::Enabled: + cfs.setListFilter(CollectionFetchScope::Enabled); + break; + } + cfs.setIncludeStatistics(fetchScope.includeStatistics()); + cfs.setResource(fetchScope.resource()); + cfs.setContentMimeTypes(fetchScope.contentMimeTypes()); + switch (fetchScope.ancestorRetrieval()) { + case Protocol::CollectionFetchScope::None: + cfs.setAncestorRetrieval(CollectionFetchScope::None); + break; + case Protocol::CollectionFetchScope::Parent: + cfs.setAncestorRetrieval(CollectionFetchScope::Parent); + break; + case Protocol::CollectionFetchScope::All: + cfs.setAncestorRetrieval(CollectionFetchScope::All); + break; + } + if (cfs.ancestorRetrieval() != CollectionFetchScope::None) { + cfs.ancestorFetchScope().setFetchIdOnly(fetchScope.ancestorFetchIdOnly()); + const auto attrs = fetchScope.ancestorAttributes(); + for (const auto attr : attrs) { + cfs.ancestorFetchScope().fetchAttribute(attr, true); + } + } + const auto attrs = fetchScope.attributes(); + for (const auto &attr : attrs) { + cfs.fetchAttribute(attr, true); + } + cfs.setFetchIdOnly(fetchScope.fetchIdOnly()); + cfs.setIgnoreRetrievalErrors(fetchScope.ignoreRetrievalErrors()); + + return cfs; +} + +Protocol::TagFetchScope ProtocolHelper::tagFetchScopeToProtocol(const TagFetchScope &fetchScope) +{ + Protocol::TagFetchScope tfs; + tfs.setFetchIdOnly(fetchScope.fetchIdOnly()); + tfs.setAttributes(fetchScope.attributes()); + return tfs; +} + +TagFetchScope ProtocolHelper::parseTagFetchScope(const Protocol::TagFetchScope &fetchScope) +{ + TagFetchScope tfs; + tfs.setFetchIdOnly(fetchScope.fetchIdOnly()); + const auto attrs = fetchScope.attributes(); + for (const auto &attr : attrs) { + tfs.fetchAttribute(attr, true); + } + return tfs; +} + static Item::Flags convertFlags(const QVector &flags, ProtocolHelperValuePool *valuePool) { #if __cplusplus >= 201103L || defined(__GNUC__) || defined(__clang__) @@ -472,7 +668,6 @@ item.setSize(data.size()); item.setModificationTime(data.mTime()); parseAncestorsCached(data.ancestors(), &item, data.parentId(), valuePool); - Q_FOREACH (const Protocol::StreamPayloadResponse &part, data.parts()) { ProtocolHelper::PartNamespace ns; const QByteArray plainKey = decodePartIdentifier(part.payloadName(), ns); diff --git a/src/core/protocolhelper_p.h b/src/core/protocolhelper_p.h --- a/src/core/protocolhelper_p.h +++ b/src/core/protocolhelper_p.h @@ -25,6 +25,7 @@ #include "collectionutils.h" #include "item.h" #include "itemfetchscope.h" +#include "collectionfetchscope.h" #include "sharedvaluepool_p.h" #include "tag.h" @@ -121,6 +122,8 @@ */ static Collection parseCollection(const Protocol::FetchCollectionsResponse &data, bool requireParent = true); + static Tag parseTag(const Protocol::FetchTagsResponse &data); + static void parseAttributes(const Protocol::Attributes &attributes, Item *item); static void parseAttributes(const Protocol::Attributes &attributes, Collection *collection); static void parseAttributes(const Protocol::Attributes &attributes, Tag *entity); @@ -222,12 +225,14 @@ /** Converts a given ItemFetchScope object into a protocol representation. */ - static Protocol::FetchScope itemFetchScopeToProtocol(const ItemFetchScope &fetchScope); + static Protocol::ItemFetchScope itemFetchScopeToProtocol(const ItemFetchScope &fetchScope); + static ItemFetchScope parseItemFetchScope(const Protocol::ItemFetchScope &fetchScope); - /** - Converts a given TagFetchScope object into a protocol representation. - */ - static QVector tagFetchScopeToProtocol(const TagFetchScope &fetchScope); + static Protocol::CollectionFetchScope collectionFetchScopeToProtocol(const CollectionFetchScope &fetchScope); + static CollectionFetchScope parseCollectionFetchScope(const Protocol::CollectionFetchScope &fetchScope); + + static Protocol::TagFetchScope tagFetchScopeToProtocol(const TagFetchScope &fetchScope); + static TagFetchScope parseTagFetchScope(const Protocol::TagFetchScope &fetchScope); /** Parses a single line from an item fetch job result into an Item object. diff --git a/src/core/qtest_akonadi.h b/src/core/qtest_akonadi.h --- a/src/core/qtest_akonadi.h +++ b/src/core/qtest_akonadi.h @@ -71,12 +71,10 @@ */ void checkTestIsIsolated() { - Q_ASSERT_X(!qEnvironmentVariableIsEmpty("TESTRUNNER_DB_ENVIRONMENT"), - "AkonadiTest::checkTestIsIsolated", - "This test must be run using ctest, in order to use the testrunner environment. Aborting, to avoid messing up your real akonadi"); - Q_ASSERT_X(qgetenv("XDG_DATA_HOME").contains("testrunner"), - "AkonadiTest::checkTestIsIsolated", - "Did you forget to run the test using QTEST_AKONADIMAIN?"); + if (qEnvironmentVariableIsEmpty("TESTRUNNER_DB_ENVIRONMENT")) + qFatal("This test must be run using ctest, in order to use the testrunner environment. Aborting, to avoid messing up your real akonadi"); + if (!qgetenv("XDG_DATA_HOME").contains("testrunner")) + qFatal("Did you forget to run the test using QTEST_AKONADIMAIN?"); } /** diff --git a/src/core/relation.h b/src/core/relation.h --- a/src/core/relation.h +++ b/src/core/relation.h @@ -84,7 +84,7 @@ /** * Returns the identifier of the left side of the relation. */ - Item left() const; + Q_REQUIRED_RESULT Item left() const; /** * Sets the @p item of the right side of the relation. @@ -94,7 +94,7 @@ /** * Returns the identifier of the right side of the relation. */ - Item right() const; + Q_REQUIRED_RESULT Item right() const; /** * Sets the type of the relation. @@ -104,7 +104,7 @@ /** * Returns the type of the relation. */ - QByteArray type() const; + Q_REQUIRED_RESULT QByteArray type() const; /** * Sets the remote id of the relation. @@ -114,9 +114,9 @@ /** * Returns the remote id of the relation. */ - QByteArray remoteId() const; + Q_REQUIRED_RESULT QByteArray remoteId() const; - bool isValid() const; + Q_REQUIRED_RESULT bool isValid() const; private: struct Private; diff --git a/src/core/relationsync.h b/src/core/relationsync.h --- a/src/core/relationsync.h +++ b/src/core/relationsync.h @@ -32,7 +32,7 @@ Q_OBJECT public: explicit RelationSync(QObject *parent = nullptr); - virtual ~RelationSync(); + ~RelationSync() override; void setRemoteRelations(const Akonadi::Relation::List &relations); @@ -50,8 +50,8 @@ private: Akonadi::Relation::List mRemoteRelations; Akonadi::Relation::List mLocalRelations; - bool mRemoteRelationsSet; - bool mLocalRelationsFetched; + bool mRemoteRelationsSet = false; + bool mLocalRelationsFetched = false; }; } diff --git a/src/core/relationsync.cpp b/src/core/relationsync.cpp --- a/src/core/relationsync.cpp +++ b/src/core/relationsync.cpp @@ -35,9 +35,7 @@ using namespace Akonadi; RelationSync::RelationSync(QObject *parent) - : Job(parent), - mRemoteRelationsSet(false), - mLocalRelationsFetched(false) + : Job(parent) { } diff --git a/src/agentbase/resourcesettings.cpp b/src/core/remotelog.cpp copy from src/agentbase/resourcesettings.cpp copy to src/core/remotelog.cpp --- a/src/agentbase/resourcesettings.cpp +++ b/src/core/remotelog.cpp @@ -1,5 +1,5 @@ /* - Copyright (C) 2010-2017 Laurent Montel + Copyright (c) 2018 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 @@ -17,26 +17,22 @@ 02110-1301, USA. */ -#include "resourcesettings.h" +#include -using namespace Akonadi; +#include +#include +#include -ResourceSettings *ResourceSettings::mSelf = nullptr; +namespace { -ResourceSettings *ResourceSettings::self() -{ - if (!mSelf) { - mSelf = new ResourceSettings(); - mSelf->load(); - } +static const auto initRemoteLogger = []() { + qAddPreRoutine([]() { + // Initialize remote logging from event loop, this way applications like + // Akonadi Console or TestRunner have a chance to change AKONADI_INSTANCE + // before the RemoteLog class initialize + QTimer::singleShot(0, qApp, []() { akInitRemoteLog(); }); + }); + return true; +}(); - return mSelf; -} - -ResourceSettings::ResourceSettings() -{ -} - -ResourceSettings::~ResourceSettings() -{ } diff --git a/src/core/searchquery.h b/src/core/searchquery.h --- a/src/core/searchquery.h +++ b/src/core/searchquery.h @@ -67,24 +67,24 @@ ~SearchTerm(); SearchTerm &operator=(const SearchTerm &other); - bool operator==(const SearchTerm &other) const; + Q_REQUIRED_RESULT bool operator==(const SearchTerm &other) const; - bool isNull() const; + Q_REQUIRED_RESULT bool isNull() const; /** * Returns key of this end term. */ - QString key() const; + Q_REQUIRED_RESULT QString key() const; /** * Returns value of this end term. */ - QVariant value() const; + Q_REQUIRED_RESULT QVariant value() const; /** * Returns relation between key and value. */ - SearchTerm::Condition condition() const; + Q_REQUIRED_RESULT SearchTerm::Condition condition() const; /** * Adds a new subterm to this term. @@ -98,12 +98,12 @@ /** * Returns all subterms, or an empty list if this is an end term. */ - QList subTerms() const; + Q_REQUIRED_RESULT QList subTerms() const; /** * Returns relation in which all subterms are. */ - SearchTerm::Relation relation() const; + Q_REQUIRED_RESULT SearchTerm::Relation relation() const; /** * Sets whether the entire term is negated. @@ -113,7 +113,7 @@ /** * Returns whether the entire term is negated. */ - bool isNegated() const; + Q_REQUIRED_RESULT bool isNegated() const; private: class Private; diff --git a/src/core/searchquery.cpp b/src/core/searchquery.cpp --- a/src/core/searchquery.cpp +++ b/src/core/searchquery.cpp @@ -33,7 +33,6 @@ : QSharedData() , condition(SearchTerm::CondEqual) , relation(SearchTerm::RelAnd) - , isNegated(false) { } @@ -63,7 +62,7 @@ Condition condition; Relation relation; QList terms; - bool isNegated; + bool isNegated = false; }; class SearchQuery::Private : public QSharedData diff --git a/src/core/servermanager.h b/src/core/servermanager.h --- a/src/core/servermanager.h +++ b/src/core/servermanager.h @@ -87,13 +87,13 @@ * see state(). * @see state() */ - static bool isRunning(); + Q_REQUIRED_RESULT static bool isRunning(); /** * Returns the state of the server. * @since 4.5 */ - static State state(); + Q_REQUIRED_RESULT static State state(); /** * Returns the reason why the Server is broken, if known. @@ -104,21 +104,21 @@ * * @since 5.6 */ - static QString brokenReason(); + Q_REQUIRED_RESULT static QString brokenReason(); /** * Returns the identifier of the Akonadi instance we are connected to. This is usually * an empty string (representing the default instance), unless you have explicitly set * the AKONADI_INSTANCE environment variable to connect to a different one. * @since 4.10 */ - static QString instanceIdentifier(); + Q_REQUIRED_RESULT static QString instanceIdentifier(); /** * Returns @c true if we are connected to a non-default Akonadi server instance. * @since 4.10 */ - static bool hasInstanceIdentifier(); + Q_REQUIRED_RESULT static bool hasInstanceIdentifier(); /** * Types of known D-Bus services. @@ -157,15 +157,15 @@ * @param identifier the agent identifier to include in the D-Bus name * @since 4.10 */ - static QString agentServiceName(ServiceAgentType agentType, const QString &identifier); + Q_REQUIRED_RESULT static QString agentServiceName(ServiceAgentType agentType, const QString &identifier); /** * Adds the multi-instance namespace to @p string if required (with '_' as separator). * Use whenever a multi-instance safe name is required (configfiles, identifiers, ...). * @param string the string to adapt * @since 4.10 */ - static QString addNamespace(const QString &string); + Q_REQUIRED_RESULT static QString addNamespace(const QString &string); /** * Returns the singleton instance of this class, for connecting to its @@ -181,13 +181,13 @@ * Returns absolute path to akonadiserverrc file with Akonadi server * configuration. */ - static QString serverConfigFilePath(OpenMode openMode); + Q_REQUIRED_RESULT static QString serverConfigFilePath(OpenMode openMode); /** * Returns absolute path to configuration file of an agent identified by * given @p identifier. */ - static QString agentConfigFilePath(const QString &identifier); + Q_REQUIRED_RESULT static QString agentConfigFilePath(const QString &identifier); /** * Returns current Akonadi database generation identifier @@ -206,7 +206,7 @@ * * @since 5.4 */ - static uint generation(); + Q_REQUIRED_RESULT static uint generation(); Q_SIGNALS: /** diff --git a/src/core/servermanager.cpp b/src/core/servermanager.cpp --- a/src/core/servermanager.cpp +++ b/src/core/servermanager.cpp @@ -54,7 +54,6 @@ : instance(new ServerManager(this)) , mState(ServerManager::NotRunning) , mSafetyTimer(new QTimer) - , mFirstRunner(nullptr) { mState = instance->state(); mSafetyTimer->setSingleShot(true); @@ -106,12 +105,19 @@ } else if (state == ServerManager::NotRunning || state == ServerManager::Broken) { emit instance->stopped(); } - +#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) + if (state == ServerManager::Starting || state == ServerManager::Stopping) { + QMetaObject::invokeMethod(mSafetyTimer.data(), QOverload<>::of(&QTimer::start), Qt::QueuedConnection); // in case we are in a different thread + } else { + QMetaObject::invokeMethod(mSafetyTimer.data(), &QTimer::stop, Qt::QueuedConnection); // in case we are in a different thread + } +#else if (state == ServerManager::Starting || state == ServerManager::Stopping) { QMetaObject::invokeMethod(mSafetyTimer.data(), "start", Qt::QueuedConnection); // in case we are in a different thread } else { QMetaObject::invokeMethod(mSafetyTimer.data(), "stop", Qt::QueuedConnection); // in case we are in a different thread } +#endif } } @@ -122,12 +128,12 @@ } } - ServerManager *instance; + ServerManager *instance = nullptr; static int serverProtocolVersion; static uint generation; ServerManager::State mState; QScopedPointer mSafetyTimer; - Firstrun *mFirstRunner; + Firstrun *mFirstRunner = nullptr; static Internal::ClientType clientType; QString mBrokenReason; }; @@ -264,7 +270,7 @@ // besides the running server processes we also need at least one resource to be operational const AgentType::List agentTypes = AgentManager::self()->types(); for (const AgentType &type : agentTypes) { - if (type.capabilities().contains(QStringLiteral("Resource"))) { + if (type.capabilities().contains(QLatin1String("Resource"))) { return Running; } } @@ -347,24 +353,15 @@ QString ServerManager::serverConfigFilePath(OpenMode openMode) { - QString relPath; - if (hasInstanceIdentifier()) { - relPath = QStringLiteral("instance/%1").arg(ServerManager::instanceIdentifier()); - } - return XdgBaseDirs::akonadiServerConfigFile(openMode == Akonadi::ServerManager::ReadOnly - ? XdgBaseDirs::ReadOnly - : XdgBaseDirs::ReadWrite, - relPath); + return StandardDirs::serverConfigFile( + openMode == Akonadi::ServerManager::ReadOnly + ? StandardDirs::ReadOnly + : StandardDirs::ReadWrite); } QString ServerManager::agentConfigFilePath(const QString &identifier) { - QString fullRelPath = QStringLiteral("akonadi"); - if (hasInstanceIdentifier()) { - fullRelPath += QStringLiteral("/instance/%1").arg(ServerManager::instanceIdentifier()); - } - fullRelPath += QStringLiteral("agent_config_%1").arg(identifier); - return Akonadi::XdgBaseDirs::findResourceFile("config", fullRelPath); + return StandardDirs::agentConfigFile(identifier); } QString ServerManager::addNamespace(const QString &string) diff --git a/src/core/session.h b/src/core/session.h --- a/src/core/session.h +++ b/src/core/session.h @@ -25,6 +25,7 @@ class KJob; class FakeSession; +class FakeNotificationConnection; namespace Akonadi { @@ -91,7 +92,7 @@ /** * Returns the session identifier. */ - QByteArray sessionId() const; + Q_REQUIRED_RESULT QByteArray sessionId() const; /** * Returns the default session for this thread. @@ -129,12 +130,13 @@ //@cond PRIVATE SessionPrivate *const d; friend class ::FakeSession; + friend class ::FakeNotificationConnection; friend class ChangeNotificationDependenciesFactory; Q_PRIVATE_SLOT(d, void reconnect()) Q_PRIVATE_SLOT(d, void socketError(const QString &error)) Q_PRIVATE_SLOT(d, void socketDisconnected()) - Q_PRIVATE_SLOT(d, bool handleCommand(qint64 tag, const Akonadi::Protocol::CommandPtr &cmd)) + Q_PRIVATE_SLOT(d, bool handleCommands()) Q_PRIVATE_SLOT(d, void doStartNext()) Q_PRIVATE_SLOT(d, void jobDone(KJob *)) Q_PRIVATE_SLOT(d, void jobWriteFinished(Akonadi::Job *)) diff --git a/src/core/session.cpp b/src/core/session.cpp --- a/src/core/session.cpp +++ b/src/core/session.cpp @@ -38,6 +38,7 @@ #include #include #include +#include #include #include @@ -60,12 +61,10 @@ void SessionPrivate::reconnect() { if (!connection) { - connection = sessionThread()->createConnection(Connection::CommandConnection, sessionId); + connection = new Connection(Connection::CommandConnection, sessionId, &mCommandBuffer); + sessionThread()->addConnection(connection); mParent->connect(connection, &Connection::reconnected, mParent, &Session::reconnected, Qt::QueuedConnection); - mParent->connect(connection, SIGNAL(commandReceived(qint64,Akonadi::Protocol::CommandPtr)), - mParent, SLOT(handleCommand(qint64,Akonadi::Protocol::CommandPtr)), - Qt::QueuedConnection); mParent->connect(connection, SIGNAL(socketDisconnected()), mParent, SLOT(socketDisconnected()), Qt::QueuedConnection); mParent->connect(connection, SIGNAL(socketError(QString)), mParent, SLOT(socketError(QString)), @@ -75,11 +74,6 @@ connection->reconnect(); } -QString SessionPrivate::connectionFile() -{ - return StandardDirs::saveDir("config") + QStringLiteral("/akonadiconnectionrc"); -} - void SessionPrivate::socketError(const QString &error) { qCWarning(AKONADICORE_LOG) << "Socket error occurred:" << error; @@ -94,49 +88,52 @@ connected = false; } -bool SessionPrivate::handleCommand(qint64 tag, const Protocol::CommandPtr &cmd) +bool SessionPrivate::handleCommands() { - // Handle Hello response -> send Login - if (cmd->type() == Protocol::Command::Hello) { - const auto &hello = Protocol::cmdCast(cmd); - if (hello.isError()) { - qCWarning(AKONADICORE_LOG) << "Error when establishing connection with Akonadi server:" << hello.errorMessage(); - connection->closeConnection(); - QTimer::singleShot(1000, connection, &Connection::reconnect); - return false; + CommandBufferLocker lock(&mCommandBuffer); + CommandBufferNotifyBlocker notify(&mCommandBuffer); + while (!mCommandBuffer.isEmpty()) { + const auto command = mCommandBuffer.dequeue(); + lock.unlock(); + const auto cmd = command.command; + const auto tag = command.tag; + + // Handle Hello response -> send Login + if (cmd->type() == Protocol::Command::Hello) { + const auto &hello = Protocol::cmdCast(cmd); + if (hello.isError()) { + qCWarning(AKONADICORE_LOG) << "Error when establishing connection with Akonadi server:" << hello.errorMessage(); + connection->closeConnection(); + QTimer::singleShot(1000, connection, &Connection::reconnect); + return false; + } + + qCDebug(AKONADICORE_LOG) << "Connected to" << hello.serverName() << ", using protocol version" << hello.protocolVersion(); + qCDebug(AKONADICORE_LOG) << "Server generation:" << hello.generation(); + qCDebug(AKONADICORE_LOG) << "Server says:" << hello.message(); + // Version mismatch is handled in SessionPrivate::startJob() so that + // we can report the error out via KJob API + protocolVersion = hello.protocolVersion(); + Internal::setServerProtocolVersion(protocolVersion); + Internal::setGeneration(hello.generation()); + + sendCommand(nextTag(), Protocol::LoginCommandPtr::create(sessionId)); + } else if (cmd->type() == Protocol::Command::Login) { + const auto &login = Protocol::cmdCast(cmd); + if (login.isError()) { + qCWarning(AKONADICORE_LOG) << "Unable to login to Akonadi server:" << login.errorMessage(); + connection->closeConnection(); + QTimer::singleShot(1000, mParent, SLOT(reconnect())); + return false; + } + + connected = true; + startNext(); + } else if (currentJob) { + currentJob->d_ptr->handleResponse(tag, cmd); } - qCDebug(AKONADICORE_LOG) << "Connected to" << hello.serverName() << ", using protocol version" << hello.protocolVersion(); - qCDebug(AKONADICORE_LOG) << "Server generation:" << hello.generation(); - qCDebug(AKONADICORE_LOG) << "Server says:" << hello.message(); - // Version mismatch is handled in SessionPrivate::startJob() so that - // we can report the error out via KJob API - protocolVersion = hello.protocolVersion(); - Internal::setServerProtocolVersion(protocolVersion); - Internal::setGeneration(hello.generation()); - - sendCommand(nextTag(), Protocol::LoginCommandPtr::create(sessionId)); - return true; - } - - // Login response - if (cmd->type() == Protocol::Command::Login) { - const auto &login = Protocol::cmdCast(cmd); - if (login.isError()) { - qCWarning(AKONADICORE_LOG) << "Unable to login to Akonadi server:" << login.errorMessage(); - connection->closeConnection(); - QTimer::singleShot(1000, mParent, SLOT(reconnect())); - return false; - } - - connected = true; - startNext(); - return true; - } - - // work for the current job - if (currentJob) { - currentJob->d_ptr->handleResponse(tag, cmd); + lock.relock(); } return true; @@ -249,6 +246,23 @@ startNext(); } +void SessionPrivate::publishOtherJobs(Job *thanThisJob) +{ + int count = 0; + for (const auto& job : queue) { + if (job != thanThisJob) { + job->d_ptr->publishJob(); + ++count; + } + } + if (count > 0) { + qCDebug(AKONADICORE_LOG) << "published" << count << "pending jobs to the job tracker"; + } + if (currentJob && currentJob != thanThisJob) { + currentJob->d_ptr->signalStartedToJobTracker(); + } +} + qint64 SessionPrivate::nextTag() { return theNextTag++; @@ -292,6 +306,7 @@ , mSessionThread(new SessionThread) , connection(nullptr) , protocolVersion(0) + , mCommandBuffer(parent, "handleCommands") , currentJob(nullptr) { // Shutdown the thread before QApplication event loop quits - the @@ -340,7 +355,11 @@ if (connection) { connection->forceReconnect(); } +#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) + QMetaObject::invokeMethod(mParent, [this]() { reconnect(); }, Qt::QueuedConnection); +#else QMetaObject::invokeMethod(mParent, "reconnect", Qt::QueuedConnection); +#endif } Session::Session(const QByteArray &sessionId, QObject *parent) @@ -360,21 +379,16 @@ Session::~Session() { - clear(); + d->clear(false); delete d; } QByteArray Session::sessionId() const { return d->sessionId; } -Q_GLOBAL_STATIC(QThreadStorage, instances) - -static void cleanupDefaultSession() -{ - instances()->setLocalData(nullptr); -} +Q_GLOBAL_STATIC(QThreadStorage>, instances) void SessionPrivate::createDefaultSession(const QByteArray &sessionId) { @@ -384,41 +398,51 @@ "You tried to create a default session twice!"); Session *session = new Session(sessionId); - instances()->setLocalData(session); - qAddPostRoutine(cleanupDefaultSession); + setDefaultSession(session); } void SessionPrivate::setDefaultSession(Session *session) { - instances()->setLocalData(session); + instances()->setLocalData({ session }); + QObject::connect(qApp, &QCoreApplication::aboutToQuit, + []() { + instances()->setLocalData({}); + }); } Session *Session::defaultSession() { if (!instances()->hasLocalData()) { Session *session = new Session(); - instances()->setLocalData(session); - qAddPostRoutine(cleanupDefaultSession); + SessionPrivate::setDefaultSession(session); } - return instances()->localData(); + return instances()->localData().data(); } void Session::clear() { - for (Job *job : qAsConst(d->queue)) { + d->clear(true); +} + +void SessionPrivate::clear(bool forceReconnect) +{ + for (Job *job : qAsConst(queue)) { job->kill(KJob::EmitResult); // safe, not started yet } - d->queue.clear(); - for (Job *job : qAsConst(d->pipeline)) { + queue.clear(); + for (Job *job : qAsConst(pipeline)) { job->d_ptr->mStarted = false; // avoid killing/reconnect loops job->kill(KJob::EmitResult); } - d->pipeline.clear(); - if (d->currentJob) { - d->currentJob->d_ptr->mStarted = false; // avoid killing/reconnect loops - d->currentJob->kill(KJob::EmitResult); + pipeline.clear(); + if (currentJob) { + currentJob->d_ptr->mStarted = false; // avoid killing/reconnect loops + currentJob->kill(KJob::EmitResult); + } + + if (forceReconnect) { + this->forceReconnect(); } - d->forceReconnect(); } #include "moc_session.cpp" diff --git a/src/core/session_p.h b/src/core/session_p.h --- a/src/core/session_p.h +++ b/src/core/session_p.h @@ -24,12 +24,14 @@ #include "session.h" #include "item.h" #include "servermanager.h" +#include "commandbuffer_p.h" #include #include #include #include +#include namespace Akonadi { @@ -58,6 +60,8 @@ return mSessionThread; } + void enqueueCommand(qint64 tag, const Protocol::CommandPtr &cmd); + void startNext(); /// Disconnects a previously existing connection and tries to reconnect void forceReconnect(); @@ -67,7 +71,7 @@ void socketDisconnected(); void socketError(const QString &error); void dataReceived(); - virtual bool handleCommand(qint64 tag, const Protocol::CommandPtr &cmd); + virtual bool handleCommands(); void doStartNext(); void startJob(Job *job); @@ -121,10 +125,9 @@ */ void itemRevisionChanged(Akonadi::Item::Id itemId, int oldRevision, int newRevision); - /** - * Default location for akonadiconnectionrc - */ - static QString connectionFile(); + void clear(bool forceReconnect); + + void publishOtherJobs(Job *thanThisJob); Session *mParent = nullptr; SessionThread *mSessionThread = nullptr; @@ -135,6 +138,8 @@ qint64 theNextTag; int protocolVersion; + CommandBuffer mCommandBuffer; + // job management QQueue queue; QQueue pipeline; diff --git a/src/core/sessionthread.cpp b/src/core/sessionthread.cpp --- a/src/core/sessionthread.cpp +++ b/src/core/sessionthread.cpp @@ -18,68 +18,94 @@ */ #include "sessionthread_p.h" - +#include "session_p.h" +#include "akonadicore_debug.h" #include +#include +#include Q_DECLARE_METATYPE(Akonadi::Connection::ConnectionType) Q_DECLARE_METATYPE(Akonadi::Connection *) +Q_DECLARE_METATYPE(Akonadi::CommandBuffer *) using namespace Akonadi; SessionThread::SessionThread(QObject *parent) : QObject(parent) { qRegisterMetaType(); qRegisterMetaType(); + qRegisterMetaType(); QThread *thread = new QThread(); + thread->setObjectName(QStringLiteral("SessionThread")); moveToThread(thread); thread->start(); } SessionThread::~SessionThread() { - QMetaObject::invokeMethod(this, "doThreadQuit"); +#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) + QMetaObject::invokeMethod(this, &SessionThread::doThreadQuit, Qt::QueuedConnection); +#else + QMetaObject::invokeMethod(this, "doThreadQuit", Qt::QueuedConnection); +#endif if (!thread()->wait(10 * 1000)) { thread()->terminate(); // Make sure to wait until it's done, otherwise it can crash when the pthread callback is called thread()->wait(); } delete thread(); } -Connection *SessionThread::createConnection(Connection::ConnectionType connectionType, - const QByteArray &sessionId) +void SessionThread::addConnection(Connection *connection) +{ + connection->moveToThread(thread()); + const bool invoke = QMetaObject::invokeMethod(this, "doAddConnection", + Qt::BlockingQueuedConnection, + Q_ARG(Akonadi::Connection*, connection)); + Q_ASSERT(invoke); Q_UNUSED(invoke); +} + +void SessionThread::doAddConnection(Connection *connection) +{ + Q_ASSERT(thread() == QThread::currentThread()); + Q_ASSERT(!mConnections.contains(connection)); + + connect(connection, &QObject::destroyed, + this, [this](QObject * obj) { + mConnections.removeOne(static_cast(obj)); + }); + mConnections.push_back(connection); +} + +void SessionThread::destroyConnection(Connection *connection) { - Connection *conn = nullptr; - const bool invoke = QMetaObject::invokeMethod(this, "doCreateConnection", - Qt::BlockingQueuedConnection, - Q_RETURN_ARG(Akonadi::Connection*, conn), - Q_ARG(Akonadi::Connection::ConnectionType, connectionType), - Q_ARG(QByteArray, sessionId)); + const bool invoke = QMetaObject::invokeMethod(this, "doDestroyConnection", + Qt::BlockingQueuedConnection, + Q_ARG(Akonadi::Connection*, connection)); Q_ASSERT(invoke); Q_UNUSED(invoke); - return conn; } -Connection *SessionThread::doCreateConnection(Connection::ConnectionType connType, - const QByteArray &sessionId) +void SessionThread::doDestroyConnection(Connection *connection) { Q_ASSERT(thread() == QThread::currentThread()); + Q_ASSERT(mConnections.contains(connection)); - Connection *conn = new Connection(connType, sessionId); - conn->moveToThread(thread()); - connect(conn, &QObject::destroyed, - this, [this](QObject * obj) { - mConnections.removeOne(static_cast(obj)); - }); - return conn; + connection->disconnect(this); + connection->doCloseConnection(); + mConnections.removeAll(connection); + delete connection; } void SessionThread::doThreadQuit() { + Q_ASSERT(thread() == QThread::currentThread()); + for (Connection *conn : qAsConst(mConnections)) { - conn->closeConnection(); + conn->disconnect(this); + conn->doCloseConnection(); // we can call directly because we are in the correct thread delete conn; } diff --git a/src/core/sessionthread_p.h b/src/core/sessionthread_p.h --- a/src/core/sessionthread_p.h +++ b/src/core/sessionthread_p.h @@ -22,27 +22,31 @@ #include #include +#include #include "connection_p.h" +class QEventLoop; + namespace Akonadi { - +class CommandBuffer; class SessionThread : public QObject { Q_OBJECT public: explicit SessionThread(QObject *parent = nullptr); ~SessionThread(); - Connection *createConnection(Connection::ConnectionType connType, const QByteArray &sessionId); + void addConnection(Connection *connection); + void destroyConnection(Connection *connection); -private: - Q_INVOKABLE Akonadi::Connection *doCreateConnection(Akonadi::Connection::ConnectionType connType, - const QByteArray &sessionId); +private Q_SLOTS: + void doDestroyConnection(Akonadi::Connection *connection); + void doAddConnection(Akonadi::Connection *connection); - Q_INVOKABLE void doThreadQuit(); + void doThreadQuit(); private: QVector mConnections; diff --git a/src/core/specialcollectionattribute.h b/src/core/specialcollectionattribute.h --- a/src/core/specialcollectionattribute.h +++ b/src/core/specialcollectionattribute.h @@ -47,7 +47,7 @@ /** * Destroys the special collection attribute. */ - virtual ~SpecialCollectionAttribute(); + ~SpecialCollectionAttribute() override; /** * Sets the special collections @p type of the collection. diff --git a/src/core/specialcollections.h b/src/core/specialcollections.h --- a/src/core/specialcollections.h +++ b/src/core/specialcollections.h @@ -76,13 +76,13 @@ * Returns whether the given agent @p instance has a special collection of * the given @p type. */ - bool hasCollection(const QByteArray &type, const AgentInstance &instance) const; + Q_REQUIRED_RESULT bool hasCollection(const QByteArray &type, const AgentInstance &instance) const; /** * Returns the special collection of the given @p type in the given agent * @p instance, or an invalid collection if such a collection is unknown. */ - Akonadi::Collection collection(const QByteArray &type, const AgentInstance &instance) const; + Q_REQUIRED_RESULT Akonadi::Collection collection(const QByteArray &type, const AgentInstance &instance) const; /** * Registers the given @p collection as a special collection @@ -123,13 +123,13 @@ * Returns whether the default resource has a special collection of * the given @p type. */ - bool hasDefaultCollection(const QByteArray &type) const; + Q_REQUIRED_RESULT bool hasDefaultCollection(const QByteArray &type) const; /** * Returns the special collection of given @p type in the default * resource, or an invalid collection if such a collection is unknown. */ - Akonadi::Collection defaultCollection(const QByteArray &type) const; + Q_REQUIRED_RESULT Akonadi::Collection defaultCollection(const QByteArray &type) const; Q_SIGNALS: /** @@ -160,8 +160,7 @@ friend class SpecialCollectionsRequestJob; friend class SpecialCollectionsRequestJobPrivate; friend class SpecialCollectionsPrivate; - -#if 1 // TODO do this only if building tests: +#ifdef BUILD_TESTING friend class SpecialMailCollectionsTesting; friend class LocalFoldersTest; #endif diff --git a/src/core/tag.h b/src/core/tag.h --- a/src/core/tag.h +++ b/src/core/tag.h @@ -197,6 +197,8 @@ static Tag genericTag(const QString &name); private: + bool checkAttribute(Attribute *attr, const QByteArray &type) const; + //@cond PRIVATE friend class TagModifyJob; @@ -214,12 +216,9 @@ const T dummy; if (hasAttribute(dummy.type())) { T *attr = dynamic_cast(attribute(dummy.type())); - if (attr) { + if (checkAttribute(attr, dummy.type())) { return attr; } - //reuse 5250 - qWarning() << "Found attribute of unknown type" << dummy.type() - << ". Did you forget to call AttributeFactory::registerAttribute()?"; } T *attr = new T(); @@ -233,12 +232,9 @@ const T dummy; if (hasAttribute(dummy.type())) { T *attr = dynamic_cast(attribute(dummy.type())); - if (attr) { + if (checkAttribute(attr, dummy.type())) { return attr; } - //Reuse 5250 - qWarning() << "Found attribute of unknown type" << dummy.type() - << ". Did you forget to call AttributeFactory::registerAttribute()?"; } return nullptr; diff --git a/src/core/tag.cpp b/src/core/tag.cpp --- a/src/core/tag.cpp +++ b/src/core/tag.cpp @@ -19,6 +19,7 @@ #include "tag.h" #include "tag_p.h" +#include "akonadicore_debug.h" #include "tagattribute.h" #include @@ -256,3 +257,13 @@ tag.setName(name); return tag; } + +bool Tag::checkAttribute(Attribute *attr, const QByteArray &type) const +{ + if (attr) { + return true; + } + qCWarning(AKONADICORE_LOG) << "Found attribute of unknown type" << type + << ". Did you forget to call AttributeFactory::registerAttribute()?"; + return false; +} diff --git a/src/core/tagattribute.cpp b/src/core/tagattribute.cpp --- a/src/core/tagattribute.cpp +++ b/src/core/tagattribute.cpp @@ -27,19 +27,17 @@ { public: Private() - : inToolbar(false) - , priority(-1) { } QString name; QString icon; QColor backgroundColor; QColor textColor; QString font; - bool inToolbar; + bool inToolbar = false; QString shortcut; - int priority; + int priority = -1; }; TagAttribute::TagAttribute() @@ -95,6 +93,7 @@ QByteArray TagAttribute::serialized() const { QList l; + l.reserve(8); l << ImapParser::quote(d->name.toUtf8()); l << ImapParser::quote(d->icon.toUtf8()); l << ImapParser::quote(d->font.toUtf8()); diff --git a/src/core/tagfetchscope.cpp b/src/core/tagfetchscope.cpp --- a/src/core/tagfetchscope.cpp +++ b/src/core/tagfetchscope.cpp @@ -24,12 +24,11 @@ struct Q_DECL_HIDDEN Akonadi::TagFetchScope::Private { Private() - : mFetchIdOnly(false) { } QSet mAttributes; - bool mFetchIdOnly; + bool mFetchIdOnly = false; }; TagFetchScope::TagFetchScope() diff --git a/src/core/tagsync.h b/src/core/tagsync.h --- a/src/core/tagsync.h +++ b/src/core/tagsync.h @@ -33,7 +33,7 @@ Q_OBJECT public: explicit TagSync(QObject *parent = nullptr); - virtual ~TagSync(); + ~TagSync() override; void setFullTagList(const Akonadi::Tag::List &tags); void setTagMembers(const QHash &ridMemberMap); diff --git a/src/core/trashsettings.h b/src/core/trashsettings.h --- a/src/core/trashsettings.h +++ b/src/core/trashsettings.h @@ -46,7 +46,7 @@ /** * Get the trash collection for the given @p resource */ -AKONADICORE_EXPORT Collection getTrashCollection(const QString &resource); +Q_REQUIRED_RESULT AKONADICORE_EXPORT Collection getTrashCollection(const QString &resource); } } diff --git a/src/core/vectorhelper.h b/src/core/vectorhelper.h --- a/src/core/vectorhelper.h +++ b/src/core/vectorhelper.h @@ -1,5 +1,5 @@ /* - Copyright (C) 2015-2017 Laurent Montel + Copyright (C) 2015-2018 Laurent Montel 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 diff --git a/src/private/CMakeLists.txt b/src/private/CMakeLists.txt --- a/src/private/CMakeLists.txt +++ b/src/private/CMakeLists.txt @@ -1,5 +1,11 @@ add_subdirectory(protocolgen) +if(NOT XMLLINT_EXECUTABLE) + message(STATUS "xmllint not found, skipping protocol.xml validation") +else() + add_test(AkonadiPrivate-protocol-xmllint ${XMLLINT_EXECUTABLE} --noout ${CMAKE_CURRENT_SOURCE_DIR}/protocol.xml) +endif() + add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/protocol_gen.cpp ${CMAKE_CURRENT_BINARY_DIR}/protocol_gen.h COMMAND protocolgen ${CMAKE_CURRENT_SOURCE_DIR}/protocol.xml @@ -18,11 +24,23 @@ protocol.cpp scope.cpp tristate.cpp - xdgbasedirs.cpp standarddirs.cpp dbus.cpp ) +set(akonadiprivate_LIBS +PUBLIC + Qt5::Core + Qt5::DBus +) +if (WIN32) + set(akonadiprivate_LIBS + ${akonadiprivate_LIBS} + PRIVATE + Qt5::Network + ) +endif() + ecm_qt_declare_logging_category(akonadiprivate_SRCS HEADER akonadiprivate_debug.h IDENTIFIER AKONADIPRIVATE_LOG CATEGORY_NAME org.kde.pim.akonadiprivate) if (WIN32) @@ -43,11 +61,7 @@ if (WIN32) add_dependencies(KF5AkonadiPrivate generate_protocol) endif() -target_link_libraries(KF5AkonadiPrivate -PUBLIC - Qt5::Core - Qt5::DBus -) +target_link_libraries(KF5AkonadiPrivate ${akonadiprivate_LIBS}) generate_export_header(KF5AkonadiPrivate BASE_NAME akonadiprivate) set_target_properties(KF5AkonadiPrivate PROPERTIES @@ -73,7 +87,6 @@ protocol_p.h ${CMAKE_CURRENT_BINARY_DIR}/protocol_gen.h protocol_exception_p.h - xdgbasedirs_p.h capabilities_p.h scope_p.h tristate_p.h @@ -90,7 +103,4 @@ set_target_properties(akonadiprivate_static PROPERTIES COMPILE_FLAGS -DAKONADIPRIVATE_STATIC_DEFINE ) -target_link_libraries(akonadiprivate_static - Qt5::Core - Qt5::DBus -) +target_link_libraries(akonadiprivate_static ${akonadiprivate_LIBS}) diff --git a/src/private/channel.h b/src/private/channel.h new file mode 100644 --- /dev/null +++ b/src/private/channel.h @@ -0,0 +1,105 @@ +/* + Copyright (c) 2018 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 AKONADIPRIVATE_CHANNEL_P_H_ +#define AKONADIPRIVATE_CHANNEL_P_H_ + +#include "akonadiprivate_export.h" + +#include + +#include +#include +#include +#include + +#include +#include + +namespace Akonadi { + +class ChannelBase +{ +public: + explicit ChannelBase(const QByteArray &id); + ChannelBase(const ChannelBase &other) = delete; + ~ChannelBase() = default; + + void setCapacity(std::size_t capacity); + Q_REQUIRED_RESULT std::size_t capacity() const; + + ChannelBase &operator=(const ChannelBase &other) = delete; + + /// Creates a new channel + bool create(); + /// Attaches to an existing channel with given id + bool attach(); + +protected: + struct Queue { + boost::interprocess::interprocess_condition full; + boost::interprocess::interprocess_condition empty; + boost::interprocess::interprocess_mutex mutex; + std::size_t capacity; + }; + + const QByteArray mId; + boost::interprocess::shared_memory_object mShm; + boost::interprocess::mapped_region mMapped; + Queue *mQueueData = nullptr; +} + +template +class Channel : public ChannelBase +{ +public: + explicit Channel(const QByteArray &id) = default; + ~Channel() = default; + + void enqueue(T &&entity) + { + Q_ASSERT(mQueueData); + std::unique_lock lock(mQueueData->mutex); + while (mQueue.size() >= mQueueData->capacity) { + mQueueData->full.wait(lock); + } + mQueue.emplace_back(entity); + mQueueData->empty.wake_one(); + } + + Q_REQUIRED_RESULT T &&dequeue() + { + Q_ASSERT(mQueueData); + std::unique_lock lock(mQueueData->mutex); + while (mQueue.empty()) { + mQueueData->empty.wait(lock); + } + T elem = std::move(mQueue.front()); + mQueue.pop(); + mQueueData->full.wake_one(); + return elem; // moves + } + +private: + std::deque mQueue; +}; + +} // namespace Akonadi + +#endif diff --git a/src/private/channel.cpp b/src/private/channel.cpp new file mode 100644 --- /dev/null +++ b/src/private/channel.cpp @@ -0,0 +1,101 @@ +/* + Copyright (c) 2018 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 "channel.h" +#include "akonadiprivate_debug.h" + +#include + +using namespace Akonadi; + +ChannelBase::ChannelBase(const QByteArray &id) + : mId(id) +{ + +} + +void ChannelBase::setCapacity(std::size_t capacity) +{ + if (!mQueueData) { + qCWarning(AKONADIPRIVATE_LOG, "Changing capacity of a closed channel!"); + return; + } + + std::unique_lock lock(mQueueData->mutex); + mQueue->capacity = capacity; +} + +std::size_t Akonadi::ChannelBase::capacity() const +{ + if (!mQueueData) { + return 0; + } + + std::unique_lock lock(mQueueData->mutex); + return mQueueData->capacity; +} + +bool ChannelBase::create() +{ + Q_ASSERT_X(mQueueData == nullptr, "ChannelBase", "Channel already opened in 'attach' mode"); + if (mQueueData) { + return false; + } + + try { + mShm = {boost::interprocess::create_only, mId.constData(), boost::interprocess::read_write}; + mShm.truncate(sizeof(Queue)); + + mMapped = {mShm, boost::interprocess::read_write}; + + mQueueData = new (mMapped.get_address()) Queue; + } catch (const boost::interprocess::interprocess_exception &e) { + qCCritical(AKONADIPRIVATE_LOG, "Failed to create Channel shared memory: %s", e.what()); + mQueueData = nullptr; + mMapped = {}; + mShm.remove(mId.constData()); + mShm = {}; + return false; + } + + return true; +} + +bool ChannelBase::attach() +{ + Q_ASSERT_X(mQueueData == nullptr, "ChannelBase", "Channel already opened in 'create' mode"); + if (mQueueData) { + return false; + } + + try { + mShm = {boost::interprocess::open_only, mId.constData(), boost::interprocess::read_write}; + mMapped = {mShm, boost::interprocess::read_write}; + mQueueData = static_cast(mMapped.get_address()); + } catch (const boost::interprocess::interprocess_exception &e) { + qCCritical(AKONADIPRIVATE_LOG, "Failed to attach to Channel shared memory: %s", e.what()); + mQueueData = nullptr; + mMapped = {}; + mShm = {}; + return false; + } + + return true; +} + diff --git a/src/private/datastream_p.cpp b/src/private/datastream_p.cpp --- a/src/private/datastream_p.cpp +++ b/src/private/datastream_p.cpp @@ -19,6 +19,12 @@ #include "datastream_p_p.h" +#ifdef Q_OS_WIN +#include +#include +#include +#endif + using namespace Akonadi; using namespace Akonadi::Protocol; @@ -38,6 +44,44 @@ { } +void DataStream::waitForData(QIODevice *device, int timeoutMs) +{ +#ifdef Q_OS_WIN + // Apparently readyRead() gets emitted sometimes even if there are no data + // so we will re-enter the wait again immediatelly + while (device->bytesAvailable() == 0) { + auto ls = qobject_cast(device); + if (ls && ls->state() != QLocalSocket::ConnectedState) { + throw ProtocolException("Socket not connected to server"); + } + + QEventLoop loop; + QObject::connect(device, &QIODevice::readyRead, &loop, &QEventLoop::quit); + if (ls) { + QObject::connect(ls, &QLocalSocket::stateChanged, &loop, &QEventLoop::quit); + } + bool timeout = false; + if (timeoutMs > 0) { + QTimer::singleShot(timeoutMs, &loop, [&]() { + timeout = true; + loop.quit(); + }); + } + loop.exec(); + if (timeout) { + throw ProtocolException("Timeout while waiting for data"); + } + if (ls && ls->state() != QLocalSocket::ConnectedState) { + throw ProtocolException("Socket not connected to server"); + } + } +#else + if (!device->waitForReadyRead(timeoutMs)) { + throw ProtocolException("Timeout while waiting for data"); + } +#endif +} + QIODevice *DataStream::device() const { return mDev; @@ -62,9 +106,7 @@ checkDevice(); while (mDev->bytesAvailable() < size) { - if (!mDev->waitForReadyRead(mWaitTimeout)) { - throw ProtocolException("Timeout while waiting for data"); - } + waitForData(mDev, mWaitTimeout); } } @@ -81,7 +123,7 @@ void DataStream::writeBytes(const char *bytes, int len) { - *this << (quint32) len; + *this << static_cast (len); if (len) { writeRawData(bytes, len); } diff --git a/src/private/datastream_p_p.h b/src/private/datastream_p_p.h --- a/src/private/datastream_p_p.h +++ b/src/private/datastream_p_p.h @@ -22,6 +22,7 @@ #include +#include "akonadiprivate_export.h" #include "protocol_exception_p.h" #include @@ -33,14 +34,16 @@ namespace Protocol { -class DataStream +class AKONADIPRIVATE_EXPORT DataStream { public: explicit DataStream(); explicit DataStream(QIODevice *device); ~DataStream(); + static void waitForData(QIODevice *device, int timeoutMs); + QIODevice *device() const; void setDevice(QIODevice *device); @@ -256,17 +259,15 @@ // Inline functions template -inline Akonadi::Protocol::DataStream &operator<<(Akonadi::Protocol::DataStream &stream, const QFlags &flags) +inline Akonadi::Protocol::DataStream &operator<<(Akonadi::Protocol::DataStream &stream, QFlags flags) { - return stream << (int) flags; + return stream << static_cast::Int>(flags); } template inline Akonadi::Protocol::DataStream &operator>>(Akonadi::Protocol::DataStream &stream, QFlags &flags) { - int i; - stream >> i; - flags = QFlags(i); + stream >> reinterpret_cast::Int&>(flags); return stream; } diff --git a/src/private/dbus.cpp b/src/private/dbus.cpp --- a/src/private/dbus.cpp +++ b/src/private/dbus.cpp @@ -70,11 +70,12 @@ if ((parts.size() == 2 && !Akonadi::Instance::hasIdentifier()) || (parts.size() == 3 && Akonadi::Instance::hasIdentifier() && Akonadi::Instance::identifier() == parts.at(2))) { // switch on parts.at( 0 ) - if (parts.first() == QLatin1String("Agent")) { + const QString &partFirst = parts.constFirst(); + if (partFirst == QLatin1String("Agent")) { agentType = Agent; - } else if (parts.first() == QLatin1String("Resource")) { + } else if (partFirst == QLatin1String("Resource")) { agentType = Resource; - } else if (parts.first() == QLatin1String("Preprocessor")) { + } else if (partFirst == QLatin1String("Preprocessor")) { agentType = Preprocessor; } else { return QString(); diff --git a/src/private/externalpartstorage.cpp b/src/private/externalpartstorage.cpp --- a/src/private/externalpartstorage.cpp +++ b/src/private/externalpartstorage.cpp @@ -95,7 +95,7 @@ const QString path = basePath + QDir::separator() + (revPos > 1 ? filename[revPos - 2] : QLatin1Char('0')) - + filename[revPos - 1] + + (revPos > 0 ? filename[revPos - 1] : QLatin1Char('0')) + QDir::separator() + filename; // If legacy fallback is disabled, return it in any case diff --git a/src/private/protocol.cpp b/src/private/protocol.cpp --- a/src/private/protocol.cpp +++ b/src/private/protocol.cpp @@ -26,6 +26,8 @@ #include #include +#include +#include #include #include @@ -141,11 +143,11 @@ case Command::_ResponseBit: Q_ASSERT(false); - return dbg << (int)type; + return dbg << static_cast(type); } Q_ASSERT(false); - return dbg << (int)type; + return dbg << static_cast(type); } template @@ -164,34 +166,77 @@ /******************************************************************************/ -Command::Command() - : mType(Invalid) -{ -} - -Command::Command(const Command &other) - : mType(other.mType) -{ -} - Command::Command(quint8 type) : mType(type) { } -Command::~Command() +bool Command::operator==(const Command &other) const { + return mType == other.mType; } -Command& Command::operator=(const Command &other) +void Command::toJson(QJsonObject &json) const { - mType = other.mType; - return *this; -} + json[QStringLiteral("response")] = static_cast(mType & Command::_ResponseBit); -bool Command::operator==(const Command &other) const -{ - return mType == other.mType; +#define case_label(x) case Command::x: { \ + json[QStringLiteral("type")] = QStringLiteral(#x); \ + } break; + + switch (mType & ~Command::_ResponseBit) + { + case_label(Invalid) + case_label(Hello) + + case_label(Login) + case_label(Logout) + + case_label(Transaction) + + case_label(CreateItem) + case_label(CopyItems) + case_label(DeleteItems) + case_label(FetchItems) + case_label(LinkItems) + case_label(ModifyItems) + case_label(MoveItems) + + case_label(CreateCollection) + case_label(CopyCollection) + case_label(DeleteCollection) + case_label(FetchCollections) + case_label(FetchCollectionStats) + case_label(ModifyCollection) + case_label(MoveCollection) + + case_label(Search) + case_label(SearchResult) + case_label(StoreSearch) + + case_label(CreateTag) + case_label(DeleteTag) + case_label(FetchTags) + case_label(ModifyTag) + + case_label(FetchRelations) + case_label(ModifyRelation) + case_label(RemoveRelations) + + case_label(SelectResource) + + case_label(StreamPayload) + case_label(CreateSubscription) + case_label(ModifySubscription) + + case_label(DebugChangeNotification) + case_label(ItemChangeNotification) + case_label(CollectionChangeNotification) + case_label(TagChangeNotification) + case_label(RelationChangeNotification) + case_label(SubscriptionChangeNotification) + } +#undef case_label } DataStream &operator<<(DataStream &stream, const Command &cmd) @@ -210,17 +255,85 @@ << static_cast(cmd.mType & ~Command::_ResponseBit) << "\n"; } -/******************************************************************************/ - -Response::Response() - : Response(Command::Invalid) +void toJson(const Akonadi::Protocol::Command *command, QJsonObject &json) { +#define case_notificationlabel(x, class) case Command::x: { \ + static_cast(command)->toJson(json); \ + } break; +#define case_commandlabel(x, cmd, resp) \ +case Command::x: { \ + static_cast(command)->toJson(json); \ + } break; \ +case Command::x | Command::_ResponseBit: { \ + static_cast(command)->toJson(json); \ + } break; + + switch (command->mType) + { + case Command::Invalid: + break; + + case Command::Hello | Command::_ResponseBit: + { + static_cast(command)->toJson(json); + } + break; + case_commandlabel(Login, LoginCommand, LoginResponse) + case_commandlabel(Logout, LogoutCommand, LogoutResponse) + + case_commandlabel(Transaction, TransactionCommand, TransactionResponse) + + case_commandlabel(CreateItem, CreateItemCommand, CreateItemResponse) + case_commandlabel(CopyItems, CopyItemsCommand, CopyItemsResponse) + case_commandlabel(DeleteItems, DeleteItemsCommand, DeleteItemsResponse) + case_commandlabel(FetchItems, FetchItemsCommand, FetchItemsResponse) + case_commandlabel(LinkItems, LinkItemsCommand, LinkItemsResponse) + case_commandlabel(ModifyItems, ModifyItemsCommand, ModifyItemsResponse) + case_commandlabel(MoveItems, MoveItemsCommand, MoveItemsResponse) + + case_commandlabel(CreateCollection, CreateCollectionCommand, CreateCollectionResponse) + case_commandlabel(CopyCollection, CopyCollectionCommand, CopyCollectionResponse) + case_commandlabel(DeleteCollection, DeleteCollectionCommand, DeleteCollectionResponse) + case_commandlabel(FetchCollections, FetchCollectionsCommand, FetchCollectionsResponse) + case_commandlabel(FetchCollectionStats, FetchCollectionStatsCommand, FetchCollectionStatsResponse) + case_commandlabel(ModifyCollection, ModifyCollectionCommand, ModifyCollectionResponse) + case_commandlabel(MoveCollection, MoveCollectionCommand, MoveCollectionResponse) + + case_commandlabel(Search, SearchCommand, SearchResponse) + case_commandlabel(SearchResult, SearchResultCommand, SearchResultResponse) + case_commandlabel(StoreSearch, StoreSearchCommand, StoreSearchResponse) + + case_commandlabel(CreateTag, CreateTagCommand, CreateTagResponse) + case_commandlabel(DeleteTag, DeleteTagCommand, DeleteTagResponse) + case_commandlabel(FetchTags, FetchTagsCommand, FetchTagsResponse) + case_commandlabel(ModifyTag, ModifyTagCommand, ModifyTagResponse) + + case_commandlabel(FetchRelations, FetchRelationsCommand, FetchRelationsResponse) + case_commandlabel(ModifyRelation, ModifyRelationCommand, ModifyRelationResponse) + case_commandlabel(RemoveRelations, RemoveRelationsCommand, RemoveRelationsResponse) + + case_commandlabel(SelectResource, SelectResourceCommand, SelectResourceResponse) + + case_commandlabel(StreamPayload, StreamPayloadCommand, StreamPayloadResponse) + case_commandlabel(CreateSubscription, CreateSubscriptionCommand, CreateSubscriptionResponse) + case_commandlabel(ModifySubscription, ModifySubscriptionCommand, ModifySubscriptionResponse) + + case_notificationlabel(DebugChangeNotification, DebugChangeNotification) + case_notificationlabel(ItemChangeNotification, ItemChangeNotification) + case_notificationlabel(CollectionChangeNotification, CollectionChangeNotification) + case_notificationlabel(TagChangeNotification, TagChangeNotification) + case_notificationlabel(RelationChangeNotification, RelationChangeNotification) + case_notificationlabel(SubscriptionChangeNotification, SubscriptionChangeNotification) + } +#undef case_notificationlabel +#undef case_commandlabel } -Response::Response(const Response &other) - : Command(other) - , mErrorCode(other.mErrorCode) - , mErrorMsg(other.mErrorMsg) +/******************************************************************************/ + +Response::Response() + : Command(Command::Invalid | Command::_ResponseBit) + , mErrorCode(0) { } @@ -230,21 +343,26 @@ { } -Response &Response::operator=(const Response &other) -{ - Command::operator=(other); - mErrorMsg = other.mErrorMsg; - mErrorCode = other.mErrorCode; - return *this; -} - bool Response::operator==(const Response &other) const { return *static_cast(this) == static_cast(other) && mErrorCode == other.mErrorCode && mErrorMsg == other.mErrorMsg; } +void Response::toJson(QJsonObject &json) const +{ + static_cast(this)->toJson(json); + if (isError()) { + QJsonObject error; + error[QStringLiteral("code")] = errorCode(); + error[QStringLiteral("message")] = errorMessage(); + json[QStringLiteral("error")] = error; + } else { + json[QStringLiteral("error")] = false; + } +} + DataStream &operator<<(DataStream &stream, const Response &cmd) { return stream << static_cast(cmd) @@ -386,54 +504,25 @@ /******************************************************************************/ -FetchScope::FetchScope() - : mAncestorDepth(NoAncestor) - , mFlags(None) -{ -} - -FetchScope::FetchScope(const FetchScope &other) - : mAncestorDepth(other.mAncestorDepth) - , mFlags(other.mFlags) - , mRequestedParts(other.mRequestedParts) - , mChangedSince(other.mChangedSince) - , mTagFetchScope(other.mTagFetchScope) -{ -} - -FetchScope::~FetchScope() -{ -} - -FetchScope &FetchScope::operator=(const FetchScope &other) -{ - mAncestorDepth = other.mAncestorDepth; - mFlags = other.mFlags; - mRequestedParts = other.mRequestedParts; - mChangedSince = other.mChangedSince; - mTagFetchScope = other.mTagFetchScope; - return *this; -} - -bool FetchScope::operator==(const FetchScope &other) const +bool ItemFetchScope::operator==(const ItemFetchScope &other) const { return mRequestedParts == other.mRequestedParts && mChangedSince == other.mChangedSince && mTagFetchScope == other.mTagFetchScope && mAncestorDepth == other.mAncestorDepth && mFlags == other.mFlags; } -QVector FetchScope::requestedPayloads() const +QVector ItemFetchScope::requestedPayloads() const { QVector rv; std::copy_if(mRequestedParts.begin(), mRequestedParts.end(), std::back_inserter(rv), [](const QByteArray &ba) { return ba.startsWith("PLD:"); }); return rv; } -void FetchScope::setFetch(FetchFlags attributes, bool fetch) +void ItemFetchScope::setFetch(FetchFlags attributes, bool fetch) { if (fetch) { mFlags |= attributes; @@ -447,47 +536,66 @@ } } -bool FetchScope::fetch(FetchFlags flags) const +bool ItemFetchScope::fetch(FetchFlags flags) const { if (flags == None) { return mFlags == None; } else { return mFlags & flags; } } -QDebug operator<<(QDebug dbg, FetchScope::AncestorDepth depth) +void ItemFetchScope::toJson(QJsonObject &json) const +{ + json[QStringLiteral("flags")] = static_cast(mFlags); + QJsonArray tagFetchArray; + for (const auto &tag : qAsConst(mTagFetchScope)) { + tagFetchArray.append(QString::fromUtf8(tag)); + } + json[QStringLiteral("TagFetchScope")] = tagFetchArray; + + json[QStringLiteral("ChangedSince")] = mChangedSince.toString(); + json[QStringLiteral("AncestorDepth")] = static_cast::type>(mAncestorDepth); + + QJsonArray requestedPartsArray; + for (const auto &part : qAsConst(mRequestedParts)) { + requestedPartsArray.append(QString::fromUtf8(part)); + } + json[QStringLiteral("RequestedParts")] = requestedPartsArray; +} + +QDebug operator<<(QDebug dbg, ItemFetchScope::AncestorDepth depth) { switch (depth) { - case FetchScope::NoAncestor: + case ItemFetchScope::NoAncestor: return dbg << "No ancestor"; - case FetchScope::ParentAncestor: + case ItemFetchScope::ParentAncestor: return dbg << "Parent ancestor"; - case FetchScope::AllAncestors: + case ItemFetchScope::AllAncestors: return dbg << "All ancestors"; } Q_UNREACHABLE(); } -DataStream &operator<<(DataStream &stream, const FetchScope &scope) +DataStream &operator<<(DataStream &stream, const ItemFetchScope &scope) { return stream << scope.mRequestedParts << scope.mChangedSince << scope.mTagFetchScope << scope.mAncestorDepth << scope.mFlags; } -DataStream &operator>>(DataStream &stream, FetchScope &scope) +DataStream &operator>>(DataStream &stream, ItemFetchScope &scope) { return stream >> scope.mRequestedParts >> scope.mChangedSince >> scope.mTagFetchScope >> scope.mAncestorDepth >> scope.mFlags; } -QDebug operator<<(QDebug dbg, const FetchScope &scope) +QDebug operator<<(QDebug dbg, const ItemFetchScope &scope) { return dbg.noquote() << "FetchScope(\n" << "Fetch Flags:" << scope.mFlags << "\n" @@ -500,11 +608,6 @@ /******************************************************************************/ - -ScopeContext::ScopeContext() -{ -} - ScopeContext::ScopeContext(Type type, qint64 id) { if (type == ScopeContext::Tag) { @@ -523,28 +626,30 @@ } } -ScopeContext::ScopeContext(const ScopeContext &other) - : mColCtx(other.mColCtx) - , mTagCtx(other.mTagCtx) -{ -} - -ScopeContext::~ScopeContext() -{ -} - -ScopeContext &ScopeContext::operator=(const ScopeContext &other) -{ - mColCtx = other.mColCtx; - mTagCtx = other.mTagCtx; - return *this; -} - bool ScopeContext::operator==(const ScopeContext &other) const { return mColCtx == other.mColCtx && mTagCtx == other.mTagCtx; } +void ScopeContext::toJson(QJsonObject &json) const +{ + if (isEmpty()) { + json[QStringLiteral("scopeContext")] = false; + } else if (hasContextId(ScopeContext::Tag)) { + json[QStringLiteral("scopeContext")] = QStringLiteral("tag"); + json[QStringLiteral("TagID")] = contextId(ScopeContext::Tag); + } else if (hasContextId(ScopeContext::Collection)) { + json[QStringLiteral("scopeContext")] = QStringLiteral("collection"); + json[QStringLiteral("ColID")] = contextId(ScopeContext::Collection); + } else if (hasContextRID(ScopeContext::Tag)) { + json[QStringLiteral("scopeContext")] = QStringLiteral("tagrid"); + json[QStringLiteral("TagRID")] = contextRID(ScopeContext::Tag); + } else if (hasContextRID(ScopeContext::Collection)) { + json[QStringLiteral("scopeContext")] = QStringLiteral("colrid"); + json[QStringLiteral("ColRID")] = contextRID(ScopeContext::Collection); + } +} + DataStream &operator<<(DataStream &stream, const ScopeContext &context) { // We don't have a custom generic DataStream streaming operator for QVariant @@ -615,28 +720,22 @@ { } -ChangeNotification::ChangeNotification(const ChangeNotification &other) - : Command(other) - , mSessionId(other.mSessionId) - , mMetaData(other.mMetaData) -{ -} - -ChangeNotification &ChangeNotification::operator=(const ChangeNotification &other) -{ - *static_cast(this) = static_cast(other); - mSessionId = other.mSessionId; - mMetaData = other.mMetaData; - return *this; -} - bool ChangeNotification::operator==(const ChangeNotification &other) const { return static_cast(*this) == other && mSessionId == other.mSessionId; // metadata are not compared } +QList ChangeNotification::itemsToUids(const QVector &items) +{ + QList rv; + rv.reserve(items.size()); + std::transform(items.cbegin(), items.cend(), std::back_inserter(rv), + [](const FetchItemsResponse &item) { return item.id(); }); + return rv; +} + bool ChangeNotification::isRemove() const { switch (type()) { @@ -697,26 +796,30 @@ // matching notification for (auto iter = list.end(), begin = list.begin(); iter != begin;) { --iter; - auto &it = Protocol::cmdCast(*iter); - if (cmsg.id() == it.id() - && cmsg.remoteId() == it.remoteId() - && cmsg.remoteRevision() == it.remoteRevision() - && cmsg.resource() == it.resource() - && cmsg.destinationResource() == it.destinationResource() - && cmsg.parentCollection() == it.parentCollection() - && cmsg.parentDestCollection() == it.parentDestCollection()) - { - // both are modifications, merge them together and drop the new one - if (cmsg.operation() == CollectionChangeNotification::Modify - && it.operation() == CollectionChangeNotification::Modify) { - const auto parts = it.changedParts(); - it.setChangedParts(parts + cmsg.changedParts()); - return false; - } - - // we found Add notification, which means we can drop this modification - if (it.operation() == CollectionChangeNotification::Add) { - return false; + if ((*iter)->type() == Protocol::Command::CollectionChangeNotification) { + auto &it = Protocol::cmdCast(*iter); + const auto &msgCol = cmsg.collection(); + const auto &itCol = it.collection(); + if (msgCol.id() == itCol.id() + && msgCol.remoteId() == itCol.remoteId() + && msgCol.remoteRevision() == itCol.remoteRevision() + && msgCol.resource() == itCol.resource() + && cmsg.destinationResource() == it.destinationResource() + && cmsg.parentCollection() == it.parentCollection() + && cmsg.parentDestCollection() == it.parentDestCollection()) + { + // both are modifications, merge them together and drop the new one + if (cmsg.operation() == CollectionChangeNotification::Modify + && it.operation() == CollectionChangeNotification::Modify) { + const auto parts = it.changedParts(); + it.setChangedParts(parts + cmsg.changedParts()); + return false; + } + + // we found Add notification, which means we can drop this modification + if (it.operation() == CollectionChangeNotification::Add) { + return false; + } } } searchCounter++; @@ -732,6 +835,18 @@ return true; } +void ChangeNotification::toJson(QJsonObject &json) const +{ + static_cast(this)->toJson(json); + json[QStringLiteral("session")] = QString::fromUtf8(mSessionId); + + QJsonArray metadata; + for (const auto &m : qAsConst(mMetaData)) { + metadata.append(QString::fromUtf8(m)); + } + json[QStringLiteral("metadata")] = metadata; +} + DataStream &operator<<(DataStream &stream, const ChangeNotification &ntf) { return stream << static_cast(ntf) @@ -752,30 +867,6 @@ } -DataStream &operator>>(DataStream &stream, ChangeNotification::Item &item) -{ - return stream >> item.id - >> item.mimeType - >> item.remoteId - >> item.remoteRevision; -} - -DataStream &operator<<(DataStream &stream, const ChangeNotification::Item &item) -{ - return stream << item.id - << item.mimeType - << item.remoteId - << item.remoteRevision; -} - -QDebug operator<<(QDebug _dbg, const ChangeNotification::Item &item) -{ - QDebug dbg(_dbg.noquote()); - return dbg << "Item:" << item.id << "(RID:" << item.remoteId - << ", RREV:" << item.remoteRevision << ", mimetype: " << item.mimeType; -} - - DataStream &operator>>(DataStream &stream, ChangeNotification::Relation &relation) { return stream >> relation.type @@ -796,10 +887,40 @@ return dbg << "Left: " << rel.leftId << ", Right:" << rel.rightId << ", Type: " << rel.type; } - } // namespace Protocol } // namespace Akonadi + + +// Helpers for the generated code +namespace Akonadi { +namespace Protocol { + +template class Container> +inline bool containerComparator(const Container &c1, const Container &c2) +{ + return c1 == c2; +} + +template class Container> +inline bool containerComparator(const Container> &c1, + const Container> &c2) +{ + if (c1.size() != c2.size()) { + return false; + } + + for (auto it1 = c1.cbegin(), it2 = c2.cbegin(), end1 = c1.cend(); it1 != end1; ++it1, ++it2) { + if (**it1 != **it2) { + return false; + } + } + return true; +} + +} +} + /******************************************************************************/ // Here comes the generated protocol implementation diff --git a/src/private/protocol.xml b/src/private/protocol.xml --- a/src/private/protocol.xml +++ b/src/private/protocol.xml @@ -1,5 +1,5 @@ - + @@ -47,9 +47,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + @@ -230,12 +260,12 @@ - + - + @@ -605,7 +635,7 @@ - + @@ -805,7 +835,6 @@ - @@ -817,6 +846,10 @@ + + + + @@ -832,14 +865,13 @@ - - - + + @@ -852,9 +884,9 @@ - - + + @@ -866,10 +898,8 @@ - - - - + + @@ -889,17 +919,20 @@ - - - - - - - - - - - + + + + + + + + + + + + + + @@ -1032,6 +1065,15 @@ + + + + + + + + + @@ -1056,6 +1098,8 @@ + + diff --git a/src/private/protocol_exception_p.h b/src/private/protocol_exception_p.h --- a/src/private/protocol_exception_p.h +++ b/src/private/protocol_exception_p.h @@ -25,6 +25,7 @@ #include "akonadiprivate_export.h" #include +#include #include @@ -37,7 +38,9 @@ explicit ProtocolException(const char *what) : std::exception() , mWhat(what) - {} + { + std::cerr << "ProtocolException thrown:" << what << std::endl; + } const char *what() const throw() override { return mWhat.constData(); diff --git a/src/private/protocol_p.h b/src/private/protocol_p.h --- a/src/private/protocol_p.h +++ b/src/private/protocol_p.h @@ -24,6 +24,7 @@ #include "akonadiprivate_export.h" +#include #include #include #include @@ -50,7 +51,7 @@ class Command; class Response; -class FetchScope; +class ItemFetchScope; class ScopeContext; class ChangeNotification; @@ -71,6 +72,8 @@ Akonadi::Protocol::Command &cmd); AKONADIPRIVATE_EXPORT QDebug operator<<(QDebug dbg, const Akonadi::Protocol::Command &cmd); +AKONADIPRIVATE_EXPORT void toJson(const Command *cmd, QJsonObject &json); + using CommandPtr = QSharedPointer; class AKONADIPRIVATE_EXPORT Command @@ -141,23 +144,26 @@ _ResponseBit = 0x80 // reserved }; - explicit Command(); - explicit Command(const Command &other); - ~Command(); + explicit Command() = default; + explicit Command(const Command &) = default; + Command(Command &&) = default; + ~Command() = default; - Command &operator=(const Command &other); + Command &operator=(const Command &) = default; + Command &operator=(Command &&) = default; bool operator==(const Command &other) const; inline bool operator!=(const Command &other) const { return !operator==(other); } inline Type type() const { return static_cast(mType & ~_ResponseBit); } inline bool isValid() const { return type() != Invalid; } inline bool isResponse() const { return mType & _ResponseBit; } + void toJson(QJsonObject &stream) const; protected: explicit Command(quint8 type); - quint8 mType; + quint8 mType = Invalid; // unused 7 bytes private: @@ -167,6 +173,7 @@ friend AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator>>(Akonadi::Protocol::DataStream &stream, Akonadi::Protocol::Command &cmd); friend AKONADIPRIVATE_EXPORT QDebug operator<<(::QDebug dbg, const Akonadi::Protocol::Command &cmd); + friend AKONADIPRIVATE_EXPORT void toJson(const Akonadi::Protocol::Command *cmd, QJsonObject &json); }; } // namespace Protocol @@ -194,8 +201,10 @@ { public: explicit Response(); - explicit Response(const Response &other); - Response &operator=(const Response &other); + explicit Response(const Response &) = default; + Response(Response &&) = default; + Response &operator=(const Response &) = default; + Response &operator=(Response &&) = default; inline void setError(int code, const QString &message) { @@ -211,6 +220,7 @@ inline int errorCode() const { return mErrorCode; } inline QString errorMessage() const { return mErrorMsg; } + void toJson(QJsonObject &json) const; protected: explicit Response(Command::Type type); @@ -273,14 +283,14 @@ AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator<<( Akonadi::Protocol::DataStream &stream, - const Akonadi::Protocol::FetchScope &scope); + const Akonadi::Protocol::ItemFetchScope &scope); AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator>>( Akonadi::Protocol::DataStream &stream, - Akonadi::Protocol::FetchScope &scope); -AKONADIPRIVATE_EXPORT QDebug operator<<(QDebug dbg, const Akonadi::Protocol::FetchScope &scope); + Akonadi::Protocol::ItemFetchScope &scope); +AKONADIPRIVATE_EXPORT QDebug operator<<(QDebug dbg, const Akonadi::Protocol::ItemFetchScope &scope); -class AKONADIPRIVATE_EXPORT FetchScope +class AKONADIPRIVATE_EXPORT ItemFetchScope { public: enum FetchFlag : int { @@ -308,14 +318,16 @@ AllAncestors }; - explicit FetchScope(); - FetchScope(const FetchScope &other); - ~FetchScope(); + explicit ItemFetchScope() = default; + ItemFetchScope(const ItemFetchScope &) = default; + ItemFetchScope(ItemFetchScope &&other) = default; + ~ItemFetchScope() = default; - FetchScope &operator=(const FetchScope &other); + ItemFetchScope &operator=(const ItemFetchScope &) = default; + ItemFetchScope &operator=(ItemFetchScope &&) = default; - bool operator==(const FetchScope &other) const; - inline bool operator!=(const FetchScope &other) const { return !operator==(other); } + bool operator==(const ItemFetchScope &other) const; + inline bool operator!=(const ItemFetchScope &other) const { return !operator==(other); } inline void setRequestedParts(const QVector &requestedParts) { @@ -351,25 +363,26 @@ void setFetch(FetchFlags attributes, bool fetch = true); bool fetch(FetchFlags flags) const; + void toJson(QJsonObject &json) const; private: - AncestorDepth mAncestorDepth; + AncestorDepth mAncestorDepth = NoAncestor; // 2 bytes free - FetchFlags mFlags; + FetchFlags mFlags = None; QVector mRequestedParts; QDateTime mChangedSince; QSet mTagFetchScope; friend AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator<<(Akonadi::Protocol::DataStream &stream, - const Akonadi::Protocol::FetchScope &scope); + const Akonadi::Protocol::ItemFetchScope &scope); friend AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator>>(Akonadi::Protocol::DataStream &stream, - Akonadi::Protocol::FetchScope &scope); - friend AKONADIPRIVATE_EXPORT QDebug operator<<(QDebug dbg, const Akonadi::Protocol::FetchScope &scope); + Akonadi::Protocol::ItemFetchScope &scope); + friend AKONADIPRIVATE_EXPORT QDebug operator<<(QDebug dbg, const Akonadi::Protocol::ItemFetchScope &scope); }; } // namespace Protocol } // namespace Akonadi -Q_DECLARE_OPERATORS_FOR_FLAGS(Akonadi::Protocol::FetchScope::FetchFlags) +Q_DECLARE_OPERATORS_FOR_FLAGS(Akonadi::Protocol::ItemFetchScope::FetchFlags) namespace Akonadi { namespace Protocol { @@ -391,13 +404,15 @@ Tag }; - explicit ScopeContext(); + explicit ScopeContext() = default; ScopeContext(Type type, qint64 id); ScopeContext(Type type, const QString &id); - ScopeContext(const ScopeContext &other); - ~ScopeContext(); + ScopeContext(const ScopeContext &) = default; + ScopeContext(ScopeContext &&) = default; + ~ScopeContext() = default; - ScopeContext &operator=(const ScopeContext &other); + ScopeContext &operator=(const ScopeContext &) = default; + ScopeContext &operator=(ScopeContext &&) = default; bool operator==(const ScopeContext &other) const; inline bool operator!=(const ScopeContext &other) const { return !operator==(other); } @@ -429,6 +444,7 @@ return hasContextRID(type) ? ctx(type).toString() : QString(); } + void toJson(QJsonObject &json) const; private: QVariant mColCtx; QVariant mTagCtx; @@ -457,10 +473,12 @@ } // namespace Protocol } // namespace akonadi - namespace Akonadi { namespace Protocol { +class FetchItemsResponse; +typedef QSharedPointer FetchItemsResponsePtr; + AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator<<( Akonadi::Protocol::DataStream &stream, const Akonadi::Protocol::ChangeNotification &ntf); @@ -475,71 +493,45 @@ class AKONADIPRIVATE_EXPORT ChangeNotification : public Command { public: - class Item - { - public: - inline Item() - : id(-1) - {} - - inline Item(qint64 id, const QString &remoteId, const QString &remoteRevision, const QString &mimeType) - : id(id) - , remoteId(remoteId) - , remoteRevision(remoteRevision) - , mimeType(mimeType) - {} - - inline bool operator==(const Item &other) const - { - return id == other.id - && remoteId == other.remoteId - && remoteRevision == other.remoteRevision - && mimeType == other.mimeType; - } - - qint64 id; - QString remoteId; - QString remoteRevision; - QString mimeType; - }; - - inline static QList itemsToUids(const QVector &items) { - QList rv; - rv.reserve(items.size()); - std::transform(items.cbegin(), items.cend(), std::back_inserter(rv), - [](const Item &item) { return item.id; }); - return rv; - } + static QList itemsToUids(const QVector &items); class Relation { public: - inline Relation() - : leftId(-1) - , rightId(-1) - { - } - + Relation() = default; + Relation(const Relation &) = default; + Relation(Relation &&) = default; inline Relation(qint64 leftId, qint64 rightId, const QString &type) : leftId(leftId) , rightId(rightId) , type(type) { } + Relation &operator=(const Relation &) = default; + Relation &operator=(Relation &&) = default; + inline bool operator==(const Relation &other) const { return leftId == other.leftId && rightId == other.rightId && type == other.type; } - qint64 leftId; - qint64 rightId; + void toJson(QJsonObject &json) const + { + json[QStringLiteral("leftId")] = leftId; + json[QStringLiteral("rightId")] = rightId; + json[QStringLiteral("type")] = type; + } + + qint64 leftId = -1; + qint64 rightId = -1; QString type; }; - ChangeNotification &operator=(const ChangeNotification &other); + ChangeNotification &operator=(const ChangeNotification &) = default; + ChangeNotification &operator=(ChangeNotification &&) = default; bool operator==(const ChangeNotification &other) const; inline bool operator!=(const ChangeNotification &other) const { return !operator==(other); } @@ -556,9 +548,12 @@ static bool appendAndCompress(ChangeNotificationList &list, const ChangeNotificationPtr &msg); + void toJson(QJsonObject &json) const; protected: + explicit ChangeNotification() = default; explicit ChangeNotification(Command::Type type); - ChangeNotification(const ChangeNotification &other); + ChangeNotification(const ChangeNotification &) = default; + ChangeNotification(ChangeNotification &&) = default; QByteArray mSessionId; @@ -582,11 +577,6 @@ // TODO: Internalize? -AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator<<( - Akonadi::Protocol::DataStream &stream, - const Akonadi::Protocol::ChangeNotification::Item &item); -AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator>>( - Akonadi::Protocol::DataStream &stream, Akonadi::Protocol::ChangeNotification::Item &item); AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator<<( Akonadi::Protocol::DataStream &stream, const Akonadi::Protocol::ChangeNotification::Relation &relation); diff --git a/src/private/protocolgen/cppgenerator.cpp b/src/private/protocolgen/cppgenerator.cpp --- a/src/private/protocolgen/cppgenerator.cpp +++ b/src/private/protocolgen/cppgenerator.cpp @@ -238,7 +238,7 @@ Q_ASSERT(enumChild->type() == Node::EnumValue); const auto valueNode = static_cast(enumChild); mHeader << " " << valueNode->name(); - if (valueNode->value() >= 0) { + if (!valueNode->value().isEmpty()) { mHeader << " = " << valueNode->value(); } mHeader << ",\n"; @@ -299,14 +299,16 @@ mHeader << ", "; } } - mHeader << ");\n"; + mHeader << ");"; } } - mHeader << " " << node->className() << "(const " << node->className() << " &other);\n" - " ~" << node->className() << "();\n" + mHeader << " " << node->className() << "(const " << node->className() << " &) = default;\n" + " " << node->className() << "(" << node->className() << " &&) = default;\n" + " ~" << node->className() << "() = default;\n" "\n" - " " << node->className() << " &operator=(const " << node->className() << " &other);\n" + " " << node->className() << " &operator=(const " << node->className() << " &) = default;\n" + " " << node->className() << " &operator=(" << node->className() << " &&) = default;\n" " bool operator==(const " << node->className() << " &other) const;\n" " inline bool operator!=(const " << node->className() << " &other) const { return !operator==(other); }\n"; @@ -342,17 +344,34 @@ mHeader << " inline void " << prop->setterName() << varType << prop->name() << ") { " << prop->mVariableName() << " = " << prop->name() << "; }\n"; + if (!TypeHelper::isNumericType(prop->type()) && !TypeHelper::isBoolType(prop->type())) { + mHeader << " inline void " << prop->setterName() << "(" << prop->type() << " &&" << prop->name() << ") { " + << prop->mVariableName() << " = std::move(" << prop->name() << "); }\n"; + } } } mHeader << "\n"; } } + mHeader << " void toJson(QJsonObject &stream) const;\n"; // End of class mHeader << "protected:\n"; const auto properties = node->properties(); for (auto prop : properties) { - mHeader << " " << prop->type() << " " << prop->mVariableName() << ";\n"; + mHeader << " " << prop->type() << " " << prop->mVariableName(); + const auto defaultValue = prop->defaultValue(); + const bool isDefaultValue = !defaultValue.isEmpty(); + const bool isNumeric = TypeHelper::isNumericType(prop->type()); + const bool isBool = TypeHelper::isBoolType(prop->type()); + if (isDefaultValue) { + mHeader << " = " << defaultValue; + } else if (isNumeric) { + mHeader << " = 0"; + } else if (isBool) { + mHeader << " = false"; + } + mHeader << ";\n"; } mHeader << "\n" @@ -436,64 +455,14 @@ if (arg != args.cend()) { mImpl << " " << startChar << " " << prop->mVariableName() << "(" << arg->name << ")\n"; startChar = ','; - } else { - const bool isDefaultValue = !defaultValue.isEmpty(); - const bool isNumeric = TypeHelper::isNumericType(prop->type()); - const bool isBool = TypeHelper::isBoolType(prop->type()); - if (isDefaultValue || isNumeric || isBool) { - mImpl << " " << startChar << " " << prop->mVariableName() << "("; - startChar = ','; - if (isDefaultValue) { - mImpl << defaultValue; - } else if (isNumeric) { - mImpl << "0"; - } else if (isBool) { - mImpl << "false"; - } - mImpl << ")\n"; - } } } mImpl << "{\n" "}\n" "\n"; } } - // Copy ctor - mImpl << node->className() << "::" << node->className() << "(const " << node->className() << "&other)\n"; - char startChar = ':'; - if (!parentClass.isEmpty()) { - mImpl << " : " << parentClass << "(other)\n"; - startChar = ','; - } - for (auto prop : properties) { - mImpl << " " << startChar << " " << prop->mVariableName() << "(other." << prop->mVariableName() << ")\n"; - startChar = ','; - } - mImpl << "{\n" - "}\n" - "\n"; - - // Dtor - mImpl << node->className() << "::~" << node->className() << "()\n" - "{\n" - "}\n" - "\n"; - - // Assignment operator - mImpl << node->className() << " &" << node->className() << "::operator=(const " << node->className() << " &other)\n" - << "{\n"; - if (!parentClass.isEmpty()) { - mImpl << " " << parentClass << "::operator=(other);\n"; - } - for (auto prop : properties) { - mImpl << " " << prop->mVariableName() << " = other." << prop->mVariableName() << ";\n"; - } - mImpl << " return *this;\n" - "}\n" - "\n"; - // Comparision operator mImpl << "bool " << node->className() << "::operator==(const " << node->className() << " &other) const\n" @@ -503,7 +472,13 @@ mImpl << " && " << parentClass << "::operator==(other)\n"; } for (auto prop : properties) { - mImpl << " && " << prop->mVariableName() << " == other." << prop->mVariableName() << "\n"; + if (prop->isPointer()) { + mImpl << " && *" << prop->mVariableName() << " == *other." << prop->mVariableName() << "\n"; + } else if (TypeHelper::isContainer(prop->type())) { + mImpl << " && containerComparator(" << prop->mVariableName() << ", other." << prop->mVariableName() << ")\n"; + } else { + mImpl << " && " << prop->mVariableName() << " == other." << prop->mVariableName() << "\n"; + } } mImpl << " ;\n" "}\n" @@ -559,7 +534,7 @@ if (!parentClass.isEmpty()) { mImpl << " stream << static_cast(obj);\n"; } - for (auto prop : serializeProperties) { + for (auto prop : qAsConst(serializeProperties)) { writeImplSerializer(prop, "<<"); } mImpl << " return stream;\n" @@ -572,7 +547,7 @@ if (!parentClass.isEmpty()) { mImpl << " stream >> static_cast<" << parentClass << " &>(obj);\n"; } - for (auto prop : serializeProperties) { + for (auto prop : qAsConst(serializeProperties)) { writeImplSerializer(prop, ">>"); } mImpl << " return stream;\n" @@ -583,17 +558,142 @@ mImpl << "QDebug operator<<(QDebug dbg, const " << node->className() << " &obj)\n" "{\n"; if (!parentClass.isEmpty()) { - mImpl << " return dbg.noquote() << static_cast(obj)\n"; + mImpl << " dbg.noquote() << static_cast(obj)\n"; } else { - mImpl << " return dbg.noquote()\n"; - } - - for (auto prop : serializeProperties) { - mImpl << " << \"" << prop->name() << ":\" << obj." << prop->mVariableName() << " << \"\\n\"\n"; + mImpl << " dbg.noquote()\n"; + } + + for (auto prop : qAsConst(serializeProperties)) { + if (prop->isPointer()) { + mImpl << " << \"" << prop->name() << ":\" << *obj." << prop->mVariableName() << " << \"\\n\"\n"; + } else if (TypeHelper::isContainer(prop->type())) { + mImpl << " << \"" << prop->name() << ": [\\n\";\n" + " for (const auto &type : qAsConst(obj." << prop->mVariableName() << ")) {\n" + " dbg.noquote() << \" \" << "; + if (TypeHelper::isPointerType(TypeHelper::containerType(prop->type()))) { + mImpl << "*type"; + } else { + mImpl << "type"; + } + mImpl << " << \"\\n\";\n" + " }\n" + " dbg.noquote() << \"]\\n\"\n"; + } else { + mImpl << " << \"" << prop->name() << ":\" << obj." << prop->mVariableName() << " << \"\\n\"\n"; + } } mImpl << " ;\n" + " return dbg;\n" "}\n" "\n"; + + // toJson + mImpl << "void " << node->className() << "::toJson(QJsonObject &json) const\n" + "{\n"; + if (!parentClass.isEmpty()) { + mImpl << " static_cast(this)->toJson(json);\n"; + } else if (serializeProperties.isEmpty()) { + mImpl << " Q_UNUSED(json);\n"; + } + for (auto prop : qAsConst(serializeProperties)) { + if (prop->isPointer()) { + mImpl << " {\n" + " QJsonObject jsonObject;\n" + " " << prop->mVariableName() << "->toJson(jsonObject);\n" + " json[QStringLiteral(\"" << prop->name() << "\")] = jsonObject;\n" + " }\n"; + } else if (TypeHelper::isContainer(prop->type())) { + const auto &containerType = TypeHelper::containerType(prop->type()); + mImpl << " {\n" + " QJsonArray jsonArray;\n" + " for (const auto &type : qAsConst(" << prop->mVariableName() << ")) {\n"; + if (TypeHelper::isPointerType(containerType)) { + mImpl << " QJsonObject jsonObject;\n" + " type->toJson(jsonObject); /* " << containerType << " */\n" + " jsonArray.append(jsonObject);\n"; + } else if (TypeHelper::isNumericType(containerType)) { + mImpl << " jsonArray.append(type); /* "<< containerType << " */\n"; + } else if (TypeHelper::isBoolType(containerType)) { + mImpl << " jsonArray.append(type); /* "<< containerType << " */\n"; + } else if (containerType == QStringLiteral("QByteArray")) { + mImpl << " jsonArray.append(QString::fromUtf8(type)); /* "<< containerType << "*/\n"; + } else if (TypeHelper::isBuiltInType(containerType)) { + if (TypeHelper::containerType(prop->type()) == QStringLiteral("Akonadi::Protocol::ChangeNotification::Relation")) { + mImpl << " QJsonObject jsonObject;\n" + " type.toJson(jsonObject); /* " << containerType << " */\n" + " jsonArray.append(jsonObject);\n"; + } else { + mImpl << " jsonArray.append(type); /* "<< containerType << " */\n"; + } + } else { + mImpl << " QJsonObject jsonObject;\n" + " type.toJson(jsonObject); /* " << containerType << " */\n" + " jsonArray.append(jsonObject);\n"; + } + mImpl << " }\n" + << " json[QStringLiteral(\"" << prop->name() << "\")] = jsonArray;\n" + << " }\n"; + } else if (prop->type() == QStringLiteral("uint")) { + mImpl << " json[QStringLiteral(\"" << prop->name() << "\")] = static_cast(" << prop->mVariableName() << ");/* "<< prop->type() << " */\n"; + } else if (TypeHelper::isNumericType(prop->type())) { + mImpl << " json[QStringLiteral(\"" << prop->name() << "\")] = " << prop->mVariableName() << ";/* "<< prop->type() << " */\n"; + } else if (TypeHelper::isBoolType(prop->type())) { + mImpl << " json[QStringLiteral(\"" << prop->name() << "\")] = " << prop->mVariableName() << ";/* "<< prop->type() << " */\n"; + } else if (TypeHelper::isBuiltInType(prop->type())) { + if (prop->type() == QStringLiteral("QStringList")) { + mImpl << " json[QStringLiteral(\"" << prop->name() << "\")] = QJsonArray::fromStringList(" << prop->mVariableName() << ");/* "<< prop->type() << " */\n"; + } else if (prop->type() == QStringLiteral("QDateTime")) { + mImpl << " json[QStringLiteral(\"" << prop->name() << "\")] = " << prop->mVariableName() << ".toString()/* "<< prop->type() << " */;\n"; + } else if (prop->type() == QStringLiteral("QByteArray")) { + mImpl << " json[QStringLiteral(\"" << prop->name() << "\")] = QString::fromUtf8(" << prop->mVariableName() << ")/* "<< prop->type() << " */;\n"; + } else if (prop->type() == QStringLiteral("Scope")) { + mImpl << " {\n" + " QJsonObject jsonObject;\n" + " " << prop->mVariableName() << ".toJson(jsonObject); /* " << prop->type() << " */\n" + " json[QStringLiteral(\"" << prop->name() << "\")] = " << "jsonObject;\n" + " }\n"; + } else if (prop->type() == QStringLiteral("Tristate")) { + mImpl << " switch (" << prop->mVariableName() << ") {\n;" + " case Tristate::True:\n" + " json[QStringLiteral(\"" << prop->name() << "\")] = QStringLiteral(\"True\");\n" + " break;\n" + " case Tristate::False:\n" + " json[QStringLiteral(\"" << prop->name() << "\")] = QStringLiteral(\"False\");\n" + " break;\n" + " case Tristate::Undefined:\n" + " json[QStringLiteral(\"" << prop->name() << "\")] = QStringLiteral(\"Undefined\");\n" + " break;\n" + " }\n"; + } else if (prop->type() == QStringLiteral("Akonadi::Protocol::Attributes")) { + mImpl << " {\n" + " QJsonObject jsonObject;\n" + " auto i = " << prop->mVariableName() << ".constBegin();\n" + " const auto &end = " << prop->mVariableName() << ".constEnd();\n" + " while (i != end) {\n" + " jsonObject[QString::fromUtf8(i.key())] = QString::fromUtf8(i.value());\n" + " ++i;\n" + " }\n" + " json[QStringLiteral(\"" << prop->name() << "\")] = jsonObject;\n" + " }\n"; + } else if (prop->type() == QStringLiteral("ModifySubscriptionCommand::ModifiedParts") || + prop->type() == QStringLiteral("ModifyTagCommand::ModifiedParts") || + prop->type() == QStringLiteral("ModifyCollectionCommand::ModifiedParts") || + prop->type() == QStringLiteral("ModifyItemsCommand::ModifiedParts") || + prop->type() == QStringLiteral("CreateItemCommand::MergeModes")) { + mImpl << " json[QStringLiteral(\"" << prop->name() << "\")] = static_cast(" << prop->mVariableName() << ");/* "<< prop->type() << "*/\n"; + } else { + mImpl << " json[QStringLiteral(\"" << prop->name() << "\")] = " << prop->mVariableName() << ";/* "<< prop->type() << "*/\n"; + } + } else { + mImpl << " {\n" + " QJsonObject jsonObject;\n" + " " << prop->mVariableName() << ".toJson(jsonObject); /* " << prop->type() << " */\n" + " json[QStringLiteral(\"" << prop->name() << "\")] = jsonObject;\n" + " }\n"; + } + } + mImpl << "}\n" + "\n"; } void CppGenerator::writeImplPropertyDependencies(const PropertyNode* node) diff --git a/src/private/protocolgen/nodetree.h b/src/private/protocolgen/nodetree.h --- a/src/private/protocolgen/nodetree.h +++ b/src/private/protocolgen/nodetree.h @@ -144,12 +144,12 @@ EnumValueNode(const QString &name, EnumNode *parent); QString name() const; - void setValue(int value); - int value() const; + void setValue(const QString &value); + QString value() const; private: QString mName; - int mValue; + QString mValue; }; @@ -177,6 +177,8 @@ bool asReference() const; void setAsReference(bool asReference); + bool isPointer() const; + QMultiMap dependencies() const; void addDependency(const QString &enumVar, const QString &enumValue); diff --git a/src/private/protocolgen/nodetree.cpp b/src/private/protocolgen/nodetree.cpp --- a/src/private/protocolgen/nodetree.cpp +++ b/src/private/protocolgen/nodetree.cpp @@ -20,6 +20,7 @@ #include "nodetree.h" #include "cpphelper.h" +#include "typehelper.h" Node::Node(NodeType type, Node *parent) : mParent(parent) @@ -201,20 +202,20 @@ EnumValueNode::EnumValueNode(const QString &name, EnumNode *parent) : Node(EnumValue, parent) , mName(name) - , mValue(-1) + , mValue() {} QString EnumValueNode::name() const { return mName; } -void EnumValueNode::setValue(int value) +void EnumValueNode::setValue(const QString &value) { mValue = value; } -int EnumValueNode::value() const +QString EnumValueNode::value() const { return mValue; } @@ -270,6 +271,11 @@ mAsReference = asReference; } +bool PropertyNode::isPointer() const +{ + return TypeHelper::isPointerType(mType); +} + QMultiMap PropertyNode::dependencies() const { return mDepends; diff --git a/src/private/protocolgen/typehelper.h b/src/private/protocolgen/typehelper.h --- a/src/private/protocolgen/typehelper.h +++ b/src/private/protocolgen/typehelper.h @@ -37,6 +37,7 @@ QString containerType(const QString &type); QString containerName(const QString &type); +bool isPointerType(const QString &type); } #endif diff --git a/src/private/protocolgen/typehelper.cpp b/src/private/protocolgen/typehelper.cpp --- a/src/private/protocolgen/typehelper.cpp +++ b/src/private/protocolgen/typehelper.cpp @@ -90,3 +90,8 @@ const int tplB = type.indexOf(QLatin1Char('<')); return type.left(tplB); } + +bool TypeHelper::isPointerType(const QString &type) +{ + return type.endsWith(QLatin1String("Ptr")); +} diff --git a/src/private/protocolgen/xmlparser.cpp b/src/private/protocolgen/xmlparser.cpp --- a/src/private/protocolgen/xmlparser.cpp +++ b/src/private/protocolgen/xmlparser.cpp @@ -211,7 +211,7 @@ auto valueNode = new EnumValueNode(attrs.value(QLatin1String("name")).toString(), enumNode); if (attrs.hasAttribute(QLatin1String("value"))) { - valueNode->setValue(attrs.value(QLatin1String("value")).toInt()); + valueNode->setValue(attrs.value(QLatin1String("value")).toString()); } return true; diff --git a/src/private/scope.cpp b/src/private/scope.cpp --- a/src/private/scope.cpp +++ b/src/private/scope.cpp @@ -21,6 +21,8 @@ #include "scope_p.h" #include "datastream_p_p.h" +#include +#include #include #include "imapset_p.h" @@ -104,6 +106,12 @@ return id == other.id && remoteId == other.remoteId; } +void Scope::HRID::toJson(QJsonObject &json) const +{ + json[QStringLiteral("ID")] = id; + json[QStringLiteral("RemoteID")] = remoteId; +} + Scope::Scope() : d(new ScopePrivate) { @@ -305,6 +313,40 @@ return d->gidSet.at(0); } +void Scope::toJson(QJsonObject &json) const +{ + switch (scope()) { + case Scope::Uid: + json[QStringLiteral("type")] = QStringLiteral("UID"); + json[QStringLiteral("value")] = QString::fromUtf8(uidSet().toImapSequenceSet()); + break; + case Scope::Rid: + json[QStringLiteral("type")] = QStringLiteral("RID"); + json[QStringLiteral("value")] = QJsonArray::fromStringList(ridSet()); + break; + case Scope::Gid: + json[QStringLiteral("type")] = QStringLiteral("GID"); + json[QStringLiteral("value")] = QJsonArray::fromStringList(gidSet()); + break; + case Scope::HierarchicalRid: + { + const auto &chain = hridChain(); + QJsonArray hridArray; + for (const auto &hrid : chain) { + QJsonObject obj; + hrid.toJson(obj); + hridArray.append(obj); + } + json[QStringLiteral("type")] = QStringLiteral("HRID"); + json[QStringLiteral("value")] = hridArray; + } + break; + default: + json[QStringLiteral("type")] = QStringLiteral("invalid"); + json[QStringLiteral("value")] = QJsonValue(static_cast(scope())); + } +} + Protocol::DataStream &operator<<(Protocol::DataStream &stream, const Akonadi::Scope &scope) { stream << (quint8) scope.d->scope; diff --git a/src/private/scope_p.h b/src/private/scope_p.h --- a/src/private/scope_p.h +++ b/src/private/scope_p.h @@ -28,6 +28,7 @@ #include #include +class QJsonObject; class QStringList; namespace Akonadi @@ -66,6 +67,8 @@ bool isEmpty() const; bool operator==(const HRID &other) const; + void toJson(QJsonObject &json) const; + qint64 id; QString remoteId; }; @@ -110,6 +113,7 @@ QString rid() const; QString gid() const; + void toJson(QJsonObject &json) const; private: QSharedDataPointer d; friend class ScopePrivate; diff --git a/src/private/standarddirs.cpp b/src/private/standarddirs.cpp --- a/src/private/standarddirs.cpp +++ b/src/private/standarddirs.cpp @@ -1,5 +1,6 @@ /* Copyright (c) 2011 Volker Krause + Copyright (c) 2018 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 @@ -18,65 +19,180 @@ */ #include "standarddirs_p.h" -#include "xdgbasedirs_p.h" #include "instance_p.h" +#include "akonadi-prefix.h" +#include +#include #include +#include +#include +#include using namespace Akonadi; -QString StandardDirs::configFile(const QString &configFile, Akonadi::XdgBaseDirs::FileAccessMode openMode) +namespace { + +QString buildFullRelPath(const char *resource, const QString &relPath) { - const QString savePath = StandardDirs::saveDir("config") + QLatin1Char('/') + configFile; + QString fullRelPath = QStringLiteral("/akonadi"); +#ifdef Q_OS_WIN + // On Windows all Generic*Location fall into ~/AppData/Local so we need to disambiguate + // inside the "akonadi" folder whether it's data or config. + fullRelPath += QLatin1Char('/') + QString::fromLocal8Bit(resource); +#else + Q_UNUSED(resource); +#endif + + if (Akonadi::Instance::hasIdentifier()) { + fullRelPath += QStringLiteral("/instance/") + Akonadi::Instance::identifier(); + } + if (!relPath.isEmpty()) { + fullRelPath += QLatin1Char('/') + relPath; + } + return fullRelPath; +} + +} - if (openMode == XdgBaseDirs::WriteOnly) { +QString StandardDirs::configFile(const QString &configFile, FileAccessMode openMode) +{ + const QString savePath = StandardDirs::saveDir("config") + QLatin1Char('/') + configFile; + if (openMode == WriteOnly) { return savePath; } - QString path = XdgBaseDirs::findResourceFile("config", QStringLiteral("akonadi/") + configFile); + + auto path = QStandardPaths::locate(QStandardPaths::GenericConfigLocation, QStringLiteral("akonadi/") + configFile); // HACK: when using instance namespaces, ignore the non-namespaced file - if (Akonadi::Instance::hasIdentifier() && path.startsWith(XdgBaseDirs::homePath("config"))) { + if (Akonadi::Instance::hasIdentifier() && path.startsWith(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation))) { path.clear(); } if (path.isEmpty()) { return savePath; - } else if (openMode == XdgBaseDirs::ReadOnly || path == savePath) { + } else if (openMode == ReadOnly || path == savePath) { return path; } // file found in system paths and mode is ReadWrite, thus // we copy to the home path location and return this path - QFile systemFile(path); - - systemFile.copy(savePath); - + QFile::copy(path, savePath); return savePath; } -QString StandardDirs::serverConfigFile(XdgBaseDirs::FileAccessMode openMode) +QString StandardDirs::serverConfigFile(FileAccessMode openMode) { return configFile(QStringLiteral("akonadiserverrc"), openMode); } -QString StandardDirs::connectionConfigFile(XdgBaseDirs::FileAccessMode openMode) +QString StandardDirs::connectionConfigFile(FileAccessMode openMode) { return configFile(QStringLiteral("akonadiconnectionrc"), openMode); } -QString StandardDirs::agentConfigFile(XdgBaseDirs::FileAccessMode openMode) +QString StandardDirs::agentsConfigFile(FileAccessMode openMode) { return configFile(QStringLiteral("agentsrc"), openMode); } +QString StandardDirs::agentConfigFile(const QString &identifier, FileAccessMode openMode) +{ + return configFile(QStringLiteral("agent_config_") + identifier, openMode); +} + QString StandardDirs::saveDir(const char *resource, const QString &relPath) { - QString fullRelPath = QStringLiteral("akonadi"); - if (Akonadi::Instance::hasIdentifier()) { - fullRelPath += QStringLiteral("/instance/") + Akonadi::Instance::identifier(); + const QString fullRelPath = buildFullRelPath(resource, relPath); + if (qstrncmp(resource, "config", 6) == 0) { + return QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + fullRelPath; + } else if (qstrncmp(resource, "data", 4) == 0) { + return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + fullRelPath; + } else { + qt_assert_x(__FUNCTION__, "Invalid resource type", __FILE__, __LINE__); + return {}; } - if (!relPath.isEmpty()) { - fullRelPath += QLatin1Char('/') + relPath; +} + +QString StandardDirs::locateResourceFile(const char *resource, const QString &relPath) +{ + const QString fullRelPath = buildFullRelPath(resource, relPath); + QVector userLocations; + QStandardPaths::StandardLocation genericLocation; + QString fallback; + if (qstrncmp(resource, "config", 6) == 0) { + userLocations = { QStandardPaths::AppConfigLocation, + QStandardPaths::ConfigLocation }; + genericLocation = QStandardPaths::GenericConfigLocation; + fallback = QDir::toNativeSeparators(QStringLiteral(AKONADIPREFIX "/" AKONADICONFIG)); + } else if (qstrncmp(resource, "data", 4) == 0) { + userLocations = { QStandardPaths::AppLocalDataLocation, + QStandardPaths::AppDataLocation }; + genericLocation = QStandardPaths::GenericDataLocation; + fallback = QDir::toNativeSeparators(QStringLiteral(AKONADIPREFIX "/" AKONADIDATA)); + } else { + qt_assert_x(__FUNCTION__, "Invalid resource type", __FILE__, __LINE__); + return {}; + } + + const auto locateFile = [](QStandardPaths::StandardLocation location, const QString &relPath) -> QString { + const auto path = QStandardPaths::locate(location, relPath); + if (!path.isEmpty()) { + QFileInfo file(path); + if (file.exists() && file.isFile() && file.isReadable()) { + return path; + } + } + return {}; + }; + + // Always honor instance in user-specific locations + for (const auto location : qAsConst(userLocations)) { + const auto path = locateFile(location, fullRelPath); + if (!path.isEmpty()) { + return path; + } + } + + // First try instance-specific path in generic locations + auto path = locateFile(genericLocation, fullRelPath); + if (!path.isEmpty()) { + return path; + } + + // Fallback to global instance path in generic locations + path = locateFile(genericLocation, QStringLiteral("/akonadi/") + relPath); + if (!path.isEmpty()) { + return path; + } + + QFile f(fallback + QStringLiteral("/akonadi/") + relPath); + if (f.exists()) { + return f.fileName(); + } + + return {}; +} + +QStringList StandardDirs::locateAllResourceDirs(const QString &relPath) +{ + auto dirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, relPath, + QStandardPaths::LocateDirectory); + + const auto fallback = QDir::toNativeSeparators(QStringLiteral(AKONADIPREFIX "/" AKONADIDATA "/") + relPath); + if (!dirs.contains(fallback)) { + if (QDir::root().exists(fallback)) { + dirs.push_back(fallback); + } + } + return dirs; +} + +QString StandardDirs::findExecutable(const QString &executableName) +{ + QString executable = QStandardPaths::findExecutable(executableName, {qApp->applicationDirPath()}); + if (executable.isEmpty()) { + executable = QStandardPaths::findExecutable(executableName); } - return XdgBaseDirs::saveDir(resource, fullRelPath); + return executable; } diff --git a/src/private/standarddirs_p.h b/src/private/standarddirs_p.h --- a/src/private/standarddirs_p.h +++ b/src/private/standarddirs_p.h @@ -20,45 +20,97 @@ #ifndef AKSTANDARDDIRS_H #define AKSTANDARDDIRS_H -#include "xdgbasedirs_p.h" #include "akonadiprivate_export.h" +#include + namespace Akonadi { /** - * Convenience wrappers on top of XdgBaseDirs that are instance namespace aware. + * Convenience wrappers on top of QStandardPaths that are instance namespace aware. * @since 1.7 */ namespace StandardDirs { + +/** + * @brief Open mode flags for resource files + * + * FileAccessMode is a typedef for QFlags. It stores + * a OR combination of FileAccessFlag values + */ +enum FileAccessMode { + ReadOnly = 0x1, + WriteOnly = 0x2, + ReadWrite = ReadOnly | WriteOnly +}; + /** * Returns path to the config file @p configFile. */ -AKONADIPRIVATE_EXPORT QString configFile(const QString &configFile, Akonadi::XdgBaseDirs::FileAccessMode openMode = Akonadi::XdgBaseDirs::ReadOnly); +AKONADIPRIVATE_EXPORT QString configFile(const QString &configFile, FileAccessMode openMode = ReadOnly); /** * Returns the full path to the server config file (akonadiserverrc). */ -AKONADIPRIVATE_EXPORT QString serverConfigFile(Akonadi::XdgBaseDirs::FileAccessMode openMode = Akonadi::XdgBaseDirs::ReadOnly); +AKONADIPRIVATE_EXPORT QString serverConfigFile(FileAccessMode openMode = ReadOnly); /** * Returns the full path to the connection config file (akonadiconnectionrc). */ -AKONADIPRIVATE_EXPORT QString connectionConfigFile(Akonadi::XdgBaseDirs::FileAccessMode openMode = Akonadi::XdgBaseDirs::ReadOnly); +AKONADIPRIVATE_EXPORT QString connectionConfigFile(FileAccessMode openMode = ReadOnly); /** - * Returns the full path to the agent config file (agentsrc). + * Returns the full path to the agentsrc config file */ -AKONADIPRIVATE_EXPORT QString agentConfigFile(Akonadi::XdgBaseDirs::FileAccessMode openMode = Akonadi::XdgBaseDirs::ReadOnly); +AKONADIPRIVATE_EXPORT QString agentsConfigFile(FileAccessMode openMode = ReadOnly); /** - * Instance-aware wrapper for XdgBaseDirs::saveDir(). + * Returns the full path to config file of agent @p identifier. + * + * Never returns empty string. + * + * @param identifier identifier of the agent (akonadi_foo_resource_0) + */ +AKONADIPRIVATE_EXPORT QString agentConfigFile(const QString &identifier, FileAccessMode openMode = ReadOnly); + +/** + * Instance-aware wrapper for QStandardPaths * @note @p relPath does not need to include the "akonadi/" folder. - * @see XdgBaseDirs::saveDir() */ AKONADIPRIVATE_EXPORT QString saveDir(const char *resource, const QString &relPath = QString()); +/** + * @brief Searches the resource specific directories for a given file + * + * Convenience method for finding a given file (with optional relative path) + * in any of the configured base directories for a given resource type. + * + * Will check the user local directory first and then process the system + * wide path list according to the inherent priority. + * + * @param resource a named resource type, e.g. "config" + * @param relPath relative path of a file to look for, e.g."akonadi/akonadiserverrc" + * + * @returns the file path of the first match, or @c QString() if no such relative path + * exists in any of the base directories or if a match is not a file + */ +AKONADIPRIVATE_EXPORT QString locateResourceFile(const char *resource, const QString &relPath); + +/** + * Equivalent to QStandardPaths::locateAll() but always includes at least the + * default Akonadi compile prefix. + */ +AKONADIPRIVATE_EXPORT QStringList locateAllResourceDirs(const QString &relPath); + +/** + * Equivalent to QStandardPaths::findExecutable() but it looks in + * qApp->applicationDirPath() first. + */ + +AKONADIPRIVATE_EXPORT QString findExecutable(const QString &relPath); + } } diff --git a/src/private/xdgbasedirs.cpp b/src/private/xdgbasedirs.cpp deleted file mode 100644 --- a/src/private/xdgbasedirs.cpp +++ /dev/null @@ -1,587 +0,0 @@ -/*************************************************************************** - * Copyright (C) 2007 by Kevin Krammer * - * * - * 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 "xdgbasedirs_p.h" - -#include "akonadi-prefix.h" // for prefix defines - -#include "akonadiprivate_debug.h" - -#include -#include -#include -#include -#include -#include - -#include - -#ifdef Q_OS_WIN -# include -#endif - -static QStringList alternateExecPaths(const QString &path) -{ - QStringList pathList; - - pathList << path; - -#if defined(Q_OS_WIN) //krazy:exclude=cpp - pathList << path + QLatin1String(".exe"); -#elif defined(Q_OS_MAC) //krazy:exclude=cpp - pathList << path + QLatin1String(".app/Contents/MacOS/") + path.section(QLatin1Char('/'), -1); -#endif - - return pathList; -} - -static QStringList splitPathList(const QString &pathList) -{ -#if defined(Q_OS_WIN) //krazy:exclude=cpp - return pathList.split(QLatin1Char(';')); -#else - return pathList.split(QLatin1Char(':')); -#endif -} - -#ifdef Q_OS_WIN -static QMap getEnvironment() -{ - QMap ret; - Q_FOREACH (const QString &str, QProcessEnvironment::systemEnvironment().toStringList()) { - const int p = str.indexOf(QLatin1Char('=')); - ret[str.left(p)] = str.mid(p + 1); - } - return ret; -} - -QString expandEnvironmentVariables(const QString &str) -{ - static QMap envVars = getEnvironment(); - static QRegExp possibleVars(QLatin1String("((\\{|%)(\\w+)(\\}|%))")); - QString ret = str; - while (possibleVars.indexIn(ret) != -1) { - QStringList caps = possibleVars.capturedTexts(); - if (caps[2] == QLatin1String("{")) { - ret.replace(QLatin1String("$") + caps[1], envVars[caps[3]]); - } else { - ret.replace(caps[1], envVars[caps[3]]); - } - QString key = possibleVars.cap(); - ret.replace(key, envVars[key]); - } - return ret; -} - -static QSettings *getKdeConf() -{ - WCHAR wPath[MAX_PATH + 1]; - GetModuleFileNameW(NULL, wPath, MAX_PATH); - QString kdeconfPath = QString::fromUtf16((const ushort *) wPath); - kdeconfPath = kdeconfPath.left(kdeconfPath.lastIndexOf(QLatin1Char('\\'))).replace(QLatin1Char('\\'), QLatin1Char('/')); - if (QFile::exists(kdeconfPath + QString::fromLatin1("/kde.conf"))) { - return new QSettings(kdeconfPath + QString::fromLatin1("/kde.conf"), QSettings::IniFormat); - } else { - return 0; - } -} -#endif - -namespace Akonadi -{ - -class XdgBaseDirsPrivate -{ -public: - XdgBaseDirsPrivate() - { - } - - ~XdgBaseDirsPrivate() - { - } -}; - -class XdgBaseDirsSingleton -{ -public: - QString homePath(const char *variable, const char *defaultSubDir); - - QStringList systemPathList(const char *variable, const char *defaultDirList); - -public: - QString mConfigHome; - QString mDataHome; - - QStringList mConfigDirs; - QStringList mDataDirs; - QStringList mExecutableDirs; - QStringList mPluginDirs; -}; - -Q_GLOBAL_STATIC(XdgBaseDirsSingleton, instance) - -} - -using namespace Akonadi; - -XdgBaseDirs::XdgBaseDirs() - : d(new XdgBaseDirsPrivate()) -{ -} - -XdgBaseDirs::~XdgBaseDirs() -{ - delete d; -} - -QString XdgBaseDirs::homePath(const char *resource) -{ -#ifdef Q_OS_WIN - static QSettings *kdeconf = getKdeConf(); -#endif - if (qstrncmp("data", resource, 4) == 0) { - if (instance()->mDataHome.isEmpty()) { -#ifdef Q_OS_WIN - if (kdeconf) { - kdeconf->beginGroup(QLatin1String("XDG")); - if (kdeconf->childKeys().contains(QLatin1String("XDG_DATA_HOME"))) { - instance()->mDataHome = expandEnvironmentVariables(kdeconf->value(QLatin1String("XDG_DATA_HOME")).toString()); - } else { - instance()->mDataHome = instance()->homePath("XDG_DATA_HOME", ".local/share"); - } - kdeconf->endGroup(); - } else { -#else - { -#endif - instance()->mDataHome = instance()->homePath("XDG_DATA_HOME", ".local/share"); - } - } - return instance()->mDataHome; - } else if (qstrncmp("config", resource, 6) == 0) { - if (instance()->mConfigHome.isEmpty()) { -#ifdef Q_OS_WIN - if (kdeconf) { - kdeconf->beginGroup(QLatin1String("XDG")); - if (kdeconf->childKeys().contains(QLatin1String("XDG_CONFIG_HOME"))) { - instance()->mConfigHome = expandEnvironmentVariables(kdeconf->value(QLatin1String("XDG_CONFIG_HOME")).toString()); - } else { - instance()->mConfigHome = instance()->homePath("XDG_CONFIG_HOME", ".config"); - } - kdeconf->endGroup(); - } else { -#else - { -#endif - instance()->mConfigHome = instance()->homePath("XDG_CONFIG_HOME", ".config"); - } - } - return instance()->mConfigHome; - } - - return QString(); -} - -QStringList XdgBaseDirs::systemPathList(const char *resource) -{ -#ifdef Q_OS_WIN - static QSettings *kdeconf = getKdeConf(); -#endif - if (qstrncmp("data", resource, 4) == 0) { - if (instance()->mDataDirs.isEmpty()) { -#ifdef Q_OS_WIN - QStringList dataDirs; - if (kdeconf) { - kdeconf->beginGroup(QLatin1String("XDG")); - if (kdeconf->childKeys().contains(QLatin1String("XDG_DATA_DIRS"))) { - dataDirs = instance()->systemPathList("XDG_DATA_DIRS", expandEnvironmentVariables(kdeconf->value(QLatin1String("XDG_DATA_DIRS")).toString()).toLocal8Bit().constData()); - } else { - QDir dir(QCoreApplication::applicationDirPath()); - dir.cdUp(); - const QString defaultPathList = dir.absoluteFilePath(QLatin1String("share")); - dataDirs = instance()->systemPathList("XDG_DATA_DIRS", defaultPathList.toLocal8Bit().constData()); - } - kdeconf->endGroup(); - } else { - QDir dir(QCoreApplication::applicationDirPath()); - dir.cdUp(); - const QString defaultPathList = dir.absoluteFilePath(QLatin1String("share")); - dataDirs = instance()->systemPathList("XDG_DATA_DIRS", defaultPathList.toLocal8Bit().constData()); - } -#else - QStringList dataDirs = instance()->systemPathList("XDG_DATA_DIRS", "/usr/local/share:/usr/share"); -#endif - -#ifdef Q_OS_WIN - const QString prefixDataDir = QLatin1String(AKONADIPREFIX "/" AKONADIDATA); -#else - const QString prefixDataDir = QStringLiteral(AKONADIDATA); -#endif - if (!dataDirs.contains(prefixDataDir)) { - dataDirs << prefixDataDir; - } - - instance()->mDataDirs = dataDirs; - } -#ifdef Q_OS_WIN - QStringList dataDirs = instance()->mDataDirs; - // on Windows installation might be scattered across several directories - // so check if any installer providing agents has registered its base path - QSettings agentProviders(QSettings::SystemScope, QLatin1String("Akonadi"), QLatin1String("Akonadi")); - agentProviders.beginGroup(QLatin1String("AgentProviders")); - Q_FOREACH (const QString &agentProvider, agentProviders.childKeys()) { - const QString basePath = agentProviders.value(agentProvider).toString(); - if (!basePath.isEmpty()) { - const QString path = basePath + QDir::separator() + QLatin1String("share"); - if (!dataDirs.contains(path)) { - dataDirs << path; - } - } - } - - return dataDirs; -#else - return instance()->mDataDirs; -#endif - } else if (qstrncmp("config", resource, 6) == 0) { - if (instance()->mConfigDirs.isEmpty()) { -#ifdef Q_OS_WIN - QStringList configDirs; - if (kdeconf) { - kdeconf->beginGroup(QLatin1String("XDG")); - if (kdeconf->childKeys().contains(QLatin1String("XDG_CONFIG_DIRS"))) { - configDirs = instance()->systemPathList("XDG_CONFIG_DIRS", expandEnvironmentVariables(kdeconf->value(QLatin1String("XDG_CONFIG_DIRS")).toString()).toLocal8Bit().constData()); - } else { - QDir dir(QCoreApplication::applicationDirPath()); - dir.cdUp(); - const QString defaultPathList = dir.absoluteFilePath(QLatin1String("etc")) + QLatin1Char(';') + dir.absoluteFilePath(QLatin1String("share/config")); - configDirs = instance()->systemPathList("XDG_CONFIG_DIRS", defaultPathList.toLocal8Bit().constData()); - } - kdeconf->endGroup(); - } else { - QDir dir(QCoreApplication::applicationDirPath()); - dir.cdUp(); - const QString defaultPathList = dir.absoluteFilePath(QLatin1String("etc")) + QLatin1Char(';') + dir.absoluteFilePath(QLatin1String("share/config")); - configDirs = instance()->systemPathList("XDG_CONFIG_DIRS", defaultPathList.toLocal8Bit().constData()); - } -#else - QStringList configDirs = instance()->systemPathList("XDG_CONFIG_DIRS", "/etc/xdg"); -#endif - -#ifdef Q_OS_WIN - const QString prefixConfigDir = QLatin1String(AKONADIPREFIX "/" AKONADICONFIG); -#else - const QString prefixConfigDir = QStringLiteral(AKONADICONFIG); -#endif - if (!configDirs.contains(prefixConfigDir)) { - configDirs << prefixConfigDir; - } - - instance()->mConfigDirs = configDirs; - } - return instance()->mConfigDirs; - } - - return QStringList(); -} - -QString XdgBaseDirs::findResourceFile(const char *resource, const QString &relPath) -{ - const QString fullPath = homePath(resource) + QLatin1Char('/') + relPath; - - QFileInfo fileInfo(fullPath); - if (fileInfo.exists() && fileInfo.isFile() && fileInfo.isReadable()) { - return fullPath; - } - - const QStringList pathList = systemPathList(resource); - - for (const QString &path : pathList) { - fileInfo = QFileInfo(path + QLatin1Char('/') + relPath); - if (fileInfo.exists() && fileInfo.isFile() && fileInfo.isReadable()) { - return fileInfo.absoluteFilePath(); - } - } - - return QString(); -} - -QString XdgBaseDirs::findExecutableFile(const QString &relPath, const QStringList &searchPath) -{ - if (instance()->mExecutableDirs.isEmpty()) { - QStringList executableDirs = instance()->systemPathList("PATH", "/usr/local/bin:/usr/bin"); - - const QString prefixExecutableDir = QStringLiteral(AKONADIPREFIX "/bin"); - if (!executableDirs.contains(prefixExecutableDir)) { - executableDirs << prefixExecutableDir; - } - - if (QCoreApplication::instance() != nullptr) { - const QString appExecutableDir = QCoreApplication::instance()->applicationDirPath(); - if (!executableDirs.contains(appExecutableDir)) { - executableDirs << appExecutableDir; - } - } - - executableDirs += searchPath; - -#if defined(Q_OS_MAC) //krazy:exclude=cpp - executableDirs += QLatin1String(AKONADIBUNDLEPATH); -#endif - qCDebug(AKONADIPRIVATE_LOG) << "search paths: " << executableDirs; - - instance()->mExecutableDirs = executableDirs; - } - -#ifdef Q_OS_WIN - QStringList executableDirs = instance()->mExecutableDirs; - // on Windows installation might be scattered across several directories - // so check if any installer providing agents has registered its base path - QSettings agentProviders(QSettings::SystemScope, QLatin1String("Akonadi"), QLatin1String("Akonadi")); - agentProviders.beginGroup(QLatin1String("AgentProviders")); - Q_FOREACH (const QString &agentProvider, agentProviders.childKeys()) { - const QString basePath = agentProviders.value(agentProvider).toString(); - if (!basePath.isEmpty()) { - const QString path = basePath + QDir::separator() + QLatin1String("bin"); - if (!executableDirs.contains(path)) { - executableDirs << path; - } - } - } - - QStringList::const_iterator pathIt = executableDirs.constBegin(); - const QStringList::const_iterator pathEndIt = executableDirs.constEnd(); -#else - QStringList::const_iterator pathIt = instance()->mExecutableDirs.constBegin(); - const QStringList::const_iterator pathEndIt = instance()->mExecutableDirs.constEnd(); -#endif - for (; pathIt != pathEndIt; ++pathIt) { - const QStringList fullPathList = alternateExecPaths(*pathIt + QLatin1Char('/') + relPath); - - QStringList::const_iterator it = fullPathList.constBegin(); - const QStringList::const_iterator endIt = fullPathList.constEnd(); - for (; it != endIt; ++it) { - const QFileInfo fileInfo(*it); - - // resolve symlinks, happens eg. with Maemo optify - if (fileInfo.canonicalFilePath().isEmpty()) { - continue; - } - - const QFileInfo canonicalFileInfo(fileInfo.canonicalFilePath()); - - if (canonicalFileInfo.exists() && canonicalFileInfo.isFile() && canonicalFileInfo.isExecutable()) { - return *it; - } - } - } - - return QString(); -} - -QStringList XdgBaseDirs::findPluginDirs() -{ - if (instance()->mPluginDirs.isEmpty()) { - QStringList pluginDirs = instance()->systemPathList("QT_PLUGIN_PATH", AKONADILIB ":" AKONADILIB "/qt5/plugins/:" AKONADILIB "/kf5/:" AKONADILIB "/kf5/plugins/:/usr/lib/qt5/plugins/"); - if (QCoreApplication::instance() != nullptr) { - Q_FOREACH (const QString &libraryPath, QCoreApplication::instance()->libraryPaths()) { - if (!pluginDirs.contains(libraryPath)) { - pluginDirs << libraryPath; - } - } - } - qCDebug(AKONADIPRIVATE_LOG) << "search paths: " << pluginDirs; - instance()->mPluginDirs = pluginDirs; - } - - return instance()->mPluginDirs; -} - -QString XdgBaseDirs::findPluginFile(const QString &relPath, const QStringList &searchPath) -{ - const QStringList searchDirs = findPluginDirs() + searchPath; - -#if defined(Q_OS_WIN) //krazy:exclude=cpp - const QString pluginName = relPath + QLatin1String(".dll"); -#else - const QString pluginName = relPath + QLatin1String(".so"); -#endif - - for (const QString &path : searchDirs) { - const QFileInfo fileInfo(path + QDir::separator() + pluginName); - - // resolve symlinks, happens eg. with Maemo optify - if (fileInfo.canonicalFilePath().isEmpty()) { - continue; - } - - const QFileInfo canonicalFileInfo(fileInfo.canonicalFilePath()); - if (canonicalFileInfo.exists() && canonicalFileInfo.isFile()) { - return canonicalFileInfo.absoluteFilePath(); - } - } - - return QString(); -} - -QString XdgBaseDirs::findResourceDir(const char *resource, const QString &relPath) -{ - QString fullPath = homePath(resource) + QLatin1Char('/') + relPath; - - QFileInfo fileInfo(fullPath); - if (fileInfo.exists() && fileInfo.isDir() && fileInfo.isReadable()) { - return fullPath; - } - - const QStringList sysPathList = systemPathList(resource); - for (const QString &path : sysPathList) { - fileInfo = QFileInfo(path + QLatin1Char('/') + relPath); - if (fileInfo.exists() && fileInfo.isDir() && fileInfo.isReadable()) { - return fileInfo.absoluteFilePath(); - } - } - - return QString(); -} - -QStringList XdgBaseDirs::findAllResourceDirs(const char *resource, const QString &relPath) -{ - QStringList resultList; - - const QString fullPath = homePath(resource) + QLatin1Char('/') + relPath; - - QFileInfo fileInfo(fullPath); - if (fileInfo.exists() && fileInfo.isDir() && fileInfo.isReadable()) { - resultList << fileInfo.absoluteFilePath(); - } - - const QStringList sysPathList = systemPathList(resource); - for (const QString &path : sysPathList) { - fileInfo = QFileInfo(path + QLatin1Char('/') + relPath); - if (fileInfo.exists() && fileInfo.isDir() && fileInfo.isReadable()) { - const QString absPath = fileInfo.absoluteFilePath(); - if (!resultList.contains(absPath)) { - resultList << absPath; - } - } - } - - return resultList; -} - -QString XdgBaseDirs::saveDir(const char *resource, const QString &relPath) -{ - const QString fullPath = homePath(resource) + QLatin1Char('/') + relPath; - - QFileInfo fileInfo(fullPath); - if (fileInfo.exists()) { - if (fileInfo.isDir()) { - return fullPath; - } else { - qCWarning(AKONADIPRIVATE_LOG) << "XdgBaseDirs::saveDir: '" << fileInfo.absoluteFilePath() - << "' exists but is not a directory"; - } - } else { - if (!QDir::home().mkpath(fileInfo.absoluteFilePath())) { - qCWarning(AKONADIPRIVATE_LOG) << "XdgBaseDirs::saveDir: failed to create directory '" - << fileInfo.absoluteFilePath() << "'"; - } else { - return fullPath; - } - } - - return QString(); -} - -QString XdgBaseDirs::akonadiServerConfigFile(FileAccessMode openMode, const QString &relPath) -{ - return akonadiConfigFile(QStringLiteral("%1/akonadiserverrc").arg(relPath), openMode); -} - -QString XdgBaseDirs::akonadiConnectionConfigFile(FileAccessMode openMode, const QString &relPath) -{ - return akonadiConfigFile(QStringLiteral("%1/akonadiconnectionrc").arg(relPath), openMode); -} - -QString XdgBaseDirs::akonadiConfigFile(const QString &file, FileAccessMode openMode) -{ - const QString akonadiDir = QStringLiteral("akonadi"); - - const QString savePath = saveDir("config", akonadiDir) + QLatin1Char('/') + file; - - if (openMode == WriteOnly) { - return savePath; - } - - const QString path = findResourceFile("config", akonadiDir + QLatin1Char('/') + file); - - if (path.isEmpty()) { - return savePath; - } else if (openMode == ReadOnly || path == savePath) { - return path; - } - - // file found in system paths and mode is ReadWrite, thus - // we copy to the home path location and return this path - QFile systemFile(path); - - systemFile.copy(savePath); - - return savePath; -} - -QString XdgBaseDirsSingleton::homePath(const char *variable, const char *defaultSubDir) -{ - const QByteArray env = qgetenv(variable); - - QString xdgPath; - if (env.isEmpty()) { - xdgPath = QDir::homePath() + QLatin1Char('/') + QLatin1String(defaultSubDir); -#if defined(Q_OS_WIN) //krazy:exclude=cpp - } else if (QDir::isAbsolutePath(QString::fromLocal8Bit(env))) { -#else - } else if (env.startsWith('/')) { -#endif - xdgPath = QString::fromLocal8Bit(env); - } else { - xdgPath = QDir::homePath() + QLatin1Char('/') + QString::fromLocal8Bit(env); - } - - return xdgPath; -} - -QStringList XdgBaseDirsSingleton::systemPathList(const char *variable, const char *defaultDirList) -{ - const QByteArray env = qgetenv(variable); - - QString xdgDirList; - if (env.isEmpty()) { - xdgDirList = QLatin1String(defaultDirList); - } else { - xdgDirList = QString::fromLocal8Bit(env); - } - - return splitPathList(xdgDirList); -} - -void XdgBaseDirs::overrideConfigPath(const QString &configFile) -{ - instance()->mConfigHome = configFile; -} diff --git a/src/private/xdgbasedirs_p.h b/src/private/xdgbasedirs_p.h deleted file mode 100644 --- a/src/private/xdgbasedirs_p.h +++ /dev/null @@ -1,335 +0,0 @@ -/*************************************************************************** - * Copyright (C) 2007 by Kevin Krammer * - * * - * 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 XDGBASEDIRS_H -#define XDGBASEDIRS_H - -// Qt includes -#include -#include - -#include "akonadiprivate_export.h" - -// forward declarations -class QString; - -namespace Akonadi -{ - -class XdgBaseDirsPrivate; - -/** - @brief Resource type based handling of standard directories - - Developers of several Free Software desktop projects have created - a specification for handling so-called "base directories", i.e. - lists of system wide directories and directories within each user's - home directory for installing and later finding certain files. - - This class handles the respective behaviour, i.e. environment variables - and their defaults, for the following type of resources: - - "config" - - "data" - - Example: getting the Akonadi server config file "akonadiserverrc", assuming - that Akonadi stores its config in an additional subdirectoy called "akonadi" - @code - QString relativeFileName = QLatin1String( "akonadi/akonadiserverrc" ); - - // look for the file "akonadiserverrc" with additional subdirectory "akonadi" - // in any directory associated with resource type "config" - QString configFile = XdgBaseDirs::findResourceFile( "config", relativeFileName ); - - if ( configFile.isEmpty() ) { - // No config file yet, get the suitable user specific directory for storing - // a new one - configFile = XdgBaseDirs::saveDir( "config", QLatin1String( "akonadi" ) ); - configFile += QLatin1String( "akonadiserverrc" ); - } - - QSettings serverConfig( configFile ); - @endcode - - @author Kevin Krammer, - - @see http://www.freedesktop.org/wiki/Specifications/basedir-spec - */ -class AKONADIPRIVATE_EXPORT XdgBaseDirs -{ -public: - /** - @brief Creates the instance - */ - XdgBaseDirs(); - - /** - @brief Destroys the instance - */ - ~XdgBaseDirs(); - - /** - @brief Returns the user specific directory for the given resource type - - Unless the user's environment has a specific path set as an override - this will be the default as defined in the freedesktop.org base-dir-spec - - @note Caches the value of the first call - - @param resource a named resource type, e.g. "config" - - @return a directory path - - @see systemPathList() - @see saveDir() - */ - static QString homePath(const char *resource); - - /** - @brief Returns the list of system wide directories for a given resource type - - The returned list can contain one or more directory paths. If there are more - than one, the list is sorted by falling priority, i.e. if an entry is valid - for the respective use case (e.g. contains a file the application looks for) - the list should not be processed further. - - @note The user's resource path should, to be compliant with the spec, - always be treated as having higher priority than any path in the - list of system wide paths - - @note Caches the value of the first call - - @param resource a named resource type, e.g. "config" - - @return a priority sorted list of directory paths - - @see homePath() - */ - static QStringList systemPathList(const char *resource); - - /** - @brief Searches the resource specific directories for a given file - - Convenience method for finding a given file (with optional relative path) - in any of the configured base directories for a given resource type. - - Will check the user local directory first and then process the system - wide path list according to the inherent priority. - - @param resource a named resource type, e.g. "config" - @param relPath relative path of a file to look for, - e.g."akonadi/akonadiserverrc" - - @returns the file path of the first match, or @c QString() if no such - relative path exists in any of the base directories or if - a match is not a file - - @see findResourceDir() - @see saveDir - */ - static QString findResourceFile(const char *resource, const QString &relPath); - - /** - @brief Searches the executable specific directories for a given file - - Convenience method for finding a given executable (with optional relative path) - in any of the configured directories for a this special type. - - @note This is not based on the XDG base dir spec, since it does not cover - executable - - @param relPath relative path of a file to look for, - e.g."akonadiserver" - @param searchPath additional paths to search for the executable, - only used if the file was not found in PATH and the install prefix - - @returns the file path of the first match, or @c QString() if no such - relative path exists in any of the base directories - - @see findResourceFile() - */ - static QString findExecutableFile(const QString &relPath, const QStringList &searchPath = QStringList()); - - /** - @brief Searches the plugin specific directories for a given file - - Convenience method for finding a given plugin (with optional relative path) - in any of the configured directories for a this special type. - - @note This is not based on the XDG base dir spec, since it does not cover - plugins - - @param relPath relative path of a file to look for, - e.g."akonadi_knut_resource" - @param searchPath additional paths to search for the plugin, - only used if the file was not found in QT_PLUGIN_PATH and the install prefix - - @returns the file path of the first match, or @c QString() if no such - relative path exists in any of the base directories - - @see findResourceFile() - */ - static QString findPluginFile(const QString &relPath, const QStringList &searchPath = QStringList()); - - /** - @brief Returns plugin specific directories - - Convenience method for listing directories that can be scanned for available - plugins. - - @note This is not based on the XDG base dir spec, since it does not cover - plugins. - - @return directories where application should look for plugins - */ - static QStringList findPluginDirs(); - - /** - @brief Searches the resource specific directories for a given subdirectory - - Convenience method for finding a given relative subdirectory in any of - the configured base directories for a given resource type. - - Will check the user local directory first and then process the system - wide path list according to the inherent priority. - - Use findAllResourceDirs() if looking for all directories with the given - subdirectory. - - @param resource a named resource type, e.g. "config" - @param relPath relative path of a subdirectory to look for, - e.g."akonadi/agents" - - @returns the directory path of the first match, or @c QString() if no such - relative path exists in any of the base directories or if - a match is not a directory - - @see findResourceFile() - @see saveDir() - */ - static QString findResourceDir(const char *resource, const QString &relPath); - - /** - @brief Searches the resource specific directories for a given subdirectory - - Convenience method for getting a list of directoreis with a given relative - subdirectory in any of the configured base directories for a given - resource type. - - Will check the user local directory first and then process the system - wide path list according to the inherent priority. - - Similar to findResourceDir() but does not just find the first best match - but all matching resource directories. The resuling list will be sorted - according to the same proprity criteria. - - @param resource a named resource type, e.g. "config" - @param relPath relative path of a subdirectory to look for, - e.g."akonadi/agents" - - @returns a list of directory paths, or @c QString() if no such - relative path exists in any of the base directories or if - non of the matches is a directory - - @see findResourceDir() - */ - static QStringList findAllResourceDirs(const char *resource, const QString &relPath); - - /** - @brief Finds or creates the "save to" directory for a given resource - - Convenience method for creating subdirectores relative to a given - resource type's user directory, i.e. homePath() + relPath - - If the target directory does not exists, it an all necessary parent - directories will be created, unless denied by the filesystem. - - @param resource a named resource type, e.g. "config" - @param relPath relative path of a directory to be used for file writing - - @return the directory path of the "save to" directory or @c QString() - if the directory or one of its parents could not be created - - @see findResourceDir() - */ - static QString saveDir(const char *resource, const QString &relPath); - - /** - * @brief Open mode flags for resource files - * - * FileAccessMode is a typedef for QFlags. It stores - * a OR combination of FileAccessFlag values - */ - enum FileAccessFlag { - ReadOnly = 0x1, - WriteOnly = 0x2, - ReadWrite = ReadOnly | WriteOnly - }; - - typedef QFlags FileAccessMode; - - /** - * @brief Returns the path of the Akonadi server config file - * - * Convenience method for getting the server config file "akonadiserverrc" - * since this is an often needed procedure in several parts of the code. - * - * @param openMode how the application wants to use the config file - * @param relPath Relative path within the akonadi config directory - * - * @return the path of the server config file, suitable for \p openMode - */ - static QString akonadiServerConfigFile(FileAccessMode openMode = ReadOnly, - const QString &relPath = QString()); - - /** - * @brief Returns the path of the Akonadi data connection config file - * - * Convenience method for getting the server config file "akonadiconnectionrc" - * since this is an often needed procedure in several parts of the code. - * - * @param openMode how the application wants to use the config file - * @relPath Relative path within the akonadi config directory - * - * @return the path of the data connection config file, suitable for \p openMode - */ - static QString akonadiConnectionConfigFile(FileAccessMode openMode = ReadOnly, - const QString &relPath = QString()); - - /** - * @brief Overrides the lookup path to the "config" resource - * - * This is useful for testing purposes and when connecting to Akonadi - * instance that is not running in the default user path. - */ - static void overrideConfigPath(const QString &configFile); - -private: - XdgBaseDirsPrivate *const d; - -private: - static QString akonadiConfigFile(const QString &file, FileAccessMode openMode); - -private: - XdgBaseDirs(const XdgBaseDirs &); - XdgBaseDirs &operator=(const XdgBaseDirs &); -}; - -} - -#endif diff --git a/src/qsqlite/src/qsql_sqlite.cpp b/src/qsqlite/src/qsql_sqlite.cpp --- a/src/qsqlite/src/qsql_sqlite.cpp +++ b/src/qsqlite/src/qsql_sqlite.cpp @@ -648,7 +648,7 @@ const QStringList opts = QString(conOpts).remove(QLatin1Char(' ')).split(QLatin1Char(';')); for (const QString &option : opts) { - if (option.startsWith(QStringLiteral("QSQLITE_BUSY_TIMEOUT="))) { + if (option.startsWith(QLatin1String("QSQLITE_BUSY_TIMEOUT="))) { bool ok; const int nt = option.midRef(21).toInt(&ok); if (ok) { diff --git a/src/rds/bridgeconnection.cpp b/src/rds/bridgeconnection.cpp --- a/src/rds/bridgeconnection.cpp +++ b/src/rds/bridgeconnection.cpp @@ -39,8 +39,14 @@ , m_remoteSocket(remoteSocket) { // wait for the vtable to be complete +#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) + QMetaObject::invokeMethod(this, &BridgeConnection::doConnects, Qt::QueuedConnection); + QMetaObject::invokeMethod(this, &BridgeConnection::connectLocal, Qt::QueuedConnection); +#else QMetaObject::invokeMethod(this, "doConnects", Qt::QueuedConnection); QMetaObject::invokeMethod(this, "connectLocal", Qt::QueuedConnection); +#endif + } BridgeConnection::~BridgeConnection() diff --git a/src/rds/bridgeserver.cpp b/src/rds/bridgeserver.cpp --- a/src/rds/bridgeserver.cpp +++ b/src/rds/bridgeserver.cpp @@ -27,7 +27,7 @@ { connect(m_server, &QTcpServer::newConnection, this, &BridgeServerBase::slotNewConnection); if (!m_server->listen(QHostAddress::Any, port)) { - throw Exception(tr("Can't listen to port %1: %2") + throw Exception(QStringLiteral("Can't listen to port %1: %2") .arg(port).arg(m_server->errorString())); } } diff --git a/src/server/CMakeLists.txt b/src/server/CMakeLists.txt --- a/src/server/CMakeLists.txt +++ b/src/server/CMakeLists.txt @@ -10,35 +10,35 @@ endif() ########### next target ############### +set(AKONADI_DB_SCHEMA "${CMAKE_CURRENT_SOURCE_DIR}/storage/akonadidb.xml") -set(AKONADI_DB_SCHEME ${CMAKE_CURRENT_SOURCE_DIR}/storage/akonadidb.xml) - -add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/entities.h - ${CMAKE_CURRENT_BINARY_DIR}/entities.cpp - COMMAND ${XSLTPROC_EXECUTABLE} - --output ${CMAKE_CURRENT_BINARY_DIR}/entities.h - --stringparam code header - ${CMAKE_CURRENT_SOURCE_DIR}/storage/entities.xsl - ${AKONADI_DB_SCHEME} - COMMAND ${XSLTPROC_EXECUTABLE} - --output ${CMAKE_CURRENT_BINARY_DIR}/entities.cpp - --stringparam code source - ${CMAKE_CURRENT_SOURCE_DIR}/storage/entities.xsl - ${AKONADI_DB_SCHEME} - DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/storage/entities.xsl - ${CMAKE_CURRENT_SOURCE_DIR}/storage/entities-header.xsl - ${CMAKE_CURRENT_SOURCE_DIR}/storage/entities-source.xsl - ${AKONADI_DB_SCHEME} +akonadi_run_xsltproc( + XSL ${CMAKE_CURRENT_SOURCE_DIR}/storage/entities.xsl + XML ${AKONADI_DB_SCHEMA} + BASENAME entities ) -add_test(akonadidb-xmllint ${XMLLINT_EXECUTABLE} --noout --schema ${CMAKE_CURRENT_SOURCE_DIR}/storage/akonadidb.xsd ${CMAKE_CURRENT_SOURCE_DIR}/storage/akonadidb.xml) -add_test(akonadidbupdate-xmllint ${XMLLINT_EXECUTABLE} --noout --schema ${CMAKE_CURRENT_SOURCE_DIR}/storage/dbupdate.xsd ${CMAKE_CURRENT_SOURCE_DIR}/storage/dbupdate.xml) +akonadi_run_xsltproc( + XSL ${Akonadi_SOURCE_DIR}/src/server/storage/schema.xsl + XML ${AKONADI_DB_SCHEMA} + CLASSNAME AkonadiSchema + BASENAME akonadischema +) -akonadi_generate_schema(${AKONADI_DB_SCHEME} AkonadiSchema 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 @@ -138,6 +138,7 @@ 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) diff --git a/src/server/aggregatedfetchscope.h b/src/server/aggregatedfetchscope.h new file mode 100644 --- /dev/null +++ b/src/server/aggregatedfetchscope.h @@ -0,0 +1,138 @@ +/* + Copyright (c) 2017 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_AGGREGATED_FETCHSCOPE_H_ +#define AKONADI_AGGREGATED_FETCHSCOPE_H_ + +#include + +#include + +class QByteArray; + +namespace Akonadi { +namespace Server { + +class AggregatedCollectionFetchScopePrivate; +class AggregatedCollectionFetchScope +{ +public: + explicit AggregatedCollectionFetchScope(); + ~AggregatedCollectionFetchScope(); + + void apply(const Protocol::CollectionFetchScope &oldScope, + const Protocol::CollectionFetchScope &newScope); + + QSet attributes() const; + void addAttribute(const QByteArray &attribute); + void removeAttribute(const QByteArray &attribute); + + bool fetchIdOnly() const; + void setFetchIdOnly(bool fetchIdOnly); + + bool fetchStatistics() const; + void setFetchStatistics(bool fetchStats); + +private: + AggregatedCollectionFetchScopePrivate * const d_ptr; + Q_DECLARE_PRIVATE(AggregatedCollectionFetchScope) +}; + +class AggregatedItemFetchScopePrivate; +class AggregatedItemFetchScope +{ +public: + explicit AggregatedItemFetchScope(); + ~AggregatedItemFetchScope(); + + void apply(const Protocol::ItemFetchScope &oldScope, + const Protocol::ItemFetchScope &newScope); + Protocol::ItemFetchScope toFetchScope() const; + + QSet requestedParts() const; + void addRequestedPart(const QByteArray &part); + void removeRequestedPart(const QByteArray &part); + + QSet tagFetchScope() const; + void addTag(const QByteArray &tag); + void removeTag(const QByteArray &tag); + + Protocol::ItemFetchScope::AncestorDepth ancestorDepth() const; + void updateAncestorDepth(Protocol::ItemFetchScope::AncestorDepth oldDepth, + Protocol::ItemFetchScope::AncestorDepth newDepth); + + bool cacheOnly() const; + void setCacheOnly(bool cacheOnly); + bool fullPayload() const; + void setFullPayload(bool fullPayload); + bool allAttributes() const; + void setAllAttributes(bool allAttributes); + bool fetchSize() const; + void setFetchSize(bool fetchSize); + bool fetchMTime() const; + void setFetchMTime(bool fetchMTime); + bool fetchRemoteRevision() const; + void setFetchRemoteRevision(bool remoteRevision); + bool ignoreErrors() const; + void setIgnoreErrors(bool ignoreErrors); + bool fetchFlags() const; + void setFetchFlags(bool fetchFlags); + bool fetchRemoteId() const; + void setFetchRemoteId(bool fetchRemoteId); + bool fetchGID() const; + void setFetchGID(bool fetchGid); + bool fetchTags() const; + void setFetchTags(bool fetchTags); + bool fetchRelations() const; + void setFetchRelations(bool fetchRelations); + bool fetchVirtualReferences() const; + void setFetchVirtualReferences(bool fetchVRefs); + +private: + AggregatedItemFetchScopePrivate * const d_ptr; + Q_DECLARE_PRIVATE(AggregatedItemFetchScope) +}; + + +class AggregatedTagFetchScopePrivate; +class AggregatedTagFetchScope +{ +public: + explicit AggregatedTagFetchScope(); + ~AggregatedTagFetchScope(); + + void apply(const Protocol::TagFetchScope &oldScope, + const Protocol::TagFetchScope &newScope); + + QSet attributes() const; + void addAttribute(const QByteArray &attribute); + void removeAttribute(const QByteArray &attribute); + + bool fetchIdOnly() const; + void setFetchIdOnly(bool fetchIdOnly); + +private: + AggregatedTagFetchScopePrivate * const d_ptr; + Q_DECLARE_PRIVATE(AggregatedTagFetchScope) +}; + +} // namespace Server +} // namespace Akonadi + +#endif diff --git a/src/server/aggregatedfetchscope.cpp b/src/server/aggregatedfetchscope.cpp new file mode 100644 --- /dev/null +++ b/src/server/aggregatedfetchscope.cpp @@ -0,0 +1,602 @@ +/* + Copyright (c) 2017 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 "aggregatedfetchscope.h" +#include +#include + +#include +#include + +#define LOCKED_D(name) \ + Q_D(name); \ + QMutexLocker lock(&d->lock); + + +namespace Akonadi { +namespace Server { + +class AggregatedFetchScopePrivate +{ +public: + AggregatedFetchScopePrivate() + : lock(QMutex::Recursive) // recursive so that we can call our own getters/setters + {} + + inline void addToSet(const QByteArray &value, QSet &set, QHash &count) + { + auto it = count.find(value); + if (it == count.end()) { + it = count.insert(value, 0); + set.insert(value); + } + ++(*it); + } + + inline void removeFromSet(const QByteArray &value, QSet &set, QHash &count) + { + auto it = count.find(value); + if (it == count.end()) { + return; + } + + if (--(*it) == 0) { + count.erase(it); + set.remove(value); + } + } + + inline void updateBool(bool newValue, int &store) + { + store += newValue ? 1 : -1; + } + + inline void applySet(const QSet &oldSet, const QSet &newSet, + QSet &set, QHash &count) + { + const auto added = newSet - oldSet; + for (const auto &value : added) { + addToSet(value, set, count); + } + const auto removed = oldSet - newSet; + for (const auto &value : removed) { + removeFromSet(value, set, count); + } + } + +public: + mutable QMutex lock; +}; + +class AggregatedCollectionFetchScopePrivate : public AggregatedFetchScopePrivate +{ +public: + QSet attrs; + QHash attrsCount; + int fetchIdOnly = 0; + int fetchStats = 0; +}; + + +class AggregatedTagFetchScopePrivate : public AggregatedFetchScopePrivate +{ +public: + QSet attrs; + QHash attrsCount; + int fetchIdOnly = 0; +}; + +class AggregatedItemFetchScopePrivate : public AggregatedFetchScopePrivate +{ +public: + mutable Protocol::ItemFetchScope mCachedScope; + mutable bool mCachedScopeValid = false; // use std::optional for mCachedScope + + QSet parts; + QHash partsCount; + QSet tags; + QHash tagsCount; + int ancestors[3] = { 0, 0, 0 }; // 3 = size of AncestorDepth enum + int cacheOnly = 0; + int fullPayload = 0; + int allAttributes = 0; + int fetchSize = 0; + int fetchMTime = 0; + int fetchRRev = 0; + int ignoreErrors = 0; + int fetchFlags = 0; + int fetchRID = 0; + int fetchGID = 0; + int fetchTags = 0; + int fetchRelations = 0; + int fetchVRefs = 0; +}; + +} // namespace Server +} // namespace Akonadi + +using namespace Akonadi; +using namespace Akonadi::Protocol; +using namespace Akonadi::Server; + +AggregatedCollectionFetchScope::AggregatedCollectionFetchScope() + : d_ptr(new AggregatedCollectionFetchScopePrivate) +{ +} + +AggregatedCollectionFetchScope::~AggregatedCollectionFetchScope() +{ + delete d_ptr; +} + +void AggregatedCollectionFetchScope::apply(const Protocol::CollectionFetchScope &oldScope, + const Protocol::CollectionFetchScope &newScope) +{ + LOCKED_D(AggregatedCollectionFetchScope) + + if (newScope.includeStatistics() != oldScope.includeStatistics()) { + setFetchStatistics(newScope.includeStatistics()); + } + if (newScope.fetchIdOnly() != oldScope.fetchIdOnly()) { + setFetchIdOnly(newScope.fetchIdOnly()); + } + if (newScope.attributes() != oldScope.attributes()) { + d->applySet(oldScope.attributes(), newScope.attributes(), d->attrs, d->attrsCount); + } +} + +QSet AggregatedCollectionFetchScope::attributes() const +{ + LOCKED_D(const AggregatedCollectionFetchScope) + return d->attrs; +} + +void AggregatedCollectionFetchScope::addAttribute(const QByteArray &attribute) +{ + LOCKED_D(AggregatedCollectionFetchScope) + d->addToSet(attribute, d->attrs, d->attrsCount); +} + +void AggregatedCollectionFetchScope::removeAttribute(const QByteArray &attribute) +{ + LOCKED_D(AggregatedCollectionFetchScope) + d->removeFromSet(attribute, d->attrs, d->attrsCount); +} + +bool AggregatedCollectionFetchScope::fetchIdOnly() const +{ + LOCKED_D(const AggregatedCollectionFetchScope) + // Aggregation: we can return true only if everyone wants fetchIdOnly, + // otherwise there's at least one subscriber who wants everything + return d->fetchIdOnly == 0; +} + +void AggregatedCollectionFetchScope::setFetchIdOnly(bool fetchIdOnly) +{ + LOCKED_D(AggregatedCollectionFetchScope) + d->updateBool(fetchIdOnly, d->fetchIdOnly); +} + +bool AggregatedCollectionFetchScope::fetchStatistics() const +{ + LOCKED_D(const AggregatedCollectionFetchScope); + // Aggregation: return true if at least one subscriber wants stats + return d->fetchStats > 0; +} + +void AggregatedCollectionFetchScope::setFetchStatistics(bool fetchStats) +{ + LOCKED_D(AggregatedCollectionFetchScope); + d->updateBool(fetchStats, d->fetchStats); +} + + + +AggregatedItemFetchScope::AggregatedItemFetchScope() + : d_ptr(new AggregatedItemFetchScopePrivate) +{ +} + +AggregatedItemFetchScope::~AggregatedItemFetchScope() +{ + delete d_ptr; +} + +void AggregatedItemFetchScope::apply(const Protocol::ItemFetchScope &oldScope, + const Protocol::ItemFetchScope &newScope) +{ + LOCKED_D(AggregatedItemFetchScope); + + const auto newParts = vectorToSet(newScope.requestedParts()); + const auto oldParts = vectorToSet(oldScope.requestedParts()); + if (newParts != oldParts) { + d->applySet(oldParts, newParts, d->parts, d->partsCount); + } + if (newScope.tagFetchScope() != oldScope.tagFetchScope()) { + d->applySet(oldScope.tagFetchScope(), newScope.tagFetchScope(), d->tags, d->tagsCount); + } + if (newScope.ancestorDepth() != oldScope.ancestorDepth()) { + updateAncestorDepth(oldScope.ancestorDepth(), newScope.ancestorDepth()); + } + if (newScope.cacheOnly() != oldScope.cacheOnly()) { + setCacheOnly(newScope.cacheOnly()); + } + if (newScope.fullPayload() != oldScope.fullPayload()) { + setFullPayload(newScope.fullPayload()); + } + if (newScope.allAttributes() != oldScope.allAttributes()) { + setAllAttributes(newScope.allAttributes()); + } + if (newScope.fetchSize() != oldScope.fetchSize()) { + setFetchSize(newScope.fetchSize()); + } + if (newScope.fetchMTime() != oldScope.fetchMTime()) { + setFetchMTime(newScope.fetchMTime()); + } + if (newScope.fetchRemoteRevision() != oldScope.fetchRemoteRevision()) { + setFetchRemoteRevision(newScope.fetchRemoteRevision()); + } + if (newScope.ignoreErrors() != oldScope.ignoreErrors()) { + setIgnoreErrors(newScope.ignoreErrors()); + } + if (newScope.fetchFlags() != oldScope.fetchFlags()) { + setFetchFlags(newScope.fetchFlags()); + } + if (newScope.fetchRemoteId() != oldScope.fetchRemoteId()) { + setFetchRemoteId(newScope.fetchRemoteId()); + } + if (newScope.fetchGID() != oldScope.fetchGID()) { + setFetchGID(newScope.fetchGID()); + } + if (newScope.fetchTags() != oldScope.fetchTags()) { + setFetchTags(newScope.fetchTags()); + } + if (newScope.fetchRelations() != oldScope.fetchRelations()) { + setFetchRelations(newScope.fetchRelations()); + } + if (newScope.fetchVirtualReferences() != oldScope.fetchVirtualReferences()) { + setFetchVirtualReferences(newScope.fetchVirtualReferences()); + } + + d->mCachedScopeValid = false; +} + +ItemFetchScope AggregatedItemFetchScope::toFetchScope() const +{ + LOCKED_D(const AggregatedItemFetchScope); + if (d->mCachedScopeValid) { + return d->mCachedScope; + } + + d->mCachedScope = ItemFetchScope(); + d->mCachedScope.setRequestedParts(setToVector(d->parts)); + d->mCachedScope.setTagFetchScope(d->tags); + d->mCachedScope.setAncestorDepth(ancestorDepth()); + + d->mCachedScope.setFetch(ItemFetchScope::CacheOnly, cacheOnly()); + d->mCachedScope.setFetch(ItemFetchScope::FullPayload, fullPayload()); + d->mCachedScope.setFetch(ItemFetchScope::AllAttributes, allAttributes()); + d->mCachedScope.setFetch(ItemFetchScope::Size, fetchSize()); + d->mCachedScope.setFetch(ItemFetchScope::MTime, fetchMTime()); + d->mCachedScope.setFetch(ItemFetchScope::RemoteRevision, fetchRemoteRevision()); + d->mCachedScope.setFetch(ItemFetchScope::IgnoreErrors, ignoreErrors()); + d->mCachedScope.setFetch(ItemFetchScope::Flags, fetchFlags()); + d->mCachedScope.setFetch(ItemFetchScope::RemoteID, fetchRemoteId()); + d->mCachedScope.setFetch(ItemFetchScope::GID, fetchGID()); + d->mCachedScope.setFetch(ItemFetchScope::Tags, fetchTags()); + d->mCachedScope.setFetch(ItemFetchScope::Relations, fetchRelations()); + d->mCachedScope.setFetch(ItemFetchScope::VirtReferences, fetchVirtualReferences()); + d->mCachedScopeValid = true; + return d->mCachedScope; +} + +QSet AggregatedItemFetchScope::requestedParts() const +{ + LOCKED_D(const AggregatedItemFetchScope) + return d->parts; +} + +void AggregatedItemFetchScope::addRequestedPart(const QByteArray &part) +{ + LOCKED_D(AggregatedItemFetchScope) + d->addToSet(part, d->parts, d->partsCount); +} + +void AggregatedItemFetchScope::removeRequestedPart(const QByteArray &part) +{ + LOCKED_D(AggregatedItemFetchScope) + d->removeFromSet(part, d->parts, d->partsCount); +} + +QSet AggregatedItemFetchScope::tagFetchScope() const +{ + LOCKED_D(const AggregatedItemFetchScope) + return d->tags; +} + +void AggregatedItemFetchScope::addTag(const QByteArray &tag) +{ + LOCKED_D(AggregatedItemFetchScope) + d->addToSet(tag, d->tags, d->tagsCount); +} + +void AggregatedItemFetchScope::removeTag(const QByteArray &tag) +{ + LOCKED_D(AggregatedItemFetchScope) + d->removeFromSet(tag, d->tags, d->tagsCount); +} + +ItemFetchScope::AncestorDepth AggregatedItemFetchScope::ancestorDepth() const +{ + LOCKED_D(const AggregatedItemFetchScope) + // Aggregation: return the largest depth with at least one subscriber + if (d->ancestors[ItemFetchScope::AllAncestors] > 0) { + return ItemFetchScope::AllAncestors; + } else if (d->ancestors[ItemFetchScope::ParentAncestor] > 0) { + return ItemFetchScope::ParentAncestor; + } else { + return ItemFetchScope::NoAncestor; + } +} + +void AggregatedItemFetchScope::updateAncestorDepth(ItemFetchScope::AncestorDepth oldDepth, + ItemFetchScope::AncestorDepth newDepth) +{ + LOCKED_D(AggregatedItemFetchScope) + if (d->ancestors[oldDepth] > 0) { + --d->ancestors[oldDepth]; + } + ++d->ancestors[newDepth]; +} + +bool AggregatedItemFetchScope::cacheOnly() const +{ + LOCKED_D(const AggregatedItemFetchScope) + // Aggregation: we can return true only if everyone wants cached data only, + // otherwise there's at least one subscriber who wants uncached data + return d->cacheOnly == 0; +} + +void AggregatedItemFetchScope::setCacheOnly(bool cacheOnly) +{ + LOCKED_D(AggregatedItemFetchScope) + d->updateBool(cacheOnly, d->cacheOnly); +} + +bool AggregatedItemFetchScope::fullPayload() const +{ + LOCKED_D(const AggregatedItemFetchScope) + // Aggregation: return true if there's at least one subscriber who wants the + // full payload + return d->fullPayload > 0; +} + +void AggregatedItemFetchScope::setFullPayload(bool fullPayload) +{ + LOCKED_D(AggregatedItemFetchScope) + d->updateBool(fullPayload, d->fullPayload); +} + +bool AggregatedItemFetchScope::allAttributes() const +{ + LOCKED_D(const AggregatedItemFetchScope) + // Aggregation: return true if there's at least one subscriber who wants + // all attributes + return d->allAttributes > 0; +} + +void AggregatedItemFetchScope::setAllAttributes(bool allAttributes) +{ + LOCKED_D(AggregatedItemFetchScope) + d->updateBool(allAttributes, d->allAttributes); +} + +bool AggregatedItemFetchScope::fetchSize() const +{ + LOCKED_D(const AggregatedItemFetchScope) + // Aggregation: return true if there's at least one subscriber who wants size + return d->fetchSize > 0; +} + +void AggregatedItemFetchScope::setFetchSize(bool fetchSize) +{ + LOCKED_D(AggregatedItemFetchScope) + d->updateBool(fetchSize, d->fetchSize); +} + +bool AggregatedItemFetchScope::fetchMTime() const +{ + LOCKED_D(const AggregatedItemFetchScope) + // Aggregation: return true if there's at least one subscriber who wants mtime + return d->fetchMTime > 0; +} + +void AggregatedItemFetchScope::setFetchMTime(bool fetchMTime) +{ + LOCKED_D(AggregatedItemFetchScope) + d->updateBool(fetchMTime, d->fetchMTime); +} + +bool AggregatedItemFetchScope::fetchRemoteRevision() const +{ + LOCKED_D(const AggregatedItemFetchScope) + // Aggregation: return true if there's at least one subscriber who wants rrev + return d->fetchRRev > 0; +} + +void AggregatedItemFetchScope::setFetchRemoteRevision(bool remoteRevision) +{ + LOCKED_D(AggregatedItemFetchScope) + d->updateBool(remoteRevision, d->fetchRRev); +} + +bool AggregatedItemFetchScope::ignoreErrors() const +{ + LOCKED_D(const AggregatedItemFetchScope) + // Aggregation: return true only if everyone wants to ignore errors, otherwise + // there's at least one subscriber who does not want to ignore them + return d->ignoreErrors == 0; +} + +void AggregatedItemFetchScope::setIgnoreErrors(bool ignoreErrors) +{ + LOCKED_D(AggregatedItemFetchScope) + d->updateBool(ignoreErrors, d->ignoreErrors); +} + +bool AggregatedItemFetchScope::fetchFlags() const +{ + LOCKED_D(const AggregatedItemFetchScope) + // Aggregation: return true if there's at least one subscriber who wants flags + return d->fetchFlags > 0; +} + +void AggregatedItemFetchScope::setFetchFlags(bool fetchFlags) +{ + LOCKED_D(AggregatedItemFetchScope) + d->updateBool(fetchFlags, d->fetchFlags); +} + +bool AggregatedItemFetchScope::fetchRemoteId() const +{ + LOCKED_D(const AggregatedItemFetchScope) + // Aggregation: return true if there's at least one subscriber who wants RID + return d->fetchRID > 0; +} + +void AggregatedItemFetchScope::setFetchRemoteId(bool fetchRemoteId) +{ + LOCKED_D(AggregatedItemFetchScope) + d->updateBool(fetchRemoteId, d->fetchRID); +} + +bool AggregatedItemFetchScope::fetchGID() const +{ + LOCKED_D(const AggregatedItemFetchScope) + // Aggregation: return true if there's at least one subscriber who wants GID + return d->fetchGID > 0; +} + +void AggregatedItemFetchScope::setFetchGID(bool fetchGid) +{ + LOCKED_D(AggregatedItemFetchScope) + d->updateBool(fetchGid, d->fetchGID); +} + +bool AggregatedItemFetchScope::fetchTags() const +{ + LOCKED_D(const AggregatedItemFetchScope) + // Aggregation: return true if there's at least one subscriber who wants tags + return d->fetchTags > 0; +} + +void AggregatedItemFetchScope::setFetchTags(bool fetchTags) +{ + LOCKED_D(AggregatedItemFetchScope) + d->updateBool(fetchTags, d->fetchTags); +} + +bool AggregatedItemFetchScope::fetchRelations() const +{ + LOCKED_D(const AggregatedItemFetchScope) + // Aggregation: return true if there's at least one subscriber who wants relations + return d->fetchRelations > 0; +} + +void AggregatedItemFetchScope::setFetchRelations(bool fetchRelations) +{ + LOCKED_D(AggregatedItemFetchScope) + d->updateBool(fetchRelations, d->fetchRelations); +} + +bool AggregatedItemFetchScope::fetchVirtualReferences() const +{ + LOCKED_D(const AggregatedItemFetchScope) + // Aggregation: return true if there's at least one subscriber who wants vrefs + return d->fetchVRefs > 0; +} + +void AggregatedItemFetchScope::setFetchVirtualReferences(bool fetchVRefs) +{ + LOCKED_D(AggregatedItemFetchScope) + d->updateBool(fetchVRefs, d->fetchVRefs); +} + + + + + + +AggregatedTagFetchScope::AggregatedTagFetchScope() + : d_ptr(new AggregatedTagFetchScopePrivate) +{ +} + +AggregatedTagFetchScope::~AggregatedTagFetchScope() +{ + delete d_ptr; +} + +void AggregatedTagFetchScope::apply(const Protocol::TagFetchScope &oldScope, + const Protocol::TagFetchScope &newScope) +{ + LOCKED_D(AggregatedTagFetchScope) + + if (newScope.fetchIdOnly() != oldScope.fetchIdOnly()) { + setFetchIdOnly(newScope.fetchIdOnly()); + } + if (newScope.attributes() != oldScope.attributes()) { + d->applySet(oldScope.attributes(), newScope.attributes(), d->attrs, d->attrsCount); + } +} + + +bool AggregatedTagFetchScope::fetchIdOnly() const +{ + LOCKED_D(const AggregatedTagFetchScope) + // Aggregation: we can return true only if everyone wants fetchIdOnly, + // otherwise there's at least one subscriber who wants everything + return d->fetchIdOnly == 0; +} + +void AggregatedTagFetchScope::setFetchIdOnly(bool fetchIdOnly) +{ + LOCKED_D(AggregatedTagFetchScope) + d->updateBool(fetchIdOnly, d->fetchIdOnly); +} + +QSet AggregatedTagFetchScope::attributes() const +{ + LOCKED_D(const AggregatedTagFetchScope) + return d->attrs; +} + +void AggregatedTagFetchScope::addAttribute(const QByteArray &attribute) +{ + LOCKED_D(AggregatedTagFetchScope) + d->addToSet(attribute, d->attrs, d->attrsCount); +} + +void AggregatedTagFetchScope::removeAttribute(const QByteArray &attribute) +{ + LOCKED_D(AggregatedTagFetchScope) + d->removeFromSet(attribute, d->attrs, d->attrsCount); +} + +#undef LOCKED_D diff --git a/src/server/akonadi.h b/src/server/akonadi.h --- a/src/server/akonadi.h +++ b/src/server/akonadi.h @@ -58,6 +58,9 @@ */ IntervalCheck *intervalChecker(); + /** + * Instance-aware server .config directory + */ QString serverPath() const; /** @@ -102,7 +105,7 @@ QProcess *mDatabaseProcess = nullptr; QVector mConnections; SearchManager *mSearchManager = nullptr; - bool mAlreadyShutdown; + bool mAlreadyShutdown = false; static AkonadiServer *s_instance; }; diff --git a/src/server/akonadi.cpp b/src/server/akonadi.cpp --- a/src/server/akonadi.cpp +++ b/src/server/akonadi.cpp @@ -18,6 +18,7 @@ ***************************************************************************/ #include "akonadi.h" +#include "handler.h" #include "connection.h" #include "serveradaptor.h" #include "akonadiserver_debug.h" @@ -41,7 +42,6 @@ #include "collectionreferencemanager.h" -#include #include #include #include @@ -56,29 +56,13 @@ #include #include -#ifdef Q_OS_WIN -#include -#include -#endif - using namespace Akonadi; using namespace Akonadi::Server; AkonadiServer *AkonadiServer::s_instance = nullptr; AkonadiServer::AkonadiServer(QObject *parent) : QObject(parent) - , mCmdServer(nullptr) - , mNtfServer(nullptr) - , mNotificationManager(nullptr) - , mCacheCleaner(nullptr) - , mIntervalCheck(nullptr) - , mStorageJanitor(nullptr) - , mItemRetrieval(nullptr) - , mAgentSearchManager(nullptr) - , mDatabaseProcess(nullptr) - , mSearchManager(nullptr) - , mAlreadyShutdown(false) { // Register bunch of useful types qRegisterMetaType(); @@ -89,7 +73,7 @@ bool AkonadiServer::init() { - const QString serverConfigFile = StandardDirs::serverConfigFile(XdgBaseDirs::ReadWrite); + const QString serverConfigFile = StandardDirs::serverConfigFile(StandardDirs::ReadWrite); QSettings settings(serverConfigFile, QSettings::IniFormat); // Restrict permission to 600, as the file might contain database password in plaintext QFile::setPermissions(serverConfigFile, QFile::ReadOwner | QFile::WriteOwner); @@ -115,7 +99,7 @@ s_instance = this; - const QString connectionSettingsFile = StandardDirs::connectionConfigFile(XdgBaseDirs::WriteOnly); + const QString connectionSettingsFile = StandardDirs::connectionConfigFile(StandardDirs::WriteOnly); QSettings connectionSettings(connectionSettingsFile, QSettings::IniFormat); mCmdServer = new AkLocalServer(this); @@ -127,48 +111,23 @@ // own thread connect(mNtfServer, QOverload::of(&AkLocalServer::newConnection), mNotificationManager, &NotificationManager::registerConnection); + // TODO: share socket setup with client #ifdef Q_OS_WIN - HANDLE hToken = NULL; - PSID sid; - QString userID; - - OpenProcessToken(GetCurrentProcess(), TOKEN_READ, &hToken); - if (hToken) { - DWORD size; - PTOKEN_USER userStruct; - - GetTokenInformation(hToken, TokenUser, NULL, 0, &size); - if (ERROR_INSUFFICIENT_BUFFER == GetLastError()) { - userStruct = reinterpret_cast(new BYTE[size]); - GetTokenInformation(hToken, TokenUser, userStruct, size, &size); - - int sidLength = GetLengthSid(userStruct->User.Sid); - sid = (PSID) malloc(sidLength); - CopySid(sidLength, sid, userStruct->User.Sid); - CloseHandle(hToken); - delete [] userStruct; - } - - LPWSTR s; - if (!ConvertSidToStringSidW(sid, &s)) { - qCCritical(AKONADISERVER_LOG) << "Could not determine user id for current process."; - userID = QString(); - } else { - userID = QString::fromUtf16(reinterpret_cast(s)); - LocalFree(s); - } - free(sid); + // use the installation prefix as uid + QString suffix; + if (Instance::hasIdentifier()) { + suffix = QStringLiteral("%1-").arg(Instance::identifier()); } - - const QString defaultCmdPipe = QStringLiteral("Akonadi-Cmd-") % userID; + suffix += QString::fromUtf8(QUrl::toPercentEncoding(qApp->applicationDirPath())); + const QString defaultCmdPipe = QStringLiteral("Akonadi-Cmd-") % suffix; const QString cmdPipe = settings.value(QStringLiteral("Connection/NamedPipe"), defaultCmdPipe).toString(); if (!mCmdServer->listen(cmdPipe)) { qCCritical(AKONADISERVER_LOG) << "Unable to listen on Named Pipe" << cmdPipe; quit(); return false; } - const QString defaultNtfPipe = QStringLiteral("Akonadi-Ntf-") % userID; + const QString defaultNtfPipe = QStringLiteral("Akonadi-Ntf-") % suffix; const QString ntfPipe = settings.value(QStringLiteral("Connection/NtfNamedPipe"), defaultNtfPipe).toString(); if (!mNtfServer->listen(ntfPipe)) { qCCritical(AKONADISERVER_LOG) << "Unable to listen on Named Pipe" << ntfPipe; @@ -327,7 +286,7 @@ } //QSettings settings(StandardDirs::serverConfigFile(), QSettings::IniFormat); - const QString connectionSettingsFile = StandardDirs::connectionConfigFile(XdgBaseDirs::WriteOnly); + const QString connectionSettingsFile = StandardDirs::connectionConfigFile(StandardDirs::WriteOnly); if (!QDir::home().remove(connectionSettingsFile)) { qCCritical(AKONADISERVER_LOG) << "Failed to remove runtime connection config file"; @@ -462,5 +421,5 @@ QString AkonadiServer::serverPath() const { - return XdgBaseDirs::homePath("config"); + return StandardDirs::saveDir("config"); } diff --git a/src/server/akthread.h b/src/server/akthread.h --- a/src/server/akthread.h +++ b/src/server/akthread.h @@ -42,7 +42,7 @@ explicit AkThread(const QString &objectName, StartMode startMode, QThread::Priority priority = QThread::InheritPriority, QObject *parent = nullptr); - virtual ~AkThread(); + ~AkThread() override; protected: void quitThread(); diff --git a/src/server/akthread.cpp b/src/server/akthread.cpp --- a/src/server/akthread.cpp +++ b/src/server/akthread.cpp @@ -51,15 +51,26 @@ void AkThread::startThread() { +#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) + const bool init = QMetaObject::invokeMethod(this, &AkThread::init, Qt::QueuedConnection); +#else const bool init = QMetaObject::invokeMethod(this, "init", Qt::QueuedConnection); - Q_ASSERT(init); Q_UNUSED(init); +#endif + Q_ASSERT(init); + Q_UNUSED(init); } void AkThread::quitThread() { qCDebug(AKONADISERVER_LOG) << "Shutting down" << objectName() << "..."; +#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) + const bool invoke = QMetaObject::invokeMethod(this, &AkThread::quit, Qt::QueuedConnection); +#else const bool invoke = QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection); - Q_ASSERT(invoke); Q_UNUSED(invoke); +#endif + + Q_ASSERT(invoke); + Q_UNUSED(invoke); if (!thread()->wait(10 * 1000)) { thread()->terminate(); thread()->wait(); diff --git a/src/server/cachecleaner.h b/src/server/cachecleaner.h --- a/src/server/cachecleaner.h +++ b/src/server/cachecleaner.h @@ -70,7 +70,7 @@ @param parent The parent object. */ explicit CacheCleaner(QObject *parent = nullptr); - ~CacheCleaner(); + ~CacheCleaner() override; protected: void collectionExpired(const Collection &collection) override; diff --git a/src/server/collectionscheduler.h b/src/server/collectionscheduler.h --- a/src/server/collectionscheduler.h +++ b/src/server/collectionscheduler.h @@ -41,7 +41,7 @@ public: explicit CollectionScheduler(const QString &threadName, QThread::Priority priority, QObject *parent = nullptr); - virtual ~CollectionScheduler(); + ~CollectionScheduler() override; void collectionChanged(qint64 collectionId); void collectionRemoved(qint64 collectionId); @@ -55,7 +55,7 @@ * @p intervalMinutes Minimum timeout interval in minutes. */ void setMinimumInterval(int intervalMinutes); - int minimumInterval() const; + Q_REQUIRED_RESULT int minimumInterval() const; protected: virtual void init() override; diff --git a/src/server/collectionscheduler.cpp b/src/server/collectionscheduler.cpp --- a/src/server/collectionscheduler.cpp +++ b/src/server/collectionscheduler.cpp @@ -126,10 +126,18 @@ void CollectionScheduler::inhibit(bool inhibit) { if (inhibit) { +#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) + const bool success = QMetaObject::invokeMethod(mScheduler, &PauseableTimer::pause, Qt::QueuedConnection); +#else const bool success = QMetaObject::invokeMethod(mScheduler, "pause", Qt::QueuedConnection); +#endif Q_ASSERT(success); Q_UNUSED(success); } else { +#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) + const bool success = QMetaObject::invokeMethod(mScheduler, &PauseableTimer::resume, Qt::QueuedConnection); +#else const bool success = QMetaObject::invokeMethod(mScheduler, "resume", Qt::QueuedConnection); +#endif Q_ASSERT(success); Q_UNUSED(success); } } @@ -149,9 +157,13 @@ Collection collection = Collection::retrieveById(collectionId); DataStore::self()->activeCachePolicy(collection); if (shouldScheduleCollection(collection)) { +#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) + QMetaObject::invokeMethod(this, [this, collection]() {scheduleCollection(collection);}, Qt::QueuedConnection); +#else QMetaObject::invokeMethod(this, "scheduleCollection", Qt::QueuedConnection, Q_ARG(Collection, collection)); +#endif } } diff --git a/src/server/connection.h b/src/server/connection.h --- a/src/server/connection.h +++ b/src/server/connection.h @@ -30,8 +30,12 @@ #include "entities.h" #include "global.h" #include "commandcontext.h" +#include "tracer.h" #include +#include + +#include namespace Akonadi { @@ -52,7 +56,9 @@ Q_OBJECT public: explicit Connection(quintptr socketDescriptor, QObject *parent = nullptr); - virtual ~Connection(); + ~Connection() override; + + static Connection *self(); virtual DataStore *storageBackend(); @@ -74,62 +80,95 @@ Protocol::CommandPtr readCommand(); -public Q_SLOTS: - virtual void sendResponse(const Protocol::CommandPtr &response); + void setState(ConnectionState state); + + template + inline typename std::enable_if::value>::type + sendResponse(T &&response); + + void sendResponse(qint64 tag, const Protocol::CommandPtr &response); Q_SIGNALS: void disconnected(); void connectionClosing(); protected Q_SLOTS: - /** - * New data arrived from the client. Creates a handler for it and passes the data to the handler. - */ - void slotNewData(); - void slotConnectionStateChange(ConnectionState state); + void handleIncomingData(); + void slotConnectionIdle(); void slotSocketDisconnected(); void slotSendHello(); protected: Connection(QObject *parent = nullptr); // used for testing - virtual void init() override; - virtual void quit() override; + void init() override; + void quit() override; - virtual Handler *findHandlerForCommand(Protocol::Command::Type cmd); + Handler *findHandlerForCommand(Protocol::Command::Type cmd); + + qint64 currentTag() const; protected: - quintptr m_socketDescriptor; + quintptr m_socketDescriptor = {}; QLocalSocket *m_socket = nullptr; - QPointer m_currentHandler; - ConnectionState m_connectionState; + std::unique_ptr m_currentHandler; + ConnectionState m_connectionState = NonAuthenticated; mutable DataStore *m_backend = nullptr; QList m_statusMessageQueue; QString m_identifier; QByteArray m_sessionId; - bool m_verifyCacheOnRetrieval; + bool m_verifyCacheOnRetrieval = false; CommandContext m_context; QTimer *m_idleTimer = nullptr; QTime m_time; - qint64 m_totalTime; + qint64 m_totalTime = 0; QHash m_totalTimeByHandler; QHash m_executionsByHandler; - bool m_connectionClosing; + bool m_connectionClosing = false; private: - void sendResponse(qint64 tag, const Protocol::CommandPtr &response); + template + inline typename std::enable_if::value>::type + sendResponse(qint64 tag, T &&response); /** For debugging */ void startTime(); void stopTime(const QString &identifier); void reportTime() const; - bool m_reportTime; + bool m_reportTime = false; }; +template +inline typename std::enable_if::value>::type +Connection::sendResponse(T &&response) +{ + sendResponse(currentTag(), std::move(response)); +} + +template +inline typename std::enable_if::value>::type +Connection::sendResponse(qint64 tag, T &&response) +{ + if (Tracer::self()->currentTracer() != QLatin1String("null")) { + Tracer::self()->connectionOutput(m_identifier, tag, response); + } + Protocol::DataStream stream(m_socket); + stream << tag; + stream << std::move(response); + if (!m_socket->waitForBytesWritten()) { + if (m_socket->state() == QLocalSocket::ConnectedState) { + throw ProtocolException("Server write timeout"); + } else { + // The client has disconnected before we managed to send our response, + // which is not an error + } + } +} + } // namespace Server } // namespace Akonadi diff --git a/src/server/connection.cpp b/src/server/connection.cpp --- a/src/server/connection.cpp +++ b/src/server/connection.cpp @@ -23,6 +23,9 @@ #include #include +#include +#include +#include #include "storage/datastore.h" #include "handler.h" @@ -37,6 +40,8 @@ #include #endif +#include + #include #include #include @@ -52,35 +57,38 @@ return id; } +namespace +{ +Q_GLOBAL_STATIC(QThreadStorage>, sConnectionStore) +} + Connection::Connection(QObject *parent) : AkThread(connectionIdentifier(this), QThread::InheritPriority, parent) - , m_socketDescriptor(0) - , m_socket(nullptr) - , m_currentHandler(nullptr) - , m_connectionState(NonAuthenticated) - , m_backend(nullptr) - , m_verifyCacheOnRetrieval(false) - , m_idleTimer(nullptr) - , m_totalTime(0) - , m_connectionClosing(false) - , m_reportTime(false) { } Connection::Connection(quintptr socketDescriptor, QObject *parent) - : Connection(parent) + : AkThread(connectionIdentifier(this), QThread::InheritPriority, parent) { m_socketDescriptor = socketDescriptor; m_identifier = connectionIdentifier(this); // same as objectName() const QSettings settings(Akonadi::StandardDirs::serverConfigFile(), QSettings::IniFormat); m_verifyCacheOnRetrieval = settings.value(QStringLiteral("Cache/VerifyOnRetrieval"), m_verifyCacheOnRetrieval).toBool(); } +Connection *Connection::self() +{ + Q_ASSERT(sConnectionStore->hasLocalData()); + return sConnectionStore->localData(); +} + void Connection::init() { AkThread::init(); + sConnectionStore->setLocalData(this); + QLocalSocket *socket = new QLocalSocket(); if (!socket->setSocketDescriptor(m_socketDescriptor)) { @@ -92,26 +100,27 @@ } m_socket = socket; - - /* Whenever a full command has been read, it is delegated to the responsible - * handler and processed by that. If that command needs to do something - * asynchronous such as ask the db for data, it returns and the input - * queue can continue to be processed. Whenever there is something to - * be sent back to the user it is queued in the form of a Response object. - * All this is meant to make it possible to process large incoming or - * outgoing data transfers in a streaming manner, without having to - * hold them in memory 'en gros'. */ - - connect(socket, &QIODevice::readyRead, - this, &Connection::slotNewData); connect(socket, &QLocalSocket::disconnected, this, &Connection::slotSocketDisconnected); m_idleTimer = new QTimer(this); connect(m_idleTimer, &QTimer::timeout, this, &Connection::slotConnectionIdle); - slotSendHello(); + + if (socket->state() == QLocalSocket::ConnectedState) { + QTimer::singleShot(0, this, &Connection::handleIncomingData); + } else { + connect(socket, &QLocalSocket::connected, this, &Connection::handleIncomingData, + Qt::QueuedConnection); + } + + try { + slotSendHello(); + } catch (const ProtocolException &e) { + qCWarning(AKONADISERVER_LOG) << "Protocol Exception sending \"hello\":" << e.what(); + m_socket->disconnectFromServer(); + } } void Connection::quit() @@ -140,12 +149,12 @@ { SchemaVersion version = SchemaVersion::retrieveAll().first(); - auto hello = Protocol::HelloResponsePtr::create(); - hello->setServerName(QStringLiteral("Akonadi")); - hello->setMessage(QStringLiteral("Not Really IMAP server")); - hello->setProtocolVersion(Protocol::version()); - hello->setGeneration(version.generation()); - sendResponse(0, hello); + Protocol::HelloResponse hello; + hello.setServerName(QStringLiteral("Akonadi")); + hello.setMessage(QStringLiteral("Not Really IMAP server")); + hello.setProtocolVersion(Protocol::version()); + hello.setGeneration(version.generation()); + sendResponse(0, std::move(hello)); } DataStore *Connection::storageBackend() @@ -179,7 +188,7 @@ // But it is safer to abort and leave the connection open, until // a later operation causes the idle timer to fire (than crash // the akonadi server). - qCDebug(AKONADISERVER_LOG) << "NOT Closing idle db connection; we are in transaction"; + qCDebug(AKONADISERVER_LOG) << m_sessionId << "NOT Closing idle db connection; we are in transaction"; return; } m_backend->close(); @@ -197,144 +206,163 @@ Q_EMIT disconnected(); } -void Connection::slotNewData() +void Connection::handleIncomingData() { - if (m_socket->state() != QLocalSocket::ConnectedState) { - return; - } + Q_FOREVER { - m_idleTimer->stop(); + if (m_connectionClosing || !m_socket || m_socket->state() != QLocalSocket::ConnectedState) { + break; + } - // will only open() a previously idle backend. - // Otherwise, a new backend could lazily be constructed by later calls. - if (!storageBackend()->isOpened()) { - m_backend->open(); - } + // Blocks with event loop until some data arrive, allows us to still use QTimers + // and similar while waiting for some data to arrive + if (m_socket->bytesAvailable() < int(sizeof(qint64))) { + QEventLoop loop; + connect(m_socket, &QLocalSocket::readyRead, &loop, &QEventLoop::quit); + connect(m_socket, &QLocalSocket::stateChanged, &loop, &QEventLoop::quit); + connect(this, &Connection::connectionClosing, &loop, &QEventLoop::quit); + loop.exec(); + } - QString currentCommand; - while (m_socket->bytesAvailable() > (int) sizeof(qint64)) { - QDataStream stream(m_socket); + if (m_connectionClosing || !m_socket || m_socket->state() != QLocalSocket::ConnectedState) { + break; + } - qint64 tag = -1; - stream >> tag; - // TODO: Check tag is incremental sequence + m_idleTimer->stop(); - Protocol::CommandPtr cmd; - try { - cmd = Protocol::deserialize(m_socket); - } catch (const Akonadi::ProtocolException &e) { - qCWarning(AKONADISERVER_LOG) << "ProtocolException:" << e.what(); - slotConnectionStateChange(Server::LoggingOut); - return; - } catch (const std::exception &e) { - qCWarning(AKONADISERVER_LOG) << "Unknown exception:" << e.what(); - slotConnectionStateChange(Server::LoggingOut); - return; - } - if (cmd->type() == Protocol::Command::Invalid) { - qCWarning(AKONADISERVER_LOG) << "Received an invalid command: resetting connection"; - slotConnectionStateChange(Server::LoggingOut); - return; + // will only open() a previously idle backend. + // Otherwise, a new backend could lazily be constructed by later calls. + if (!storageBackend()->isOpened()) { + m_backend->open(); } - // Tag context and collection context is not persistent. - context()->setTag(-1); - context()->setCollection(Collection()); - if (Tracer::self()->currentTracer() != QLatin1String("null")) { - Tracer::self()->connectionInput(m_identifier, QByteArray::number(tag) + ' ' + Protocol::debugString(cmd).toUtf8()); - } + QString currentCommand; + while (m_socket->bytesAvailable() >= int(sizeof(qint64))) { + Protocol::DataStream stream(m_socket); + qint64 tag = -1; + stream >> tag; + // TODO: Check tag is incremental sequence + + Protocol::CommandPtr cmd; + try { + cmd = Protocol::deserialize(m_socket); + } catch (const Akonadi::ProtocolException &e) { + qCWarning(AKONADISERVER_LOG) << "ProtocolException:" << e.what(); + setState(Server::LoggingOut); + return; + } catch (const std::exception &e) { + qCWarning(AKONADISERVER_LOG) << "Unknown exception:" << e.what(); + setState(Server::LoggingOut); + return; + } + if (cmd->type() == Protocol::Command::Invalid) { + qCWarning(AKONADISERVER_LOG) << "Received an invalid command: resetting connection"; + setState(Server::LoggingOut); + return; + } - m_currentHandler = findHandlerForCommand(cmd->type()); - if (!m_currentHandler) { - qCWarning(AKONADISERVER_LOG) << "Invalid command: no such handler for" << cmd->type(); - slotConnectionStateChange(Server::LoggingOut); - return; - } - if (m_reportTime) { - startTime(); - } - connect(m_currentHandler.data(), &Handler::connectionStateChange, - this, &Connection::slotConnectionStateChange, - Qt::DirectConnection); - - m_currentHandler->setConnection(this); - m_currentHandler->setTag(tag); - m_currentHandler->setCommand(cmd); - try { - if (!m_currentHandler->parseStream()) { - try { - m_currentHandler->failureResponse("Unknown error while handling a command"); - } catch (...) { - qCWarning(AKONADISERVER_LOG) << "Unknown error while handling a command"; - m_connectionClosing = true; - } + // Tag context and collection context is not persistent. + context()->setTag(-1); + context()->setCollection(Collection()); + if (Tracer::self()->currentTracer() != QLatin1String("null")) { + Tracer::self()->connectionInput(m_identifier, tag, cmd); } - } catch (const Akonadi::Server::HandlerException &e) { - if (m_currentHandler) { - try { - m_currentHandler->failureResponse(e.what()); - } catch (...) { - qCWarning(AKONADISERVER_LOG) << "Handler exception:" << e.what(); - m_connectionClosing = true; - } + + m_currentHandler = std::unique_ptr(findHandlerForCommand(cmd->type())); + if (!m_currentHandler) { + qCWarning(AKONADISERVER_LOG) << "Invalid command: no such handler for" << cmd->type(); + setState(Server::LoggingOut); + return; } - } catch (const Akonadi::Server::Exception &e) { - if (m_currentHandler) { - try { - m_currentHandler->failureResponse(QString::fromUtf8(e.type()) + QLatin1String(": ") + QString::fromUtf8(e.what())); - } catch (...) { - qCWarning(AKONADISERVER_LOG) << e.type() << "exception:" << e.what(); - m_connectionClosing = true; - } + if (m_reportTime) { + startTime(); } - } catch (const Akonadi::ProtocolException &e) { - // No point trying to send anything back to client, the connection is - // already messed up - qCWarning(AKONADISERVER_LOG) << "Protocol exception:" << e.what(); - m_connectionClosing = true; + + m_currentHandler->setConnection(this); + m_currentHandler->setTag(tag); + m_currentHandler->setCommand(cmd); + try { + if (!m_currentHandler->parseStream()) { + try { + m_currentHandler->failureResponse("Unknown error while handling a command"); + } catch (...) { + qCWarning(AKONADISERVER_LOG) << "Unknown error while handling a command"; + m_connectionClosing = true; + } + } + } catch (const Akonadi::Server::HandlerException &e) { + if (m_currentHandler) { + try { + m_currentHandler->failureResponse(e.what()); + } catch (...) { + qCWarning(AKONADISERVER_LOG) << "Handler exception:" << e.what(); + m_connectionClosing = true; + } + } + } catch (const Akonadi::Server::Exception &e) { + if (m_currentHandler) { + try { + m_currentHandler->failureResponse(QString::fromUtf8(e.type()) + QLatin1String(": ") + QString::fromUtf8(e.what())); + } catch (...) { + qCWarning(AKONADISERVER_LOG) << e.type() << "exception:" << e.what(); + m_connectionClosing = true; + } + } + } catch (const Akonadi::ProtocolException &e) { + // No point trying to send anything back to client, the connection is + // already messed up + qCWarning(AKONADISERVER_LOG) << "Protocol exception:" << e.what(); + m_connectionClosing = true; #if defined(Q_OS_LINUX) - } catch (abi::__forced_unwind&) { - // HACK: NPTL throws __forced_unwind during thread cancellation and - // we *must* rethrow it otherwise the program aborts. Due to the issue - // described in #376385 we might end up destroying (cancelling) the - // thread from a nested loop executed inside parseStream() above, - // so the exception raised in there gets caught by this try..catch - // statement and it must be rethrown at all cost. Remove this hack - // once the root problem is fixed. - throw; + } catch (abi::__forced_unwind&) { + // HACK: NPTL throws __forced_unwind during thread cancellation and + // we *must* rethrow it otherwise the program aborts. Due to the issue + // described in #376385 we might end up destroying (cancelling) the + // thread from a nested loop executed inside parseStream() above, + // so the exception raised in there gets caught by this try..catch + // statement and it must be rethrown at all cost. Remove this hack + // once the root problem is fixed. + throw; #endif - } catch (...) { - qCCritical(AKONADISERVER_LOG) << "Unknown exception caught in Connection for session" << m_sessionId; - if (m_currentHandler) { - try { - m_currentHandler->failureResponse("Unknown exception caught"); - } catch (...) { - qCWarning(AKONADISERVER_LOG) << "Unknown exception caught"; - m_connectionClosing = true; + } catch (...) { + qCCritical(AKONADISERVER_LOG) << "Unknown exception caught in Connection for session" << m_sessionId; + if (m_currentHandler) { + try { + m_currentHandler->failureResponse("Unknown exception caught"); + } catch (...) { + qCWarning(AKONADISERVER_LOG) << "Unknown exception caught"; + m_connectionClosing = true; + } } } - } - if (m_reportTime) { - stopTime(currentCommand); - } - delete m_currentHandler; - m_currentHandler = nullptr; + if (m_reportTime) { + stopTime(currentCommand); + } + m_currentHandler.reset(); - if (m_socket->state() != QLocalSocket::ConnectedState) { - Q_EMIT disconnected(); - return; + if (!m_socket || m_socket->state() != QLocalSocket::ConnectedState) { + Q_EMIT disconnected(); + return; + } + + if (m_connectionClosing) { + break; + } } + // reset, arm the timer + m_idleTimer->start(IDLE_TIMER_TIMEOUT); + if (m_connectionClosing) { - m_socket->disconnect(this); - m_socket->close(); - QTimer::singleShot(0, this, &Connection::quit); - return; + break; } } - // reset, arm the timer - m_idleTimer->start(IDLE_TIMER_TIMEOUT); + if (m_connectionClosing) { + m_socket->disconnect(this); + m_socket->close(); + QTimer::singleShot(0, this, &Connection::quit); + } } CommandContext *Connection::context() const @@ -365,7 +393,12 @@ return handler; } -void Connection::slotConnectionStateChange(ConnectionState state) +qint64 Connection::currentTag() const +{ + return m_currentHandler->tag(); +} + +void Connection::setState(ConnectionState state) { if (state == m_connectionState) { return; @@ -459,29 +492,29 @@ void Connection::sendResponse(qint64 tag, const Protocol::CommandPtr &response) { if (Tracer::self()->currentTracer() != QLatin1String("null")) { - Tracer::self()->connectionOutput(m_identifier, QByteArray::number(tag) + ' ' + Protocol::debugString(response).toUtf8()); + Tracer::self()->connectionOutput(m_identifier, tag, response); } - QDataStream stream(m_socket); + Protocol::DataStream stream(m_socket); stream << tag; Protocol::serialize(m_socket, response); + if (!m_socket->waitForBytesWritten()) { + if (m_socket->state() == QLocalSocket::ConnectedState) { + throw ProtocolException("Server write timeout"); + } else { + // The client has disconnected before we managed to send our response, + // which is not an error + } + } } -void Connection::sendResponse(const Protocol::CommandPtr &response) -{ - Q_ASSERT(m_currentHandler); - sendResponse(m_currentHandler->tag(), response); -} Protocol::CommandPtr Connection::readCommand() { while (m_socket->bytesAvailable() < (int) sizeof(qint64)) { - if (m_socket->state() == QLocalSocket::UnconnectedState) { - throw ProtocolException("Socket disconnected"); - } - m_socket->waitForReadyRead(500); + Protocol::DataStream::waitForData(m_socket, 10000); // 10 seconds, just in case client is busy } - QDataStream stream(m_socket); + Protocol::DataStream stream(m_socket); qint64 tag; stream >> tag; diff --git a/src/server/dbustracer.h b/src/server/dbustracer.h --- a/src/server/dbustracer.h +++ b/src/server/dbustracer.h @@ -38,7 +38,7 @@ public: DBusTracer(); - virtual ~DBusTracer(); + ~DBusTracer() override; void beginConnection(const QString &identifier, const QString &msg) override; void endConnection(const QString &identifier, const QString &msg) override; @@ -48,6 +48,8 @@ void warning(const QString &componentName, const QString &msg) override; void error(const QString &componentName, const QString &msg) override; + TracerInterface::ConnectionFormat connectionFormat() const override {return TracerInterface::Json;} + Q_SIGNALS: void connectionStarted(const QString &identifier, const QString &msg); void connectionEnded(const QString &identifier, const QString &msg); diff --git a/src/server/filetracer.h b/src/server/filetracer.h --- a/src/server/filetracer.h +++ b/src/server/filetracer.h @@ -36,7 +36,7 @@ { public: explicit FileTracer(const QString &fileName); - virtual ~FileTracer(); + ~FileTracer() override; void beginConnection(const QString &identifier, const QString &msg) override; void endConnection(const QString &identifier, const QString &msg) override; diff --git a/src/server/handler.h b/src/server/handler.h --- a/src/server/handler.h +++ b/src/server/handler.h @@ -34,7 +34,6 @@ { class Response; -class Connection; AKONADI_EXCEPTION_MAKE_INSTANCE(HandlerException); @@ -46,13 +45,11 @@ /** The handler interfaces describes an entity capable of handling an AkonadiIMAP command.*/ -class Handler : public QObject +class Handler { - Q_OBJECT public: - Handler(); - - virtual ~Handler(); + Handler() = default; + virtual ~Handler() = default; /** * Set the tag of the command to be processed, and thus of the response @@ -67,7 +64,6 @@ quint64 tag() const; void setCommand(const Protocol::CommandPtr &cmd); - Protocol::CommandPtr command() const; /** @@ -99,12 +95,14 @@ bool failureResponse(const QString &response); template - typename std::enable_if::value, bool>::type - successResponse(const QSharedPointer &response = QSharedPointer()); + inline bool successResponse(); + template + inline bool successResponse(T &&response); template - typename std::enable_if::value, void>::type - sendResponse(const QSharedPointer &response = QSharedPointer()); + inline void sendResponse(T &&response); + template + inline void sendResponse(); /** * Parse and handle the IMAP message using the streaming parser. The implementation MUST leave the trailing newline character(s) in the stream! @@ -114,40 +112,39 @@ bool checkScopeConstraints(const Scope &scope, int permittedScopes); -public Q_SLOTS: - void sendResponse(const Protocol::CommandPtr &response); - -Q_SIGNALS: - /** - * Emitted whenever a handler wants the connection to change into a - * different state. The connection usually honors such requests, but - * the decision is up to it. - * @param state The new state the handler suggests to enter. - */ - void connectionStateChange(ConnectionState state); - private: - quint64 m_tag; + quint64 m_tag = 0; Connection *m_connection = nullptr; - bool m_sentFailureResponse; + bool m_sentFailureResponse = false; protected: Protocol::CommandPtr m_command; }; template -typename std::enable_if::value, bool>::type -Handler::successResponse(const QSharedPointer &response) +inline bool Handler::successResponse() { - sendResponse(response ? response : QSharedPointer::create()); + sendResponse(T{}); return true; } template -typename std::enable_if::value, void>::type -Handler::sendResponse(const QSharedPointer &response) +inline bool Handler::successResponse(T &&response) +{ + sendResponse(std::move(response)); + return true; +} + +template +inline void Handler::sendResponse() +{ + m_connection->sendResponse(T{}); +} + +template +inline void Handler::sendResponse(T &&response) { - sendResponse(response.template staticCast()); + m_connection->sendResponse(std::move(response)); } } // namespace Server diff --git a/src/server/handler.cpp b/src/server/handler.cpp --- a/src/server/handler.cpp +++ b/src/server/handler.cpp @@ -57,17 +57,6 @@ using namespace Akonadi; using namespace Akonadi::Server; -Handler::Handler() - : QObject() - , m_connection(nullptr) - , m_sentFailureResponse(false) -{ -} - -Handler::~Handler() -{ -} - Handler *Handler::findHandlerForCommandNonAuthenticated(Protocol::Command::Type cmd) { // allowed are LOGIN @@ -87,26 +76,6 @@ 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; -} - Handler *Handler::findHandlerForCommandAuthenticated(Protocol::Command::Type cmd) { switch (cmd) { @@ -119,12 +88,8 @@ "Hello command is not allowed in this context"); return nullptr; case Protocol::Command::Login: - Q_ASSERT_X(cmd != Protocol::Command::StreamPayload, __FUNCTION__, - "Login command is not allowed in this context"); return nullptr; case Protocol::Command::Logout: - Q_ASSERT_X(cmd != Protocol::Command::StreamPayload, __FUNCTION__, - "Logout command is not allowed in this context"); return nullptr; case Protocol::Command::_ResponseBit: Q_ASSERT_X(cmd != Protocol::Command::_ResponseBit, __FUNCTION__, @@ -232,6 +197,26 @@ 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; @@ -263,17 +248,12 @@ // FIXME: Error enums? r->setError(1, failureMessage); - sendResponse(r); + m_connection->sendResponse(m_tag, r); } return false; } -void Handler::sendResponse(const Protocol::CommandPtr &response) -{ - m_connection->sendResponse(response); -} - bool Handler::checkScopeConstraints(const Akonadi::Scope &scope, int permittedScopes) { return scope.scope() & permittedScopes; diff --git a/src/server/handler/akappend.h b/src/server/handler/akappend.h --- a/src/server/handler/akappend.h +++ b/src/server/handler/akappend.h @@ -37,8 +37,9 @@ */ class AkAppend : public Handler { - Q_OBJECT public: + ~AkAppend() override = default; + bool parseStream() override; private: diff --git a/src/server/handler/akappend.cpp b/src/server/handler/akappend.cpp --- a/src/server/handler/akappend.cpp +++ b/src/server/handler/akappend.cpp @@ -48,10 +48,10 @@ { parentCol = HandlerHelper::collectionFromScope(cmd.collection(), connection()); if (!parentCol.isValid()) { - return failureResponse("Invalid parent collection"); + return failureResponse(QStringLiteral("Invalid parent collection")); } if (parentCol.isVirtual()) { - return failureResponse("Cannot append item into virtual collection"); + return failureResponse(QStringLiteral("Cannot append item into virtual collection")); } MimeType mimeType = MimeType::retrieveByNameOrCreate(cmd.mimeType()); @@ -87,7 +87,7 @@ } if (!item.insert()) { - return failureResponse("Failed to append item"); + return failureResponse(QStringLiteral("Failed to append item")); } // set message flags @@ -106,15 +106,13 @@ const Tag::List tagList = HandlerHelper::tagsFromScope(tags, connection()); bool tagsChanged = false; if (!DataStore::self()->appendItemsTags(PimItem::List() << item, tagList, &tagsChanged, false, parentCol, true)) { - return failureResponse("Unable to append item tags."); + return failureResponse(QStringLiteral("Unable to append item tags.")); } } // Handle individual parts qint64 partSizes = 0; - PartStreamer streamer(connection(), item, this); - connect(&streamer, &PartStreamer::responseAvailable, - this, static_cast(&Handler::sendResponse)); + PartStreamer streamer(connection(), item); Q_FOREACH (const QByteArray &partName, cmd.parts()) { qint64 partSize = 0; if (!streamer.stream(true, partName, partSize)) { @@ -260,8 +258,6 @@ } PartStreamer streamer(connection(), currentItem); - connect(&streamer, &PartStreamer::responseAvailable, - this, static_cast(&Handler::sendResponse)); Q_FOREACH (const QByteArray &partName, cmd.parts()) { bool changed = false; qint64 partSize = 0; @@ -304,25 +300,25 @@ bool AkAppend::sendResponse(const PimItem &item, Protocol::CreateItemCommand::MergeModes mergeModes) { if (mergeModes & Protocol::CreateItemCommand::Silent || mergeModes & Protocol::CreateItemCommand::None) { - auto resp = Protocol::FetchItemsResponsePtr::create(); - resp->setId(item.id()); - resp->setMTime(item.datetime()); - Handler::sendResponse(resp); + Protocol::FetchItemsResponse resp; + resp.setId(item.id()); + resp.setMTime(item.datetime()); + Handler::sendResponse(std::move(resp)); return true; } - Protocol::FetchScope fetchScope; - fetchScope.setAncestorDepth(Protocol::FetchScope::ParentAncestor); - fetchScope.setFetch(Protocol::FetchScope::AllAttributes | - Protocol::FetchScope::FullPayload | - Protocol::FetchScope::CacheOnly | - Protocol::FetchScope::Flags | - Protocol::FetchScope::GID | - Protocol::FetchScope::MTime | - Protocol::FetchScope::RemoteID | - Protocol::FetchScope::RemoteRevision | - Protocol::FetchScope::Size | - Protocol::FetchScope::Tags); + 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); fetchScope.setTagFetchScope({ "GID" }); ImapSet set; @@ -382,7 +378,7 @@ return false; } if (!transaction.commit()) { - return failureResponse("Failed to commit transaction"); + return failureResponse(QStringLiteral("Failed to commit transaction")); } storageTrx.commit(); } else { @@ -440,15 +436,15 @@ storageTrx.commit(); } else { qCDebug(AKONADISERVER_LOG) << "Multiple merge candidates:"; - Q_FOREACH (const PimItem &item, result) { + for (const PimItem &item : result) { qCDebug(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("Multiple merge candidates, aborting"); + return failureResponse(QStringLiteral("Multiple merge candidates, aborting")); } } diff --git a/src/server/handler/colcopy.h b/src/server/handler/colcopy.h --- a/src/server/handler/colcopy.h +++ b/src/server/handler/colcopy.h @@ -48,8 +48,9 @@ */ class ColCopy : public Copy { - Q_OBJECT public: + ~ColCopy() override = default; + bool parseStream() override; private: diff --git a/src/server/handler/colcopy.cpp b/src/server/handler/colcopy.cpp --- a/src/server/handler/colcopy.cpp +++ b/src/server/handler/colcopy.cpp @@ -48,25 +48,24 @@ } DataStore *db = connection()->storageBackend(); - if (!db->appendCollection(col)) { - return false; - } - Q_FOREACH (const MimeType &mt, source.mimeTypes()) { - if (!col.addMimeType(mt)) { - return false; - } + 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()); } - Q_FOREACH (const CollectionAttribute &attr, source.attributes()) { - CollectionAttribute newAttr = attr; - newAttr.setId(-1); - newAttr.setCollectionId(col.id()); - if (!newAttr.insert()) { - return false; - } + if (!db->appendCollection(col, mimeTypes, attributes)) { + return false; } + // copy sub-collections const Collection::List lstCols = source.children(); for (const Collection &child : lstCols) { @@ -91,12 +90,12 @@ const Collection source = HandlerHelper::collectionFromScope(cmd.collection(), connection()); if (!source.isValid()) { - return failureResponse("No valid source specified"); + return failureResponse(QStringLiteral("No valid source specified")); } const Collection target = HandlerHelper::collectionFromScope(cmd.destination(), connection()); if (!target.isValid()) { - return failureResponse("No valid target specified"); + return failureResponse(QStringLiteral("No valid target specified")); } CacheCleanerInhibitor inhibitor; @@ -113,11 +112,11 @@ Transaction transaction(store, QStringLiteral("COLCOPY")); if (!copyCollection(source, target)) { - return failureResponse("Failed to copy collection"); + return failureResponse(QStringLiteral("Failed to copy collection")); } if (!transaction.commit()) { - return failureResponse("Cannot commit transaction."); + return failureResponse(QStringLiteral("Cannot commit transaction.")); } return successResponse(); diff --git a/src/server/handler/colmove.h b/src/server/handler/colmove.h --- a/src/server/handler/colmove.h +++ b/src/server/handler/colmove.h @@ -37,8 +37,9 @@ */ class ColMove : public Handler { - Q_OBJECT public: + ~ColMove() override = default; + bool parseStream() override; }; diff --git a/src/server/handler/colmove.cpp b/src/server/handler/colmove.cpp --- a/src/server/handler/colmove.cpp +++ b/src/server/handler/colmove.cpp @@ -37,16 +37,16 @@ Collection source = HandlerHelper::collectionFromScope(cmd.collection(), connection()); if (!source.isValid()) { - return failureResponse("Invalid collection to move"); + 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("Invalid destination collection"); + return failureResponse(QStringLiteral("Invalid destination collection")); } } @@ -68,11 +68,11 @@ Transaction transaction(store, QStringLiteral("COLMOVE")); if (!store->moveCollection(source, target)) { - return failureResponse("Unable to reparent collection"); + return failureResponse(QStringLiteral("Unable to reparent collection")); } if (!transaction.commit()) { - return failureResponse("Cannot commit transaction."); + return failureResponse(QStringLiteral("Cannot commit transaction.")); } return successResponse(); diff --git a/src/server/handler/copy.h b/src/server/handler/copy.h --- a/src/server/handler/copy.h +++ b/src/server/handler/copy.h @@ -41,20 +41,13 @@ - empty remote id - possible located in a different collection (and thus resource) -

Syntax

- - Request: - @verbatim - request = tag " COPY " sequence-set " " collection-id - @endverbatim - There is only the usual status response indicating success or failure of the COPY command */ class Copy : public Handler { - Q_OBJECT public: + ~Copy() override = default; bool parseStream() override; @@ -64,9 +57,7 @@ The changes mentioned above are applied. */ bool copyItem(const PimItem &item, const Collection &target); - -private Q_SLOTS: - void itemsRetrieved(const QList &ids); + void processItems(const QList &ids); private: Collection mTargetCollection; diff --git a/src/server/handler/copy.cpp b/src/server/handler/copy.cpp --- a/src/server/handler/copy.cpp +++ b/src/server/handler/copy.cpp @@ -62,12 +62,12 @@ return true; } -void Copy::itemsRetrieved(const QList &ids) +void Copy::processItems(const QList &ids) { SelectQueryBuilder qb; ItemQueryHelper::itemSetToQuery(ImapSet(ids), qb); if (!qb.exec()) { - failureResponse("Unable to retrieve items"); + failureResponse(QStringLiteral("Unable to retrieve items")); return; } const PimItem::List items = qb.result(); @@ -78,13 +78,13 @@ for (const PimItem &item : items) { if (!copyItem(item, mTargetCollection)) { - failureResponse("Unable to copy item"); + failureResponse(QStringLiteral("Unable to copy item")); return; } } if (!transaction.commit()) { - failureResponse("Cannot commit transaction."); + failureResponse(QStringLiteral("Cannot commit transaction.")); return; } } @@ -94,28 +94,30 @@ const auto &cmd = Protocol::cmdCast(m_command); if (!checkScopeConstraints(cmd.items(), Scope::Uid)) { - return failureResponse("Only UID copy is allowed"); + return failureResponse(QStringLiteral("Only UID copy is allowed")); } if (cmd.items().isEmpty()) { - return failureResponse("No items specified"); + return failureResponse(QStringLiteral("No items specified")); } mTargetCollection = HandlerHelper::collectionFromScope(cmd.destination(), connection()); if (!mTargetCollection.isValid()) { - return failureResponse("No valid target specified"); + return failureResponse(QStringLiteral("No valid target specified")); } if (mTargetCollection.isVirtual()) { - return failureResponse("Copying items into virtual collections is not allowed"); + return failureResponse(QStringLiteral("Copying items into virtual collections is not allowed")); } CacheCleanerInhibitor inhibitor; ItemRetriever retriever(connection()); retriever.setItemSet(cmd.items().uidSet()); retriever.setRetrieveFullPayload(true); - connect(&retriever, &ItemRetriever::itemsRetrieved, - this, &Copy::itemsRetrieved); + QObject::connect(&retriever, &ItemRetriever::itemsRetrieved, + [this](const QList &ids) { + processItems(ids); + }); if (!retriever.exec()) { return failureResponse(retriever.lastError()); } diff --git a/src/server/handler/create.h b/src/server/handler/create.h --- a/src/server/handler/create.h +++ b/src/server/handler/create.h @@ -28,17 +28,12 @@ /** @ingroup akonadi_server_handler - - Handler for the CREATE command. CREATE is backward-compatible with RFC 3051, - except recursive collection creation. - - Response: - A untagged response identical to AkList is sent for every created collection. */ class Create : public Handler { - Q_OBJECT public: + ~Create() override = default; + bool parseStream() override; }; diff --git a/src/server/handler/create.cpp b/src/server/handler/create.cpp --- a/src/server/handler/create.cpp +++ b/src/server/handler/create.cpp @@ -35,7 +35,7 @@ const auto &cmd = Protocol::cmdCast(m_command); if (cmd.name().isEmpty()) { - return failureResponse("Invalid collection name"); + return failureResponse(QStringLiteral("Invalid collection name")); } Collection parent; @@ -47,24 +47,25 @@ if (cmd.parent().scope() != Scope::Invalid && !cmd.parent().isEmpty()) { parent = HandlerHelper::collectionFromScope(cmd.parent(), connection()); if (!parent.isValid()) { - return failureResponse("Invalid parent collection"); + 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)) { - if (mt.name() == QLatin1String("inode/directory")) { + const QString mtName{mt.name()}; + if (mtName == QLatin1String("inode/directory")) { found = true; - } else if (mt.name() == QLatin1String("application/x-vnd.akonadi.collection.virtual")) { + } else if (mtName == QLatin1String("application/x-vnd.akonadi.collection.virtual")) { foundVirtual = true; } if (found && foundVirtual) { break; } } if (!found && !foundVirtual) { - return failureResponse("Parent collection can not contain sub-collections"); + return failureResponse(QStringLiteral("Parent collection can not contain sub-collections")); } // If only virtual collections are supported, force every new collection to @@ -79,7 +80,7 @@ const QString sessionId = QString::fromUtf8(connection()->sessionId()); Resource res = Resource::retrieveByName(sessionId); if (!res.isValid()) { - return failureResponse("Cannot create top-level collection"); + return failureResponse(QStringLiteral("Cannot create top-level collection")); } resourceId = res.id(); } @@ -107,33 +108,21 @@ DataStore *db = connection()->storageBackend(); Transaction transaction(db, QStringLiteral("CREATE")); - if (!db->appendCollection(collection)) { - return failureResponse(QStringLiteral("Could not create collection ") % cmd.name() - % QStringLiteral(", resourceId: ") % QString::number(resourceId)); - } - QStringList effectiveMimeTypes = cmd.mimeTypes(); if (effectiveMimeTypes.isEmpty()) { effectiveMimeTypes.reserve(parentContentTypes.count()); for (const MimeType &mt : qAsConst(parentContentTypes)) { effectiveMimeTypes << mt.name(); } } - if (!db->appendMimeTypeForCollection(collection.id(), effectiveMimeTypes)) { - return failureResponse(QStringLiteral("Unable to append mimetype for collection ") % cmd.name() - % QStringLiteral(" resourceId: ") % QString::number(resourceId)); - } - // store user defined attributes - const QMap attrs = cmd.attributes(); - for (auto iter = attrs.constBegin(), end = attrs.constEnd(); iter != end; ++iter) { - if (!db->addCollectionAttribute(collection, iter.key(), iter.value())) { - return failureResponse("Unable to add collection attribute."); - } + 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("Unable to commit transaction."); + return failureResponse(QStringLiteral("Unable to commit transaction.")); } db->activeCachePolicy(collection); diff --git a/src/server/handler/delete.h b/src/server/handler/delete.h --- a/src/server/handler/delete.h +++ b/src/server/handler/delete.h @@ -39,8 +39,9 @@ */ class Delete : public Handler { - Q_OBJECT public: + ~Delete() override = default; + bool parseStream() override; private: diff --git a/src/server/handler/delete.cpp b/src/server/handler/delete.cpp --- a/src/server/handler/delete.cpp +++ b/src/server/handler/delete.cpp @@ -51,25 +51,25 @@ Collection collection = HandlerHelper::collectionFromScope(cmd.collection(), connection()); if (!collection.isValid()) { - return failureResponse("No such collection."); + 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("Cannot delete virtual root collection"); + return failureResponse(QStringLiteral("Cannot delete virtual root collection")); } } Transaction transaction(DataStore::self(), QStringLiteral("DELETE")); if (!deleteRecursive(collection)) { - return failureResponse("Unable to delete collection"); + return failureResponse(QStringLiteral("Unable to delete collection")); } if (!transaction.commit()) { - return failureResponse("Unable to commit transaction"); + return failureResponse(QStringLiteral("Unable to commit transaction")); } return successResponse(); diff --git a/src/server/handler/fetch.h b/src/server/handler/fetch.h --- a/src/server/handler/fetch.h +++ b/src/server/handler/fetch.h @@ -34,8 +34,9 @@ */ class Fetch : public Handler { - Q_OBJECT public: + ~Fetch() override = default; + bool parseStream() override; }; diff --git a/src/server/handler/fetch.cpp b/src/server/handler/fetch.cpp --- a/src/server/handler/fetch.cpp +++ b/src/server/handler/fetch.cpp @@ -32,19 +32,19 @@ const auto &cmd = Protocol::cmdCast(m_command); if (!connection()->context()->setScopeContext(cmd.scopeContext())) { - return failureResponse("Invalid scope context"); + 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("No FETCH context specified"); + return failureResponse(QStringLiteral("No FETCH context specified")); } CacheCleanerInhibitor inhibitor; FetchHelper fetchHelper(connection(), cmd.scope(), cmd.fetchScope()); if (!fetchHelper.fetchItems()) { - return failureResponse("Failed to fetch items"); + return failureResponse(QStringLiteral("Failed to fetch items")); } return successResponse(); diff --git a/src/server/handler/fetchhelper.h b/src/server/handler/fetchhelper.h --- a/src/server/handler/fetchhelper.h +++ b/src/server/handler/fetchhelper.h @@ -24,29 +24,32 @@ #include "storage/countquerybuilder.h" #include "storage/datastore.h" #include "storage/itemretriever.h" +#include "commandcontext.h" #include #include #include +#include + class FetchHelperTest; namespace Akonadi { namespace Server { +class AggregatedItemFetchScope; class Connection; -class FetchHelper : public QObject +class FetchHelper { - Q_OBJECT - public: - FetchHelper(Connection *connection, const Scope &scope, const Protocol::FetchScope &fetchScope); + FetchHelper(Connection *connection, const Scope &scope, const Protocol::ItemFetchScope &fetchScope); + FetchHelper(Connection *connection, CommandContext *context, const Scope &scope, const Protocol::ItemFetchScope &fetchScope); - bool fetchItems(); + bool fetchItems(std::function &&callback = {}); private: enum ItemQueryColumns { @@ -74,14 +77,16 @@ 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::FetchScope mFetchScope; + Protocol::ItemFetchScope mFetchScope; int mItemQueryColumnMap[ItemQueryColumnCount]; friend class ::FetchHelperTest; diff --git a/src/server/handler/fetchhelper.cpp b/src/server/handler/fetchhelper.cpp --- a/src/server/handler/fetchhelper.cpp +++ b/src/server/handler/fetchhelper.cpp @@ -1,3 +1,4 @@ + /*************************************************************************** * Copyright (C) 2006-2009 by Tobias Koenig * * * @@ -68,9 +69,16 @@ #define PROF_INC(name) #endif -FetchHelper::FetchHelper(Connection *connection, const Scope &scope, - const Protocol::FetchScope &fetchScope) +FetchHelper::FetchHelper(Connection *connection, const Scope &scope, const Protocol::ItemFetchScope &fetchScope) + : FetchHelper(connection, connection->context(), scope, fetchScope) +{ +} + + +FetchHelper::FetchHelper(Connection *connection, CommandContext *context, + const Scope &scope, const Protocol::ItemFetchScope &fetchScope) : mConnection(connection) + , mContext(context) , mScope(scope) , mFetchScope(fetchScope) { @@ -122,7 +130,7 @@ partQuery.addCondition(cond); } - ItemQueryHelper::scopeToQuery(mScope, mConnection->context(), partQuery); + ItemQueryHelper::scopeToQuery(mScope, mContext, partQuery); if (!partQuery.exec()) { throw HandlerException("Unable to list item parts"); @@ -162,7 +170,7 @@ itemQuery.addSortColumn(PimItem::idFullColumnName(), Query::Descending); - ItemQueryHelper::scopeToQuery(mScope, mConnection->context(), itemQuery); + ItemQueryHelper::scopeToQuery(mScope, mContext, itemQuery); if (mFetchScope.changedSince().isValid()) { itemQuery.addValueCondition(PimItem::datetimeFullColumnName(), Query::GreaterOrEqual, mFetchScope.changedSince().toUTC()); @@ -190,7 +198,7 @@ flagQuery.addColumn(PimItem::idFullColumnName()); flagQuery.addColumn(PimItemFlagRelation::rightFullColumnName()); - ItemQueryHelper::scopeToQuery(mScope, mConnection->context(), flagQuery); + ItemQueryHelper::scopeToQuery(mScope, mContext, flagQuery); flagQuery.addSortColumn(PimItem::idFullColumnName(), Query::Descending); if (!flagQuery.exec()) { @@ -217,7 +225,7 @@ tagQuery.addColumn(PimItem::idFullColumnName()); tagQuery.addColumn(Tag::idFullColumnName()); - ItemQueryHelper::scopeToQuery(mScope, mConnection->context(), tagQuery); + ItemQueryHelper::scopeToQuery(mScope, mContext, tagQuery); tagQuery.addSortColumn(PimItem::idFullColumnName(), Query::Descending); if (!tagQuery.exec()) { @@ -242,7 +250,7 @@ PimItem::idFullColumnName()); vRefQuery.addColumn(CollectionPimItemRelation::leftFullColumnName()); vRefQuery.addColumn(CollectionPimItemRelation::rightFullColumnName()); - ItemQueryHelper::scopeToQuery(mScope, mConnection->context(), vRefQuery); + ItemQueryHelper::scopeToQuery(mScope, mContext, vRefQuery); vRefQuery.addSortColumn(PimItem::idFullColumnName(), Query::Descending); if (!vRefQuery.exec()) { @@ -269,10 +277,10 @@ PimItem::collectionIdFullColumnName(), Collection::idFullColumnName()); qb.addJoin(QueryBuilder::LeftJoin, Resource::tableName(), Collection::resourceIdFullColumnName(), Resource::idFullColumnName()); - ItemQueryHelper::scopeToQuery(scope, mConnection->context(), qb); - if (mConnection->context()->resource().isValid()) { + ItemQueryHelper::scopeToQuery(scope, mContext, qb); + if (mContext->resource().isValid()) { qb.addValueCondition(Resource::nameFullColumnName(), Query::NotEquals, - mConnection->context()->resource().name()); + mContext->resource().name()); } if (!qb.exec()) { @@ -300,7 +308,19 @@ return properties.value(QStringLiteral("HasLocalStorage"), false).toBool(); } -bool FetchHelper::fetchItems() +DataStore *FetchHelper::storageBackend() const +{ + if (mConnection) { + if (auto store = mConnection->storageBackend()) { + return store; + } + } + + return DataStore::self(); +} + + +bool FetchHelper::fetchItems(std::function &&itemCallback) { BEGIN_TIMER(fetch) @@ -332,14 +352,14 @@ retriever.setRetrieveFullPayload(mFetchScope.fullPayload()); retriever.setChangedSince(mFetchScope.changedSince()); if (!retriever.exec() && !mFetchScope.ignoreErrors()) { // There we go, retrieve the missing parts from the resource. - if (mConnection->context()->resource().isValid()) { + if (mContext->resource().isValid()) { throw HandlerException(QStringLiteral("Unable to fetch item from backend (collection %1, resource %2) : %3") - .arg(mConnection->context()->collectionId()) - .arg(mConnection->context()->resource().id()) + .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(mConnection->context()->collectionId()) + .arg(mContext->collectionId()) .arg(QString::fromLatin1(retriever.lastError()))); } } @@ -369,30 +389,30 @@ } // build part query if needed BEGIN_TIMER(parts) - QSqlQuery partQuery; + QSqlQuery partQuery(DataStore::self()->database()); if (!mFetchScope.requestedParts().isEmpty() || mFetchScope.fullPayload() || mFetchScope.allAttributes()) { partQuery = buildPartQuery(mFetchScope.requestedParts(), mFetchScope.fullPayload(), mFetchScope.allAttributes()); } END_TIMER(parts) // build flag query if needed BEGIN_TIMER(flags) - QSqlQuery flagQuery; + QSqlQuery flagQuery(DataStore::self()->database()); if (mFetchScope.fetchFlags()) { flagQuery = buildFlagQuery(); } END_TIMER(flags) // build tag query if needed BEGIN_TIMER(tags) - QSqlQuery tagQuery; + QSqlQuery tagQuery(DataStore::self()->database()); if (mFetchScope.fetchTags()) { tagQuery = buildTagQuery(); } END_TIMER(tags) BEGIN_TIMER(vRefs) - QSqlQuery vRefQuery; + QSqlQuery vRefQuery(DataStore::self()->database()); if (mFetchScope.fetchVirtualReferences()) { vRefQuery = buildVRefQuery(); } @@ -416,31 +436,31 @@ const qint64 pimItemId = extractQueryResult(itemQuery, ItemQueryPimItemIdColumn).toLongLong(); const int pimItemRev = extractQueryResult(itemQuery, ItemQueryRevColumn).toInt(); - auto response = Protocol::FetchItemsResponsePtr::create(); - response->setId(pimItemId); - response->setRevision(pimItemRev); + 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()); + response.setMimeType(mtIter.value()); if (mFetchScope.fetchRemoteId()) { - response->setRemoteId(extractQueryResult(itemQuery, ItemQueryPimItemRidColumn).toString()); + response.setRemoteId(extractQueryResult(itemQuery, ItemQueryPimItemRidColumn).toString()); } - response->setParentId(extractQueryResult(itemQuery, ItemQueryCollectionIdColumn).toLongLong()); + response.setParentId(extractQueryResult(itemQuery, ItemQueryCollectionIdColumn).toLongLong()); if (mFetchScope.fetchSize()) { - response->setSize(extractQueryResult(itemQuery, ItemQuerySizeColumn).toLongLong()); + response.setSize(extractQueryResult(itemQuery, ItemQuerySizeColumn).toLongLong()); } if (mFetchScope.fetchMTime()) { - response->setMTime(Utils::variantToDateTime(extractQueryResult(itemQuery, ItemQueryDatetimeColumn))); + response.setMTime(Utils::variantToDateTime(extractQueryResult(itemQuery, ItemQueryDatetimeColumn))); } if (mFetchScope.fetchRemoteRevision()) { - response->setRemoteRevision(extractQueryResult(itemQuery, ItemQueryRemoteRevisionColumn).toString()); + response.setRemoteRevision(extractQueryResult(itemQuery, ItemQueryRemoteRevisionColumn).toString()); } if (mFetchScope.fetchGID()) { - response->setGid(extractQueryResult(itemQuery, ItemQueryPimItemGidColumn).toString()); + response.setGid(extractQueryResult(itemQuery, ItemQueryPimItemGidColumn).toString()); } if (mFetchScope.fetchFlags()) { @@ -461,7 +481,7 @@ flags << flagNameIter.value(); flagQuery.next(); } - response->setFlags(flags); + response.setFlags(flags); } if (mFetchScope.fetchTags()) { @@ -491,10 +511,10 @@ } } else { for (qint64 tagId : qAsConst(tagIds)) { - tags << *HandlerHelper::fetchTagsResponse(Tag::retrieveById(tagId)); + tags.push_back(HandlerHelper::fetchTagsResponse(Tag::retrieveById(tagId))); } } - response->setTags(tags); + response.setTags(tags); } if (mFetchScope.fetchVirtualReferences()) { @@ -511,7 +531,7 @@ vRefs << vRefQuery.value(VRefQueryCollectionIdColumn).toLongLong(); vRefQuery.next(); } - response->setVirtualReferences(vRefs); + response.setVirtualReferences(vRefs); } if (mFetchScope.fetchRelations()) { @@ -529,13 +549,13 @@ const auto result = qb.result(); relations.reserve(result.size()); for (const Relation &rel : result) { - relations << *HandlerHelper::fetchRelationsResponse(rel); + relations.push_back(HandlerHelper::fetchRelationsResponse(rel));; } - response->setRelations(relations); + response.setRelations(relations); } - if (mFetchScope.ancestorDepth() != Protocol::FetchScope::NoAncestor) { - response->setAncestors(ancestorsForItem(response->parentId())); + if (mFetchScope.ancestorDepth() != Protocol::ItemFetchScope::NoAncestor) { + response.setAncestors(ancestorsForItem(response.parentId())); } bool skipItem = false; @@ -574,7 +594,7 @@ if (mFetchScope.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" << partName; + qCDebug(AKONADISERVER_LOG) << "item" << id << "has an empty payload part in parttable for part" << metaPart.name(); skipItem = true; break; } @@ -594,18 +614,22 @@ partQuery.next(); } } - response->setParts(parts); + response.setParts(parts); if (skipItem) { itemQuery.next(); continue; } if (mFetchScope.checkCachedPayloadPartsOnly()) { - response->setCachedParts(cachedParts); + response.setCachedParts(cachedParts); } - mConnection->sendResponse(response); + if (itemCallback) { + itemCallback(std::move(response)); + } else { + mConnection->sendResponse(std::move(response)); + } itemQuery.next(); } @@ -651,10 +675,10 @@ void FetchHelper::updateItemAccessTime() { - Transaction transaction(mConnection->storageBackend(), QStringLiteral("update atime")); + Transaction transaction(storageBackend(), QStringLiteral("update atime")); QueryBuilder qb(PimItem::tableName(), QueryBuilder::Update); qb.setColumnValue(PimItem::atimeColumn(), QDateTime::currentDateTimeUtc()); - ItemQueryHelper::scopeToQuery(mScope, mConnection->context(), qb); + ItemQueryHelper::scopeToQuery(mScope, mContext, qb); if (!qb.exec()) { qCWarning(AKONADISERVER_LOG) << "Unable to update item access time"; @@ -665,19 +689,18 @@ void FetchHelper::triggerOnDemandFetch() { - if (mConnection->context()->collectionId() <= 0 || mFetchScope.cacheOnly()) { + if (mContext->collectionId() <= 0 || mFetchScope.cacheOnly()) { return; } - Collection collection = mConnection->context()->collection(); + 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; } - DataStore *store = mConnection->storageBackend(); - store->activeCachePolicy(collection); + storageBackend()->activeCachePolicy(collection); if (!collection.cachePolicySyncOnDemand()) { return; } @@ -689,16 +712,17 @@ QVector FetchHelper::ancestorsForItem(Collection::Id parentColId) { - if (mFetchScope.ancestorDepth() == Protocol::FetchScope::NoAncestor || parentColId == 0) { + if (mFetchScope.ancestorDepth() == Protocol::ItemFetchScope::NoAncestor || parentColId == 0) { return QVector(); } - if (mAncestorCache.contains(parentColId)) { - return mAncestorCache.value(parentColId); + const auto it = mAncestorCache.constFind(parentColId); + if (it != mAncestorCache.cend()) { + return *it; } QVector ancestors; Collection col = Collection::retrieveById(parentColId); - const int depthNum = mFetchScope.ancestorDepth() == Protocol::FetchScope::ParentAncestor ? 1 : INT_MAX; + const int depthNum = mFetchScope.ancestorDepth() == Protocol::ItemFetchScope::ParentAncestor ? 1 : INT_MAX; for (int i = 0; i < depthNum; ++i) { if (!col.isValid()) { Protocol::Ancestor ancestor; diff --git a/src/server/handler/link.h b/src/server/handler/link.h --- a/src/server/handler/link.h +++ b/src/server/handler/link.h @@ -37,8 +37,9 @@ */ class Link : public Handler { - Q_OBJECT public: + ~Link() override = default; + bool parseStream() override; }; diff --git a/src/server/handler/link.cpp b/src/server/handler/link.cpp --- a/src/server/handler/link.cpp +++ b/src/server/handler/link.cpp @@ -26,6 +26,7 @@ #include "storage/transaction.h" #include "storage/selectquerybuilder.h" #include "storage/collectionqueryhelper.h" +#include "akonadiserver_debug.h" #include @@ -38,7 +39,7 @@ const Collection collection = HandlerHelper::collectionFromScope(cmd.destination(), connection()); if (!collection.isVirtual()) { - return failureResponse("Can't link items to non-virtual collections"); + return failureResponse(QStringLiteral("Can't link items to non-virtual collections")); } /* FIXME BIN @@ -68,7 +69,7 @@ */ if (!qb.exec()) { - return failureResponse("Unable to execute item query"); + return failureResponse(QStringLiteral("Unable to execute item query")); } const PimItem::List items = qb.result(); @@ -89,19 +90,20 @@ toUnlink << item; } if (!result) { - return failureResponse("Failed to modify item reference"); + 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); } - if (!transaction.commit()) { - return failureResponse("Cannot commit transaction."); - } return successResponse(); } diff --git a/src/server/handler/list.h b/src/server/handler/list.h --- a/src/server/handler/list.h +++ b/src/server/handler/list.h @@ -38,22 +38,6 @@ 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. -

Syntax

- - Request: - @verbatim - request = tag " " command " " collection-id " " depth " (" filter-list ")" " (" option-list ")" - command = "LIST" | "LSUB" | "RID LIST" | "RID LSUB" - depth = number | "INF" - filter-list = *(filter-key " " filter-value) - filter-key = "RESOURCE" | "MIMETYPE" | "ENABLED" | "SYNC" | "DISPLAY" | "INDEX" - option-list = *(option-key " " option-value) - option-key = "STATISTICS" - @endverbatim - - @c LIST will include all known collections, @c LSUB only those that are - subscribed or contains subscribed collections (cf. RFC 3501, LIST vs. LSUB). - 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. @@ -71,24 +55,16 @@ Possible values are @c 0 (the default), @c 1 for the direct parent node and @c INF for all, terminating with the root collection. - Response: - @verbatim - response = "*" collection-id " " parent-id " ("attribute-list")" - attribute-list = *(attribute-identifier " " attribute-value) - attribute-identifier = "NAME" | "MIMETYPE" | "REMOTEID" | "REMOTEREVISION" | "RESOURCE" | "VIRTUAL" | "MESSAGES" | "UNSEEN" | "SIZE" | "ANCESTORS" | "custom-attr-identifier - @endverbatim - 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 { - Q_OBJECT - public: - List(); + List() = default; + ~List() override = default; bool parseStream() override; @@ -109,12 +85,12 @@ Resource mResource; QVector mMimeTypes; - int mAncestorDepth; - bool mIncludeStatistics; - bool mEnabledCollections; - bool mCollectionsToDisplay; - bool mCollectionsToSynchronize; - bool mCollectionsToIndex; + int mAncestorDepth = 0; + bool mIncludeStatistics = false; + bool mEnabledCollections = false; + bool mCollectionsToDisplay = false; + bool mCollectionsToSynchronize = false; + bool mCollectionsToIndex = false; QSet mAncestorAttributes; QMap mCollections; QHash mAncestors; diff --git a/src/server/handler/list.cpp b/src/server/handler/list.cpp --- a/src/server/handler/list.cpp +++ b/src/server/handler/list.cpp @@ -44,17 +44,6 @@ return false; } -List::List() - : Handler() - , mAncestorDepth(0) - , mIncludeStatistics(false) - , mEnabledCollections(false) - , mCollectionsToDisplay(false) - , mCollectionsToSynchronize(false) - , mCollectionsToIndex(false) -{ -} - QStack List::ancestorsForCollection(const Collection &col) { if (mAncestorDepth <= 0) { @@ -363,10 +352,11 @@ QVariantList mimeTypeIds; QVariantList attributeIds; QVariantList ancestorIds; - mimeTypeIds.reserve(mCollections.size()); - attributeIds.reserve(mCollections.size()); + 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(mCollections.size()); + ancestorIds.reserve(collectionSize); for (auto it = mCollections.cbegin(), end = mCollections.cend(); it != end; ++it) { mimeTypeIds << it.key(); attributeIds << it.key(); @@ -444,8 +434,8 @@ const int querySizeLimit = 999; int mimetypeQueryStart = 0; int attributeQueryStart = 0; - QSqlQuery mimeTypeQuery; - QSqlQuery attributeQuery; + QSqlQuery mimeTypeQuery(DataStore::self()->database()); + QSqlQuery attributeQuery(DataStore::self()->database()); auto it = mCollections.begin(); while (it != mCollections.end()) { const Collection col = it.value(); diff --git a/src/server/handler/login.h b/src/server/handler/login.h --- a/src/server/handler/login.h +++ b/src/server/handler/login.h @@ -33,8 +33,9 @@ */ class Login : public Handler { - Q_OBJECT public: + ~Login() override = default; + bool parseStream() override; }; diff --git a/src/server/handler/login.cpp b/src/server/handler/login.cpp --- a/src/server/handler/login.cpp +++ b/src/server/handler/login.cpp @@ -29,11 +29,11 @@ const auto &cmd = Protocol::cmdCast(m_command); if (cmd.sessionId().isEmpty()) { - return failureResponse("Missing session identifier"); + return failureResponse(QStringLiteral("Missing session identifier")); } connection()->setSessionId(cmd.sessionId()); - Q_EMIT connectionStateChange(Server::Authenticated); + connection()->setState(Server::Authenticated); return successResponse(); } diff --git a/src/server/handler/logout.h b/src/server/handler/logout.h --- a/src/server/handler/logout.h +++ b/src/server/handler/logout.h @@ -33,8 +33,9 @@ */ class Logout : public Handler { - Q_OBJECT public: + ~Logout() override = default; + bool parseStream() override; }; diff --git a/src/server/handler/logout.cpp b/src/server/handler/logout.cpp --- a/src/server/handler/logout.cpp +++ b/src/server/handler/logout.cpp @@ -14,7 +14,7 @@ * 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. * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "logout.h" @@ -26,6 +26,6 @@ { sendResponse(); - Q_EMIT connectionStateChange(LoggingOut); + connection()->setState(LoggingOut); return true; } diff --git a/src/server/handler/modify.h b/src/server/handler/modify.h --- a/src/server/handler/modify.h +++ b/src/server/handler/modify.h @@ -37,8 +37,9 @@ */ class Modify : public Handler { - Q_OBJECT public: + ~Modify() override = default; + bool parseStream() override; }; diff --git a/src/server/handler/move.h b/src/server/handler/move.h --- a/src/server/handler/move.h +++ b/src/server/handler/move.h @@ -40,17 +40,16 @@ Destination is a collection id. */ -class Move : public Handler +class Move : public Handler { - Q_OBJECT - public: + ~Move() override = default; + bool parseStream() override; -private Q_SLOTS: +private: void itemsRetrieved(const QList &ids); -private: Collection mDestination; }; diff --git a/src/server/handler/move.cpp b/src/server/handler/move.cpp --- a/src/server/handler/move.cpp +++ b/src/server/handler/move.cpp @@ -28,6 +28,7 @@ #include "storage/selectquerybuilder.h" #include "storage/transaction.h" #include "storage/collectionqueryhelper.h" +#include "akonadiserver_debug.h" using namespace Akonadi; using namespace Akonadi::Server; @@ -41,7 +42,6 @@ ItemQueryHelper::itemSetToQuery(ImapSet(ids), qb); qb.addValueCondition(PimItem::collectionIdFullColumnName(), Query::NotEquals, mDestination.id()); - const QDateTime mtime = QDateTime::currentDateTimeUtc(); if (!qb.exec()) { failureResponse("Unable to execute query"); @@ -53,6 +53,7 @@ return; } + const QDateTime mtime = QDateTime::currentDateTimeUtc(); // Split the list by source collection QMap toMove; QMap sources; @@ -96,17 +97,6 @@ return; } - // 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). - 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; - } - // Emit notification for each source collection separately Collection source; PimItem::List itemsToMove; @@ -125,6 +115,20 @@ 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() @@ -152,8 +156,10 @@ ItemRetriever retriever(connection()); retriever.setScope(cmd.items()); retriever.setRetrieveFullPayload(true); - connect(&retriever, &ItemRetriever::itemsRetrieved, - this, &Move::itemsRetrieved); + QObject::connect(&retriever, &ItemRetriever::itemsRetrieved, + [this](const QList &ids) { + itemsRetrieved(ids); + }); if (!retriever.exec()) { return failureResponse(retriever.lastError()); } diff --git a/src/server/handler/relationfetch.h b/src/server/handler/relationfetch.h --- a/src/server/handler/relationfetch.h +++ b/src/server/handler/relationfetch.h @@ -34,8 +34,9 @@ */ class RelationFetch : public Handler { - Q_OBJECT public: + ~RelationFetch() override = default; + bool parseStream() override; }; diff --git a/src/server/handler/relationremove.h b/src/server/handler/relationremove.h --- a/src/server/handler/relationremove.h +++ b/src/server/handler/relationremove.h @@ -29,8 +29,9 @@ class RelationRemove : public Handler { - Q_OBJECT public: + ~RelationRemove() override = default; + bool parseStream() override; }; diff --git a/src/server/handler/relationstore.h b/src/server/handler/relationstore.h --- a/src/server/handler/relationstore.h +++ b/src/server/handler/relationstore.h @@ -31,9 +31,9 @@ class RelationStore : public Handler { - Q_OBJECT - public: + ~RelationStore() override = default; + bool parseStream() override; private: diff --git a/src/server/handler/remove.h b/src/server/handler/remove.h --- a/src/server/handler/remove.h +++ b/src/server/handler/remove.h @@ -32,24 +32,17 @@ Handler for the item deletion command. -

Syntax

- One of the following three: - @verbatim - REMOVE - UID REMOVE - RID REMOVE - @endverbatim -

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 Remove : public Handler { - Q_OBJECT public: + ~Remove() override = default; + bool parseStream() override; }; diff --git a/src/server/handler/resourceselect.h b/src/server/handler/resourceselect.h --- a/src/server/handler/resourceselect.h +++ b/src/server/handler/resourceselect.h @@ -37,10 +37,11 @@ to be unique per resource, so this command should be issued before running any RID based collection commands. */ -class ResourceSelect : public Handler +class ResourceSelect : public Handler { - Q_OBJECT public: + ~ResourceSelect() override = default; + bool parseStream() override; }; diff --git a/src/server/handler/search.h b/src/server/handler/search.h --- a/src/server/handler/search.h +++ b/src/server/handler/search.h @@ -36,16 +36,15 @@ */ class Search : public Handler { - Q_OBJECT - public: - bool parseStream() override; + ~Search() override = default; -private Q_SLOTS: - void slotResultsAvailable(const QSet &results); + bool parseStream() override; private: - Protocol::FetchScope mFetchScope; + void processResults(const QSet &results); + + Protocol::ItemFetchScope mFetchScope; QSet mAllResults; }; diff --git a/src/server/handler/search.cpp b/src/server/handler/search.cpp --- a/src/server/handler/search.cpp +++ b/src/server/handler/search.cpp @@ -25,6 +25,7 @@ #include "searchhelper.h" #include "search/searchrequest.h" #include "search/searchmanager.h" +#include "akonadiserver_search_debug.h" using namespace Akonadi; using namespace Akonadi::Server; @@ -50,12 +51,12 @@ collections += SearchHelper::matchSubcollectionsByMimeType(collectionIds, cmd.mimeTypes()); } - qCDebug(AKONADISERVER_LOG) << "SEARCH:"; - qCDebug(AKONADISERVER_LOG) << "\tQuery:" << cmd.query(); - qCDebug(AKONADISERVER_LOG) << "\tMimeTypes:" << cmd.mimeTypes(); - qCDebug(AKONADISERVER_LOG) << "\tCollections:" << collections; - qCDebug(AKONADISERVER_LOG) << "\tRemote:" << cmd.remote(); - qCDebug(AKONADISERVER_LOG) << "\tRecursive" << recursive; + 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(); @@ -68,17 +69,19 @@ request.setMimeTypes(cmd.mimeTypes()); request.setQuery(cmd.query()); request.setRemoteSearch(cmd.remote()); - connect(&request, &SearchRequest::resultsAvailable, - this, &Search::slotResultsAvailable); + QObject::connect(&request, &SearchRequest::resultsAvailable, + [this](const QSet &results) { + processResults(results); + }); request.exec(); - //qCDebug(AKONADISERVER_LOG) << "\tResult:" << uids; - qCDebug(AKONADISERVER_LOG) << "\tResult:" << mAllResults.count() << "matches"; + //qCDebug(AKONADISERVER_SEARCH_LOG) << "\tResult:" << uids; + qCDebug(AKONADISERVER_SEARCH_LOG) << "\tResult:" << mAllResults.count() << "matches"; return successResponse(); } -void Search::slotResultsAvailable(const QSet &results) +void Search::processResults(const QSet &results) { QSet newResults = results; newResults.subtract(mAllResults); diff --git a/src/server/handler/searchpersistent.h b/src/server/handler/searchpersistent.h --- a/src/server/handler/searchpersistent.h +++ b/src/server/handler/searchpersistent.h @@ -34,8 +34,9 @@ */ class SearchPersistent : public Handler { - Q_OBJECT public: + ~SearchPersistent() override = default; + bool parseStream() override; }; diff --git a/src/server/handler/searchpersistent.cpp b/src/server/handler/searchpersistent.cpp --- a/src/server/handler/searchpersistent.cpp +++ b/src/server/handler/searchpersistent.cpp @@ -71,21 +71,10 @@ col.setResourceId(1); // search resource col.setName(cmd.name()); col.setIsVirtual(true); - if (!db->appendCollection(col)) { - return failureResponse("Unable to create persistent search"); - } - - if (!db->addCollectionAttribute(col, "AccessRights", "luD")) { - return failureResponse("Unable to set rights attribute on persistent search"); - } const QStringList lstMimeTypes = cmd.mimeTypes(); - for (const QString &mimeType : lstMimeTypes) { - const MimeType mt = MimeType::retrieveByNameOrCreate(mimeType); - if (!mt.isValid()) { - return failureResponse("Failed to create new mimetype"); - } - col.addMimeType(mt); + if (!db->appendCollection(col, lstMimeTypes, {{"AccessRights", "luD"}})) { + return failureResponse("Unable to create persistent search"); } if (!transaction.commit()) { diff --git a/src/server/handler/searchresult.h b/src/server/handler/searchresult.h --- a/src/server/handler/searchresult.h +++ b/src/server/handler/searchresult.h @@ -34,8 +34,9 @@ */ class SearchResult : public Handler { - Q_OBJECT public: + ~SearchResult() override = default; + bool parseStream() override; private: diff --git a/src/server/handler/status.h b/src/server/handler/status.h --- a/src/server/handler/status.h +++ b/src/server/handler/status.h @@ -33,8 +33,9 @@ */ class Status : public Handler { - Q_OBJECT public: + ~Status() override = default; + bool parseStream() override; }; diff --git a/src/server/handler/status.cpp b/src/server/handler/status.cpp --- a/src/server/handler/status.cpp +++ b/src/server/handler/status.cpp @@ -36,17 +36,17 @@ const Collection col = HandlerHelper::collectionFromScope(cmd.collection(), connection()); if (!col.isValid()) { - return failureResponse("No status for this folder"); + return failureResponse(QStringLiteral("No status for this folder")); } const CollectionStatistics::Statistics stats = CollectionStatistics::self()->statistics(col); if (stats.count == -1) { - return failureResponse("Failed to query statistics."); + return failureResponse(QStringLiteral("Failed to query statistics.")); } - auto resp = Protocol::FetchCollectionStatsResponsePtr::create(); - resp->setCount(stats.count); - resp->setUnseen(stats.count - stats.read); - resp->setSize(stats.size); - return successResponse(resp); + 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/store.h b/src/server/handler/store.h --- a/src/server/handler/store.h +++ b/src/server/handler/store.h @@ -34,33 +34,6 @@ Handler for the item modification command. -

Syntax

- One of the following three: - @verbatim - STORE - UID MOVE [] - RID MOVE [] - @endverbatim - - @c revision-check is one of the following and allowed iff one item was selected for modification: - @verbatim - NOREV - REV - @endverbatim - - @c modifcations is a parenthesized list containing any of the following: - @verbatim - SIZE - [+-]FLAGS - REMOTEID - REMOTEREVISION - GID - DIRTY false - INVALIDATECACHE - - - @endverbatim -

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 @@ -95,9 +68,9 @@ class Store : public Handler { - Q_OBJECT - public: + ~Store() override = default; + bool parseStream() override; private: diff --git a/src/server/handler/store.cpp b/src/server/handler/store.cpp --- a/src/server/handler/store.cpp +++ b/src/server/handler/store.cpp @@ -284,9 +284,7 @@ } if (item.isValid() && cmd.modifiedParts() & Protocol::ModifyItemsCommand::Parts) { - PartStreamer streamer(connection(), item, this); - connect(&streamer, &PartStreamer::responseAvailable, - this, static_cast(&Handler::sendResponse)); + PartStreamer streamer(connection(), item); Q_FOREACH (const QByteArray &partName, cmd.parts()) { qint64 partSize = 0; if (!streamer.stream(true, partName, partSize)) { @@ -299,9 +297,7 @@ } if (item.isValid() && cmd.modifiedParts() & Protocol::ModifyItemsCommand::Attributes) { - PartStreamer streamer(connection(), item, this); - connect(&streamer, &PartStreamer::responseAvailable, - this, static_cast(&Handler::sendResponse)); + PartStreamer streamer(connection(), item); const Protocol::Attributes attrs = cmd.attributes(); for (auto iter = attrs.cbegin(), end = attrs.cend(); iter != end; ++iter) { bool changed = false; @@ -365,10 +361,10 @@ } if (!cmd.noResponse()) { - auto resp = Protocol::ModifyItemsResponsePtr::create(); - resp->setId(item.id()); - resp->setNewRevision(item.rev()); - sendResponse(resp); + Protocol::ModifyItemsResponse resp; + resp.setId(item.id()); + resp.setNewRevision(item.rev()); + sendResponse(std::move(resp)); } } @@ -383,7 +379,7 @@ datetime = pimItems.first().datetime(); } - auto resp = Protocol::ModifyItemsResponsePtr::create(); - resp->setModificationDateTime(datetime); - return successResponse(resp); + Protocol::ModifyItemsResponse resp; + resp.setModificationDateTime(datetime); + return successResponse(std::move(resp)); } diff --git a/src/server/handler/tagappend.h b/src/server/handler/tagappend.h --- a/src/server/handler/tagappend.h +++ b/src/server/handler/tagappend.h @@ -29,9 +29,9 @@ class TagAppend : public Handler { - Q_OBJECT - public: + ~TagAppend() override = default; + bool parseStream() override; }; diff --git a/src/server/handler/tagappend.cpp b/src/server/handler/tagappend.cpp --- a/src/server/handler/tagappend.cpp +++ b/src/server/handler/tagappend.cpp @@ -37,7 +37,7 @@ const auto &cmd = Protocol::cmdCast(m_command); if (!cmd.remoteId().isEmpty() && !connection()->context()->resource().isValid()) { - return failureResponse("Only resources can create tags with remote ID"); + return failureResponse(QStringLiteral("Only resources can create tags with remote ID")); } Transaction trx(DataStore::self(), QStringLiteral("TAGAPPEND")); diff --git a/src/server/handler/tagfetch.h b/src/server/handler/tagfetch.h --- a/src/server/handler/tagfetch.h +++ b/src/server/handler/tagfetch.h @@ -34,8 +34,9 @@ */ class TagFetch : public Handler { - Q_OBJECT public: + ~TagFetch() override = default; + bool parseStream() override; }; diff --git a/src/server/handler/tagfetchhelper.h b/src/server/handler/tagfetchhelper.h --- a/src/server/handler/tagfetchhelper.h +++ b/src/server/handler/tagfetchhelper.h @@ -33,12 +33,11 @@ class Connection; -class TagFetchHelper : public QObject +class TagFetchHelper { - Q_OBJECT - public: TagFetchHelper(Connection *connection, const Scope &scope); + ~TagFetchHelper() = default; bool fetchTags(); diff --git a/src/server/handler/tagfetchhelper.cpp b/src/server/handler/tagfetchhelper.cpp --- a/src/server/handler/tagfetchhelper.cpp +++ b/src/server/handler/tagfetchhelper.cpp @@ -28,8 +28,7 @@ using namespace Akonadi::Server; TagFetchHelper::TagFetchHelper(Connection *connection, const Scope &scope) - : QObject() - , mConnection(connection) + : mConnection(connection) , mScope(scope) { } @@ -124,13 +123,13 @@ while (tagQuery.isValid()) { const qint64 tagId = tagQuery.value(0).toLongLong(); - auto response = Protocol::FetchTagsResponsePtr::create(); - response->setId(tagId); - response->setGid(Utils::variantToByteArray(tagQuery.value(1))); - response->setParentId(tagQuery.value(2).toLongLong()); - response->setType(Utils::variantToByteArray(tagQuery.value(3))); + Protocol::FetchTagsResponse response; + response.setId(tagId); + response.setGid(Utils::variantToByteArray(tagQuery.value(1))); + response.setParentId(tagQuery.value(2).toLongLong()); + response.setType(Utils::variantToByteArray(tagQuery.value(3))); if (mConnection->context()->resource().isValid()) { - response->setRemoteId(Utils::variantToByteArray(tagQuery.value(4))); + response.setRemoteId(Utils::variantToByteArray(tagQuery.value(4))); } QMap tagAttributes; @@ -148,9 +147,9 @@ attributeQuery.next(); } - response->setAttributes(tagAttributes); + response.setAttributes(tagAttributes); - mConnection->sendResponse(response); + mConnection->sendResponse(std::move(response)); tagQuery.next(); } diff --git a/src/server/handler/tagremove.h b/src/server/handler/tagremove.h --- a/src/server/handler/tagremove.h +++ b/src/server/handler/tagremove.h @@ -29,8 +29,9 @@ class TagRemove : public Handler { - Q_OBJECT public: + ~TagRemove() override = default; + bool parseStream() override; }; diff --git a/src/server/handler/tagremove.cpp b/src/server/handler/tagremove.cpp --- a/src/server/handler/tagremove.cpp +++ b/src/server/handler/tagremove.cpp @@ -34,19 +34,19 @@ const auto &cmd = Protocol::cmdCast(m_command); if (!checkScopeConstraints(cmd.tag(), Scope::Uid)) { - return failureResponse("Only UID-based TAGREMOVE is supported"); + return failureResponse(QStringLiteral("Only UID-based TAGREMOVE is supported")); } SelectQueryBuilder tagQuery; QueryHelper::setToQuery(cmd.tag().uidSet(), Tag::idFullColumnName(), tagQuery); if (!tagQuery.exec()) { - return failureResponse("Failed to obtain tags"); + return failureResponse(QStringLiteral("Failed to obtain tags")); } const Tag::List tags = tagQuery.result(); if (!DataStore::self()->removeTags(tags)) { - return failureResponse("Failed to remove tags"); + return failureResponse(QStringLiteral("Failed to remove tags")); } return successResponse(); diff --git a/src/server/handler/tagstore.h b/src/server/handler/tagstore.h --- a/src/server/handler/tagstore.h +++ b/src/server/handler/tagstore.h @@ -29,9 +29,9 @@ class TagStore : public Handler { - Q_OBJECT - public: + ~TagStore() override = default; + bool parseStream() override; }; diff --git a/src/server/handler/transaction.h b/src/server/handler/transaction.h --- a/src/server/handler/transaction.h +++ b/src/server/handler/transaction.h @@ -34,10 +34,9 @@ */ class TransactionHandler : public Handler { - Q_OBJECT - Q_ENUMS(Mode) - public: + ~TransactionHandler() override = default; + bool parseStream() override; }; diff --git a/src/server/handlerhelper.h b/src/server/handlerhelper.h --- a/src/server/handlerhelper.h +++ b/src/server/handlerhelper.h @@ -37,7 +37,6 @@ class Ancestor; class CachePolicy; class FetchCollectionsResponse; -using FetchCollectionsResponsePtr = QSharedPointer; class FetchTagsResponse; using FetchTagsResponsePtr = QSharedPointer; class FetchRelationsResponse; @@ -77,14 +76,14 @@ Make sure DataStore::activeCachePolicy() has been called before to include the effective cache policy */ - static Protocol::FetchCollectionsResponsePtr fetchCollectionsResponse(const Collection &col); + static Protocol::FetchCollectionsResponse fetchCollectionsResponse(const Collection &col); /** Returns the protocol representation of the given collection. Make sure DataStore::activeCachePolicy() has been called before to include the effective cache policy */ - static Protocol::FetchCollectionsResponsePtr fetchCollectionsResponse(const Collection &col, + static Protocol::FetchCollectionsResponse fetchCollectionsResponse(const Collection &col, const CollectionAttribute::List &attributeList, bool includeStatistics = false, int ancestorDepth = 0, @@ -100,11 +99,11 @@ const QStack &ancestors, const QStack &_ancestorsAttributes = QStack()); - static Protocol::FetchTagsResponsePtr fetchTagsResponse(const Tag &tag, + static Protocol::FetchTagsResponse fetchTagsResponse(const Tag &tag, bool withRID = false, Connection *connection = nullptr); - static Protocol::FetchRelationsResponsePtr fetchRelationsResponse(const Relation &relation); + static Protocol::FetchRelationsResponse fetchRelationsResponse(const Relation &relation); /** Converts a bytearray list of flag names into flag records. diff --git a/src/server/handlerhelper.cpp b/src/server/handlerhelper.cpp --- a/src/server/handlerhelper.cpp +++ b/src/server/handlerhelper.cpp @@ -87,12 +87,14 @@ cachePolicy.setInherit(col.cachePolicyInherit()); cachePolicy.setCacheTimeout(col.cachePolicyCacheTimeout()); cachePolicy.setCheckInterval(col.cachePolicyCheckInterval()); - cachePolicy.setLocalParts(col.cachePolicyLocalParts().split(QLatin1Char(' '))); + if (!col.cachePolicyLocalParts().isEmpty()) { + cachePolicy.setLocalParts(col.cachePolicyLocalParts().split(QLatin1Char(' '))); + } cachePolicy.setSyncOnDemand(col.cachePolicySyncOnDemand()); return cachePolicy; } -Protocol::FetchCollectionsResponsePtr HandlerHelper::fetchCollectionsResponse(const Collection &col) +Protocol::FetchCollectionsResponse HandlerHelper::fetchCollectionsResponse(const Collection &col) { QStringList mimeTypes; mimeTypes.reserve(col.mimeTypes().size()); @@ -104,67 +106,67 @@ QStack(), false, mimeTypes); } -Protocol::FetchCollectionsResponsePtr HandlerHelper::fetchCollectionsResponse(const Collection &col, +Protocol::FetchCollectionsResponse HandlerHelper::fetchCollectionsResponse(const Collection &col, const CollectionAttribute::List &attrs, bool includeStatistics, int ancestorDepth, const QStack &ancestors, const QStack &ancestorAttributes, bool isReferenced, const QStringList &mimeTypes) { - auto response = Protocol::FetchCollectionsResponsePtr::create(); - response->setId(col.id()); - response->setParentId(col.parentId()); - response->setName(col.name()); - response->setMimeTypes(mimeTypes); - response->setRemoteId(col.remoteId()); - response->setRemoteRevision(col.remoteRevision()); - response->setResource(col.resource().name()); - response->setIsVirtual(col.isVirtual()); + Protocol::FetchCollectionsResponse response; + response.setId(col.id()); + response.setParentId(col.parentId()); + response.setName(col.name()); + response.setMimeTypes(mimeTypes); + response.setRemoteId(col.remoteId()); + response.setRemoteRevision(col.remoteRevision()); + response.setResource(col.resource().name()); + response.setIsVirtual(col.isVirtual()); if (includeStatistics) { const CollectionStatistics::Statistics stats = CollectionStatistics::self()->statistics(col); if (stats.count > -1) { Protocol::FetchCollectionStatsResponse statsResponse; statsResponse.setCount(stats.count); statsResponse.setUnseen(stats.count - stats.read); statsResponse.setSize(stats.size); - response->setStatistics(statsResponse); + response.setStatistics(statsResponse); } } if (!col.queryString().isEmpty()) { - response->setSearchQuery(col.queryString()); + response.setSearchQuery(col.queryString()); QVector searchCols; const QStringList searchColIds = col.queryCollections().split(QLatin1Char(' ')); searchCols.reserve(searchColIds.size()); for (const QString &searchColId : searchColIds) { searchCols << searchColId.toLongLong(); } - response->setSearchCollections(searchCols); + response.setSearchCollections(searchCols); } Protocol::CachePolicy cachePolicy = cachePolicyResponse(col); - response->setCachePolicy(cachePolicy); + response.setCachePolicy(cachePolicy); if (ancestorDepth) { QVector ancestorList = HandlerHelper::ancestorsResponse(ancestorDepth, ancestors, ancestorAttributes); - response->setAncestors(ancestorList); + response.setAncestors(ancestorList); } - response->setReferenced(isReferenced); - response->setEnabled(col.enabled()); - response->setDisplayPref(static_cast(col.displayPref())); - response->setSyncPref(static_cast(col.syncPref())); - response->setIndexPref(static_cast(col.indexPref())); + response.setReferenced(isReferenced); + response.setEnabled(col.enabled()); + response.setDisplayPref(static_cast(col.displayPref())); + response.setSyncPref(static_cast(col.syncPref())); + response.setIndexPref(static_cast(col.indexPref())); QMap ra; for (const CollectionAttribute &attr : attrs) { ra.insert(attr.type(), attr.value()); } - response->setAttributes(ra); + response.setAttributes(ra); return response; } @@ -204,15 +206,15 @@ return rv; } -Protocol::FetchTagsResponsePtr HandlerHelper::fetchTagsResponse(const Tag &tag, +Protocol::FetchTagsResponse HandlerHelper::fetchTagsResponse(const Tag &tag, bool withRID, Connection *connection) { - auto response = Protocol::FetchTagsResponsePtr::create(); - response->setId(tag.id()); - response->setType(tag.tagType().name().toUtf8()); - response->setParentId(tag.parentId()); - response->setGid(tag.gid().toUtf8()); + Protocol::FetchTagsResponse response; + response.setId(tag.id()); + response.setType(tag.tagType().name().toUtf8()); + response.setParentId(tag.parentId()); + response.setGid(tag.gid().toUtf8()); if (withRID && connection) { // Fail silently if retrieving tag RID is not allowed in current context @@ -236,20 +238,20 @@ if (!query.next()) { return response; } - response->setRemoteId(Utils::variantToByteArray(query.value(0))); + response.setRemoteId(Utils::variantToByteArray(query.value(0))); } return response; } -Protocol::FetchRelationsResponsePtr HandlerHelper::fetchRelationsResponse(const Relation &relation) +Protocol::FetchRelationsResponse HandlerHelper::fetchRelationsResponse(const Relation &relation) { - auto resp = Protocol::FetchRelationsResponsePtr::create(); - resp->setLeft(relation.leftId()); - resp->setLeftMimeType(relation.left().mimeType().name().toUtf8()); - resp->setRight(relation.rightId()); - resp->setRightMimeType(relation.right().mimeType().name().toUtf8()); - resp->setType(relation.relationType().name().toUtf8()); + Protocol::FetchRelationsResponse resp; + resp.setLeft(relation.leftId()); + resp.setLeftMimeType(relation.left().mimeType().name().toUtf8()); + resp.setRight(relation.rightId()); + resp.setRightMimeType(relation.right().mimeType().name().toUtf8()); + resp.setType(relation.relationType().name().toUtf8()); return resp; } diff --git a/src/server/intervalcheck.h b/src/server/intervalcheck.h --- a/src/server/intervalcheck.h +++ b/src/server/intervalcheck.h @@ -39,7 +39,7 @@ public: explicit IntervalCheck(QObject *parent = nullptr); - ~IntervalCheck(); + ~IntervalCheck() override; /** * Requests the given collection to be synced. diff --git a/src/server/intervalcheck.cpp b/src/server/intervalcheck.cpp --- a/src/server/intervalcheck.cpp +++ b/src/server/intervalcheck.cpp @@ -40,9 +40,13 @@ void IntervalCheck::requestCollectionSync(const Collection &collection) { +#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) + QMetaObject::invokeMethod(this, [this, collection]() { collectionExpired(collection); }, Qt::QueuedConnection); +#else QMetaObject::invokeMethod(this, "collectionExpired", Qt::QueuedConnection, Q_ARG(Collection, collection)); +#endif } int IntervalCheck::collectionScheduleInterval(const Collection &collection) diff --git a/src/server/notificationmanager.h b/src/server/notificationmanager.h --- a/src/server/notificationmanager.h +++ b/src/server/notificationmanager.h @@ -29,32 +29,38 @@ class NotificationManagerTest; class QThreadPool; +class QEventLoop; namespace Akonadi { namespace Server { class NotificationCollector; class NotificationSubscriber; +class AggregatedCollectionFetchScope; +class AggregatedItemFetchScope; +class AggregatedTagFetchScope; class NotificationManager : public AkThread { Q_OBJECT public: explicit NotificationManager(); - virtual ~NotificationManager(); + ~NotificationManager() override; - void connectNotificationCollector(NotificationCollector *collector); void forgetSubscriber(NotificationSubscriber *subscriber); + AggregatedCollectionFetchScope *collectionFetchScope() const { return mCollectionFetchScope; } + AggregatedItemFetchScope *itemFetchScope() const { return mItemFetchScope; } + AggregatedTagFetchScope *tagFetchScope() const { return mTagFetchScope; } + public Q_SLOTS: void registerConnection(quintptr socketDescriptor); void emitPendingNotifications(); -private Q_SLOTS: void slotNotify(const Akonadi::Protocol::ChangeNotificationList &msgs); protected: @@ -71,6 +77,13 @@ QThreadPool *mNotifyThreadPool = nullptr; QVector> mSubscribers; int mDebugNotifications; + AggregatedCollectionFetchScope *mCollectionFetchScope = nullptr; + AggregatedItemFetchScope *mItemFetchScope = nullptr; + AggregatedTagFetchScope *mTagFetchScope = nullptr; + + QEventLoop *mEventLoop = nullptr; + bool mWaiting = false; + bool mQuitting = false; friend class NotificationSubscriber; friend class ::NotificationManagerTest; diff --git a/src/server/notificationmanager.cpp b/src/server/notificationmanager.cpp --- a/src/server/notificationmanager.cpp +++ b/src/server/notificationmanager.cpp @@ -23,15 +23,21 @@ #include "storage/notificationcollector.h" #include "tracer.h" #include "akonadiserver_debug.h" - +#include "aggregatedfetchscope.h" +#include "storage/collectionstatistics.h" +#include "storage/selectquerybuilder.h" +#include "handler/fetchhelper.h" +#include "handlerhelper.h" #include -#include +#include #include #include #include #include +#include +#include using namespace Akonadi; using namespace Akonadi::Server; @@ -53,7 +59,7 @@ { AkThread::init(); - const QString serverConfigFile = StandardDirs::serverConfigFile(XdgBaseDirs::ReadWrite); + const QString serverConfigFile = StandardDirs::serverConfigFile(StandardDirs::ReadWrite); QSettings settings(serverConfigFile, QSettings::IniFormat); mTimer = new QTimer(this); @@ -64,10 +70,20 @@ 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; @@ -77,6 +93,10 @@ qDeleteAll(mSubscribers); + delete mCollectionFetchScope; + delete mItemFetchScope; + delete mTagFetchScope; + AkThread::quit(); } @@ -87,40 +107,43 @@ NotificationSubscriber *subscriber = new NotificationSubscriber(this, socketDescriptor); qCDebug(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()); - }); + 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.removeOne(subscriber); -} - -void NotificationManager::connectNotificationCollector(NotificationCollector *collector) -{ - connect(collector, &NotificationCollector::notify, - this, &NotificationManager::slotNotify); + mSubscribers.removeAll(subscriber); } void NotificationManager::slotNotify(const Protocol::ChangeNotificationList &msgs) { Q_ASSERT(QThread::currentThread() == thread()); - for (const auto &msg : msgs) { - if (msg->type() == Protocol::Command::CollectionChangeNotification) { + switch (msg->type()) { + case Protocol::Command::CollectionChangeNotification: Protocol::CollectionChangeNotification::appendAndCompress(mNotifications, msg); - } else { + 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; } } @@ -169,15 +192,17 @@ if (mDebugNotifications == 0) { for (NotificationSubscriber *subscriber : qAsConst(mSubscribers)) { - mNotifyThreadPool->start(new NotifyRunnable(subscriber, mNotifications)); + 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->notify(notification)) { + if (subscriber && subscriber->notify(notification)) { listeners.push_back(subscriber->subscriber()); } } @@ -197,6 +222,8 @@ debugNtf->setListeners(listeners); debugNtf->setTimestamp(QDateTime::currentMSecsSinceEpoch()); for (NotificationSubscriber *subscriber : qAsConst(mSubscribers)) { - mNotifyThreadPool->start(new NotifyRunnable(subscriber, { debugNtf })); + if (subscriber) { + mNotifyThreadPool->start(new NotifyRunnable(subscriber, { debugNtf })); + } } } diff --git a/src/server/notificationsubscriber.h b/src/server/notificationsubscriber.h --- a/src/server/notificationsubscriber.h +++ b/src/server/notificationsubscriber.h @@ -45,16 +45,22 @@ explicit NotificationSubscriber(NotificationManager *manager, quintptr socketDescriptor); ~NotificationSubscriber(); - inline QByteArray subscriber() const + Q_REQUIRED_RESULT inline QByteArray subscriber() const { return mSubscriber; } + Q_REQUIRED_RESULT QLocalSocket *socket() const + { + return mSocket; + } + + void handleIncomingData(); + public Q_SLOTS: bool notify(const Akonadi::Protocol::ChangeNotificationPtr ¬ification); private Q_SLOTS: - void socketReadyRead(); void socketDisconnected(); Q_SIGNALS: @@ -79,6 +85,7 @@ bool isMoveDestinationResourceMonitored(const Protocol::ItemChangeNotification &msg) const; bool isMoveDestinationResourceMonitored(const Protocol::CollectionChangeNotification &msg) const; + Protocol::CollectionChangeNotificationPtr customizeCollection(const Protocol::CollectionChangeNotificationPtr &msg); Protocol::SubscriptionChangeNotificationPtr toChangeNotification() const; protected Q_SLOTS: @@ -101,6 +108,9 @@ QSet mMonitoredResources; QSet mIgnoredSessions; QByteArray mSession; + Protocol::ItemFetchScope mItemFetchScope; + Protocol::CollectionFetchScope mCollectionFetchScope; + Protocol::TagFetchScope mTagFetchScope; bool mAllMonitored; bool mExclusive; bool mNotificationDebugging; diff --git a/src/server/notificationsubscriber.cpp b/src/server/notificationsubscriber.cpp --- a/src/server/notificationsubscriber.cpp +++ b/src/server/notificationsubscriber.cpp @@ -21,6 +21,9 @@ #include "akonadiserver_debug.h" #include "notificationmanager.h" #include "collectionreferencemanager.h" +#include "aggregatedfetchscope.h" +#include "storage/querybuilder.h" +#include "utils.h" #include #include @@ -52,7 +55,7 @@ { mSocket = new QLocalSocket(this); connect(mSocket, &QLocalSocket::readyRead, - this, &NotificationSubscriber::socketReadyRead); + this, &NotificationSubscriber::handleIncomingData); connect(mSocket, &QLocalSocket::disconnected, this, &NotificationSubscriber::socketDisconnected); mSocket->setSocketDescriptor(socketDescriptor); @@ -76,7 +79,7 @@ } } -void NotificationSubscriber::socketReadyRead() +void NotificationSubscriber::handleIncomingData() { while (mSocket->bytesAvailable() > (int) sizeof(qint64)) { QDataStream stream(mSocket); @@ -138,19 +141,29 @@ { QMutexLocker locker(&mLock); - if (mManager) { - auto changeNtf = Protocol::SubscriptionChangeNotificationPtr::create(); - changeNtf->setSubscriber(mSubscriber); - changeNtf->setSessionId(mSession); - changeNtf->setOperation(Protocol::SubscriptionChangeNotification::Remove); - mManager->slotNotify({ changeNtf }); - } + auto changeNtf = Protocol::SubscriptionChangeNotificationPtr::create(); + changeNtf->setSubscriber(mSubscriber); + changeNtf->setSessionId(mSession); + changeNtf->setOperation(Protocol::SubscriptionChangeNotification::Remove); + mManager->slotNotify({ changeNtf }); - disconnect(mSocket, &QLocalSocket::readyRead, - this, &NotificationSubscriber::socketReadyRead); disconnect(mSocket, &QLocalSocket::disconnected, this, &NotificationSubscriber::socketDisconnected); mSocket->close(); + + // Unregister ourselves from the aggergated collection fetch scope + auto cfs = mManager->collectionFetchScope(); + if (mCollectionFetchScope.fetchIdOnly()) { + cfs->setFetchIdOnly(false); + } + if (mCollectionFetchScope.includeStatistics()) { + cfs->setFetchStatistics(false); + } + const auto attrs = mCollectionFetchScope.attributes(); + for (const auto &attr : attrs) { + cfs->removeAttribute(attr); + } + mManager->forgetSubscriber(this); deleteLater(); } @@ -163,13 +176,11 @@ mSubscriber = command.subscriberName(); mSession = command.session(); - if (mManager) { - auto changeNtf = Protocol::SubscriptionChangeNotificationPtr::create(); - changeNtf->setSubscriber(mSubscriber); - changeNtf->setSessionId(mSession); - changeNtf->setOperation(Protocol::SubscriptionChangeNotification::Add); - mManager->slotNotify({ changeNtf }); - } + auto changeNtf = Protocol::SubscriptionChangeNotificationPtr::create(); + changeNtf->setSubscriber(mSubscriber); + changeNtf->setSessionId(mSession); + changeNtf->setOperation(Protocol::SubscriptionChangeNotification::Add); + mManager->slotNotify({ changeNtf }); } void NotificationSubscriber::modifySubscription(const Protocol::ModifySubscriptionCommand &command) @@ -243,6 +254,21 @@ if (modifiedParts & Protocol::ModifySubscriptionCommand::ExclusiveFlag) { mExclusive = command.isExclusive(); } + if (modifiedParts & Protocol::ModifySubscriptionCommand::ItemFetchScope) { + const auto newScope = command.itemFetchScope(); + mManager->itemFetchScope()->apply(mItemFetchScope, newScope); + mItemFetchScope = newScope; + } + if (modifiedParts & Protocol::ModifySubscriptionCommand::CollectionFetchScope) { + const auto newScope = command.collectionFetchScope(); + mManager->collectionFetchScope()->apply(mCollectionFetchScope, newScope); + mCollectionFetchScope = newScope; + } + if (modifiedParts & Protocol::ModifySubscriptionCommand::TagFetchScope) { + const auto newScope = command.tagFetchScope(); + mManager->tagFetchScope()->apply(mTagFetchScope, newScope); + mTagFetchScope = newScope; + } if (mManager) { if (modifiedParts & Protocol::ModifySubscriptionCommand::Types) { @@ -300,6 +326,8 @@ ntf->setIgnoredSessions(mIgnoredSessions); ntf->setAllMonitored(mAllMonitored); ntf->setExclusive(mExclusive); + ntf->setItemFetchScope(mItemFetchScope); + ntf->setCollectionFetchScope(mCollectionFetchScope); return ntf; } @@ -373,8 +401,8 @@ || notificationForParentResource; TRACE_NTF("ACCEPTS ITEM: parent col referenced" << "exclusive:" << mExclusive << "," - << "parent monitored:" << isCollectionMonitored(notification.parentCollection()) << "," - << "destination monitored:" << isMoveDestinationResourceMonitored(notification) << "," + << "parent monitored:" << isCollectionMonitored(msg.parentCollection()) << "," + << "destination monitored:" << isMoveDestinationResourceMonitored(msg) << "," << "ntf for parent resource:" << notificationForParentResource << ":" << "ACCEPTED:" << accepts); return accepts; @@ -403,7 +431,7 @@ } Q_FOREACH (const auto &item, msg.items()) { - if (isMimeTypeMonitored(item.mimeType)) { + if (isMimeTypeMonitored(item.mimeType())) { TRACE_NTF("ACCEPTS ITEM: ACCEPTED - mimetype monitored"); return true; } @@ -415,7 +443,7 @@ // we explicitly monitor that item or the collections it's in Q_FOREACH (const auto &item, msg.items()) { - if (mMonitoredItems.contains(item.id)) { + if (mMonitoredItems.contains(item.id())) { TRACE_NTF("ACCEPTS ITEM: ACCEPTED: item explicitly monitored"); return true; } @@ -438,7 +466,8 @@ { // Assumes mLock being locked by caller - if (msg.id() < 0) { + const auto &collection = msg.collection(); + if (collection.id() < 0) { return false; } @@ -455,12 +484,12 @@ } //Deliver the notification if referenced from this session - if (CollectionReferenceManager::instance()->isReferenced(msg.id(), mSession)) { + if (CollectionReferenceManager::instance()->isReferenced(collection.id(), mSession)) { return true; } //Exclusive subscribers still want the notification - if (mExclusive && CollectionReferenceManager::instance()->isReferenced(msg.id())) { + if (mExclusive && CollectionReferenceManager::instance()->isReferenced(collection.id())) { return true; } @@ -498,7 +527,7 @@ } // we explicitly monitor that colleciton, or all of them - if (isCollectionMonitored(msg.id())) { + if (isCollectionMonitored(collection.id())) { return true; } @@ -511,7 +540,7 @@ { // Assumes mLock being locked by caller - if (msg.id() < 0) { + if (msg.tag().id() < 0) { return false; } @@ -537,7 +566,6 @@ if (!msg.resource().isEmpty() && !mIgnoredSessions.contains(msg.resource())) { return false; } - // Now we got here, which means that this notification either has empty // resource, i.e. it is destined for a client applications, or it's // destined for resource that we *think* (see the hack above) this @@ -558,11 +586,11 @@ return true; } - if (mMonitoredTags.contains(msg.id())) { + if (mMonitoredTags.contains(msg.tag().id())) { return true; } - return false; + return true; } bool NotificationSubscriber::acceptsRelationNotification(const Protocol::RelationChangeNotification &msg) const @@ -644,6 +672,20 @@ } } +Protocol::CollectionChangeNotificationPtr NotificationSubscriber::customizeCollection(const Protocol::CollectionChangeNotificationPtr &ntf) +{ + const bool isReferencedFromSession = CollectionReferenceManager::instance()->isReferenced(ntf->collection().id(), mSession); + if (isReferencedFromSession != ntf->collection().referenced()) { + auto copy = Protocol::CollectionChangeNotificationPtr::create(*ntf); + auto copyCol = ntf->collection(); + copyCol.setReferenced(isReferencedFromSession); + copy->setCollection(std::move(copyCol)); + return copy; + } + + return ntf; +} + bool NotificationSubscriber::notify(const Protocol::ChangeNotificationPtr ¬ification) { // Guard against this object being deleted while we are waiting for the lock @@ -654,8 +696,12 @@ } if (acceptsNotification(*notification)) { + auto ntf = notification; + if (ntf->type() == Protocol::Command::CollectionChangeNotification) { + ntf = customizeCollection(notification.staticCast()); + } QMetaObject::invokeMethod(this, "writeNotification", Qt::QueuedConnection, - Q_ARG(Akonadi::Protocol::ChangeNotificationPtr, notification)); + Q_ARG(Akonadi::Protocol::ChangeNotificationPtr, ntf)); return true; } return false; @@ -673,5 +719,16 @@ QDataStream stream(mSocket); stream << tag; - Protocol::serialize(mSocket, cmd); + try { + Protocol::serialize(mSocket, cmd); + if (!mSocket->waitForBytesWritten()) { + if (mSocket->state() == QLocalSocket::ConnectedState) { + qCWarning(AKONADISERVER_LOG) << "Notification socket write timeout!"; + } else { + // client has disconnected, just discard the message + } + } + } catch (const ProtocolException &e) { + qCWarning(AKONADISERVER_LOG) << "Notification protocol exception:" << e.what(); + } } diff --git a/src/server/nulltracer.h b/src/server/nulltracer.h --- a/src/server/nulltracer.h +++ b/src/server/nulltracer.h @@ -33,7 +33,7 @@ class NullTracer : public TracerInterface { public: - virtual ~NullTracer() + ~NullTracer() override { } diff --git a/src/server/preprocessorinstance.h b/src/server/preprocessorinstance.h --- a/src/server/preprocessorinstance.h +++ b/src/server/preprocessorinstance.h @@ -83,7 +83,7 @@ * This, in fact, *should* be equivalent to "mItemQueue.count() > 0" * as the head item in the queue is the one being processed now. */ - bool mBusy; + bool mBusy = false; /** * The date-time at that we have started processing the current @@ -102,7 +102,7 @@ /** * The preprocessor D-Bus interface. Owned. */ - OrgFreedesktopAkonadiPreprocessorInterface *mInterface; + OrgFreedesktopAkonadiPreprocessorInterface *mInterface = nullptr; protected: @@ -129,7 +129,7 @@ * to the slave preprocessor instance. If no item is currently being * processed then this function returns -1; */ - int currentProcessingTime(); + qint64 currentProcessingTime(); /** * Returns the id of this preprocessor. This is actually diff --git a/src/server/preprocessorinstance.cpp b/src/server/preprocessorinstance.cpp --- a/src/server/preprocessorinstance.cpp +++ b/src/server/preprocessorinstance.cpp @@ -42,9 +42,7 @@ PreprocessorInstance::PreprocessorInstance(const QString &id) : QObject() - , mBusy(false) , mId(id) - , mInterface(nullptr) { Q_ASSERT(!id.isEmpty()); } @@ -142,7 +140,7 @@ qCDebug(AKONADISERVER_LOG) << "PreprocessorInstance::processHeadItem(): processing started for item " << itemId; } -int PreprocessorInstance::currentProcessingTime() +qint64 PreprocessorInstance::currentProcessingTime() { if (!mBusy) { return -1; // nothing being processed diff --git a/src/server/search/agentsearchengine.cpp b/src/server/search/agentsearchengine.cpp --- a/src/server/search/agentsearchengine.cpp +++ b/src/server/search/agentsearchengine.cpp @@ -19,7 +19,7 @@ #include "agentsearchengine.h" #include "entities.h" -#include "akonadiserver_debug.h" +#include "akonadiserver_search_debug.h" #include @@ -41,7 +41,7 @@ return; } - qCCritical(AKONADISERVER_LOG) << "Failed to connect to agent manager: " << agentMgr.lastError().message(); + qCCritical(AKONADISERVER_SEARCH_LOG) << "Failed to connect to agent manager: " << agentMgr.lastError().message(); } void AgentSearchEngine::removeSearch(qint64 id) @@ -55,5 +55,5 @@ return; } - qCCritical(AKONADISERVER_LOG) << "Failed to connect to agent manager: " << agentMgr.lastError().message(); + qCCritical(AKONADISERVER_SEARCH_LOG) << "Failed to connect to agent manager: " << agentMgr.lastError().message(); } diff --git a/src/server/search/agentsearchinstance.h b/src/server/search/agentsearchinstance.h --- a/src/server/search/agentsearchinstance.h +++ b/src/server/search/agentsearchinstance.h @@ -36,7 +36,7 @@ Q_OBJECT public: explicit AgentSearchInstance(const QString &id); - virtual ~AgentSearchInstance(); + ~AgentSearchInstance() override; bool init(); diff --git a/src/server/search/searchmanager.h b/src/server/search/searchmanager.h --- a/src/server/search/searchmanager.h +++ b/src/server/search/searchmanager.h @@ -54,7 +54,7 @@ /** Create a new search manager with the given @p searchEngines. */ explicit SearchManager(const QStringList &searchEngines, QObject *parent = nullptr); - ~SearchManager(); + ~SearchManager() override; /** * Returns a global instance of the search manager. diff --git a/src/server/search/searchmanager.cpp b/src/server/search/searchmanager.cpp --- a/src/server/search/searchmanager.cpp +++ b/src/server/search/searchmanager.cpp @@ -20,7 +20,7 @@ #include "searchmanager.h" #include "abstractsearchplugin.h" -#include "akonadiserver_debug.h" +#include "akonadiserver_search_debug.h" #include "agentsearchengine.h" #include "notificationmanager.h" @@ -33,8 +33,6 @@ #include "storage/selectquerybuilder.h" #include "handler/searchhelper.h" - -#include #include #include @@ -86,7 +84,7 @@ if (engineName == QLatin1String("Agent")) { mEngines.append(new AgentSearchEngine); } else { - qCCritical(AKONADISERVER_LOG) << "Unknown search engine type: " << engineName; + qCCritical(AKONADISERVER_SEARCH_LOG) << "Unknown search engine type: " << engineName; } } @@ -159,33 +157,33 @@ QStringList loadedPlugins; const QString pluginOverride = QString::fromLatin1(qgetenv("AKONADI_OVERRIDE_SEARCHPLUGIN")); if (!pluginOverride.isEmpty()) { - qCDebug(AKONADISERVER_LOG) << "Overriding the search plugins with: " << pluginOverride; + qCDebug(AKONADISERVER_SEARCH_LOG) << "Overriding the search plugins with: " << pluginOverride; } - const QStringList dirs = XdgBaseDirs::findPluginDirs(); + const QStringList dirs = QCoreApplication::libraryPaths(); for (const QString &pluginDir : dirs) { QDir dir(pluginDir + QLatin1String("/akonadi")); const QStringList fileNames = dir.entryList(QDir::Files); - qCDebug(AKONADISERVER_LOG) << "SEARCH MANAGER: searching in " << pluginDir + QLatin1String("/akonadi") << ":" << fileNames; + qCDebug(AKONADISERVER_SEARCH_LOG) << "SEARCH MANAGER: searching in " << pluginDir + QLatin1String("/akonadi") << ":" << fileNames; for (const QString &fileName : fileNames) { const QString filePath = pluginDir % QLatin1String("/akonadi/") % fileName; std::unique_ptr loader(new QPluginLoader(filePath)); const QVariantMap metadata = loader->metaData().value(QStringLiteral("MetaData")).toVariant().toMap(); if (metadata.value(QStringLiteral("X-Akonadi-PluginType")).toString() != QLatin1String("SearchPlugin")) { - qCDebug(AKONADISERVER_LOG) << "===>" << fileName << metadata.value(QStringLiteral("X-Akonadi-PluginType")).toString(); + qCDebug(AKONADISERVER_SEARCH_LOG) << "===>" << fileName << metadata.value(QStringLiteral("X-Akonadi-PluginType")).toString(); continue; } const QString libraryName = metadata.value(QStringLiteral("X-Akonadi-Library")).toString(); if (loadedPlugins.contains(libraryName)) { - qCDebug(AKONADISERVER_LOG) << "Already loaded one version of this plugin, skipping: " << libraryName; + qCDebug(AKONADISERVER_SEARCH_LOG) << "Already loaded one version of this plugin, skipping: " << libraryName; continue; } // When search plugin override is active, ignore all plugins except for the override if (!pluginOverride.isEmpty()) { if (libraryName != pluginOverride) { - qCDebug(AKONADISERVER_LOG) << libraryName << "skipped because of AKONADI_OVERRIDE_SEARCHPLUGIN"; + qCDebug(AKONADISERVER_SEARCH_LOG) << libraryName << "skipped because of AKONADI_OVERRIDE_SEARCHPLUGIN"; continue; } @@ -195,7 +193,7 @@ } if (!loader->load()) { - qCCritical(AKONADISERVER_LOG) << "Failed to load search plugin" << libraryName << ":" << loader->errorString(); + qCCritical(AKONADISERVER_SEARCH_LOG) << "Failed to load search plugin" << libraryName << ":" << loader->errorString(); continue; } @@ -209,17 +207,17 @@ { for (QPluginLoader *loader : qAsConst(mPluginLoaders)) { if (!loader->load()) { - qCCritical(AKONADISERVER_LOG) << "Failed to load search plugin" << loader->fileName() << ":" << loader->errorString(); + qCCritical(AKONADISERVER_SEARCH_LOG) << "Failed to load search plugin" << loader->fileName() << ":" << loader->errorString(); continue; } AbstractSearchPlugin *plugin = qobject_cast(loader->instance()); if (!plugin) { - qCCritical(AKONADISERVER_LOG) << loader->fileName() << "is not a valid Akonadi search plugin"; + qCCritical(AKONADISERVER_SEARCH_LOG) << loader->fileName() << "is not a valid Akonadi search plugin"; continue; } - qCDebug(AKONADISERVER_LOG) << "SearchManager: loaded search plugin" << loader->fileName(); + qCDebug(AKONADISERVER_SEARCH_LOG) << "SearchManager: loaded search plugin" << loader->fileName(); mPlugins << plugin; } } @@ -229,7 +227,11 @@ // Reset if the timer is active (use QueuedConnection to invoke start() from // the thread the QTimer lives in instead of caller's thread, otherwise crashes // and weird things can happen. +#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) + QMetaObject::invokeMethod(mSearchUpdateTimer, QOverload<>::of(&QTimer::start), Qt::QueuedConnection); +#else QMetaObject::invokeMethod(mSearchUpdateTimer, "start", Qt::QueuedConnection); +#endif } void SearchManager::searchUpdateTimeout() @@ -243,9 +245,13 @@ void SearchManager::updateSearchAsync(const Collection &collection) { +#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) + QMetaObject::invokeMethod(this, [this, collection]() { updateSearchImpl(collection); }, Qt::QueuedConnection); +#else QMetaObject::invokeMethod(this, "updateSearchImpl", Qt::QueuedConnection, Q_ARG(Collection, collection)); +#endif } void SearchManager::updateSearch(const Collection &collection) @@ -259,20 +265,22 @@ } mUpdatingCollections.insert(collection.id()); mLock.unlock(); - +#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) + QMetaObject::invokeMethod(this, [this, collection]() { updateSearchImpl(collection); }, Qt::BlockingQueuedConnection); +#else QMetaObject::invokeMethod(this, "updateSearchImpl", Qt::BlockingQueuedConnection, Q_ARG(Collection, collection)); - +#endif mLock.lock(); mUpdatingCollections.remove(collection.id()); mLock.unlock(); } void SearchManager::updateSearchImpl(const Collection &collection) { if (collection.queryString().size() >= 32768) { - qCWarning(AKONADISERVER_LOG) << "The query is at least 32768 chars long, which is the maximum size supported by the akonadi db schema. The query is therefore most likely truncated and will not be executed."; + qCWarning(AKONADISERVER_SEARCH_LOG) << "The query is at least 32768 chars long, which is the maximum size supported by the akonadi db schema. The query is therefore most likely truncated and will not be executed."; return; } if (collection.queryString().isEmpty()) { @@ -313,7 +321,7 @@ //This happens if we try to search a virtual collection in recursive mode (because virtual collections are excluded from listCollectionsRecursive) if (queryCollections.isEmpty()) { - qCDebug(AKONADISERVER_LOG) << "No collections to search, you're probably trying to search a virtual collection."; + qCDebug(AKONADISERVER_SEARCH_LOG) << "No collections to search, you're probably trying to search a virtual collection."; return; } @@ -366,15 +374,15 @@ DataStore::self()->notificationCollector()->itemsUnlinked(removedItems, collection); } - qCDebug(AKONADISERVER_LOG) << "Search update finished"; - qCDebug(AKONADISERVER_LOG) << "All results:" << results.count(); - qCDebug(AKONADISERVER_LOG) << "Removed results:" << toRemove.count(); + qCDebug(AKONADISERVER_SEARCH_LOG) << "Search update finished"; + qCDebug(AKONADISERVER_SEARCH_LOG) << "All results:" << results.count(); + qCDebug(AKONADISERVER_SEARCH_LOG) << "Removed results:" << toRemove.count(); } void SearchManager::searchUpdateResultsAvailable(const QSet &results) { const Collection collection = sender()->property("SearchCollection").value(); - qCDebug(AKONADISERVER_LOG) << "searchUpdateResultsAvailable" << collection.id() << results.count() << "results"; + qCDebug(AKONADISERVER_SEARCH_LOG) << "searchUpdateResultsAvailable" << collection.id() << results.count() << "results"; QSet newMatches = results; QSet existingMatches; @@ -394,11 +402,11 @@ } } - qCDebug(AKONADISERVER_LOG) << "Got" << newMatches.count() << "results, out of which" << existingMatches.count() << "are already in the collection"; + qCDebug(AKONADISERVER_SEARCH_LOG) << "Got" << newMatches.count() << "results, out of which" << existingMatches.count() << "are already in the collection"; newMatches = newMatches - existingMatches; if (newMatches.isEmpty()) { - qCDebug(AKONADISERVER_LOG) << "Added results: 0 (fast path)"; + qCDebug(AKONADISERVER_SEARCH_LOG) << "Added results: 0 (fast path)"; return; } @@ -421,28 +429,28 @@ const auto items = qb.result(); if (items.count() != newMatches.count()) { - qCDebug(AKONADISERVER_LOG) << "Search backend returned" << (newMatches.count() - items.count()) << "results that no longer exist in Akonadi."; - qCDebug(AKONADISERVER_LOG) << "Please reindex collection" << collection.id(); + qCDebug(AKONADISERVER_SEARCH_LOG) << "Search backend returned" << (newMatches.count() - items.count()) << "results that no longer exist in Akonadi."; + qCDebug(AKONADISERVER_SEARCH_LOG) << "Please reindex collection" << collection.id(); // TODO: Request the reindexing directly from here } if (items.isEmpty()) { - qCDebug(AKONADISERVER_LOG) << "Added results: 0 (no existing result)"; + qCDebug(AKONADISERVER_SEARCH_LOG) << "Added results: 0 (no existing result)"; return; } for (const auto &item : items) { Collection::addPimItem(collection.id(), item.id()); } if (!transaction.commit()) { - qCDebug(AKONADISERVER_LOG) << "Failed to commit transaction"; + qCDebug(AKONADISERVER_SEARCH_LOG) << "Failed to commit transaction"; return; } DataStore::self()->notificationCollector()->itemsLinked(items, collection); // Force collector to dispatch the notification now DataStore::self()->notificationCollector()->dispatchNotifications(); - qCDebug(AKONADISERVER_LOG) << "Added results:" << items.count(); + qCDebug(AKONADISERVER_SEARCH_LOG) << "Added results:" << items.count(); } diff --git a/src/server/search/searchrequest.cpp b/src/server/search/searchrequest.cpp --- a/src/server/search/searchrequest.cpp +++ b/src/server/search/searchrequest.cpp @@ -24,9 +24,7 @@ #include "abstractsearchplugin.h" #include "searchmanager.h" #include "connection.h" -#include "akonadiserver_debug.h" - -#include +#include "akonadiserver_search_debug.h" using namespace Akonadi::Server; @@ -115,15 +113,15 @@ void SearchRequest::exec() { - qCDebug(AKONADISERVER_LOG) << "Executing search" << mConnectionId; + qCDebug(AKONADISERVER_SEARCH_LOG) << "Executing search" << mConnectionId; //TODO should we move this to the AgentSearchManager as well? If we keep it here the agents can be searched in parallel //since the plugin search is executed in this thread directly. searchPlugins(); // If remote search is disabled, just finish here after searching the plugins if (!mRemoteSearch) { - qCDebug(AKONADISERVER_LOG) << "Search done" << mConnectionId << "(without remote search)"; + qCDebug(AKONADISERVER_SEARCH_LOG) << "Search done" << mConnectionId << "(without remote search)"; return; } @@ -139,12 +137,12 @@ task.sharedLock.lock(); Q_FOREVER { if (task.complete) { - qCDebug(AKONADISERVER_LOG) << "All queries processed!"; + qCDebug(AKONADISERVER_SEARCH_LOG) << "All queries processed!"; break; } else { task.notifier.wait(&task.sharedLock); - qCDebug(AKONADISERVER_LOG) << task.pendingResults.count() << "search results available in search" << task.id; + qCDebug(AKONADISERVER_SEARCH_LOG) << task.pendingResults.count() << "search results available in search" << task.id; if (!task.pendingResults.isEmpty()) { emitResults(task.pendingResults); } @@ -157,5 +155,5 @@ } task.sharedLock.unlock(); - qCDebug(AKONADISERVER_LOG) << "Search done" << mConnectionId; + qCDebug(AKONADISERVER_SEARCH_LOG) << "Search done" << mConnectionId; } diff --git a/src/server/search/searchtaskmanager.cpp b/src/server/search/searchtaskmanager.cpp --- a/src/server/search/searchtaskmanager.cpp +++ b/src/server/search/searchtaskmanager.cpp @@ -23,7 +23,7 @@ #include "storage/selectquerybuilder.h" #include "dbusconnectionpool.h" #include "entities.h" -#include "akonadiserver_debug.h" +#include "akonadiserver_search_debug.h" #include @@ -69,21 +69,21 @@ { QMutexLocker locker(&mInstancesLock); - qCDebug(AKONADISERVER_LOG) << "SearchManager::registerInstance(" << id << ")"; + qCDebug(AKONADISERVER_SEARCH_LOG) << "SearchManager::registerInstance(" << id << ")"; AgentSearchInstance *instance = mInstances.value(id); if (instance) { return; // already registered } instance = new AgentSearchInstance(id); if (!instance->init()) { - qCDebug(AKONADISERVER_LOG) << "Failed to initialize Search agent"; + qCDebug(AKONADISERVER_SEARCH_LOG) << "Failed to initialize Search agent"; delete instance; return; } - qCDebug(AKONADISERVER_LOG) << "Registering search instance " << id; + qCDebug(AKONADISERVER_SEARCH_LOG) << "Registering search instance " << id; mInstances.insert(id, instance); } @@ -93,7 +93,7 @@ QMap::Iterator it = mInstances.find(id); if (it != mInstances.end()) { - qCDebug(AKONADISERVER_LOG) << "Unregistering search instance" << id; + qCDebug(AKONADISERVER_SEARCH_LOG) << "Unregistering search instance" << id; it.value()->deleteLater(); mInstances.erase(it); } @@ -132,14 +132,14 @@ do { const QString resourceId = query.value(1).toString(); if (!mInstances.contains(resourceId)) { - qCDebug(AKONADISERVER_LOG) << "Resource" << resourceId << "does not implement Search interface, skipping"; + qCDebug(AKONADISERVER_SEARCH_LOG) << "Resource" << resourceId << "does not implement Search interface, skipping"; } else if (!agentManager.agentInstanceOnline(resourceId)) { - qCDebug(AKONADISERVER_LOG) << "Agent" << resourceId << "is offline, skipping"; + qCDebug(AKONADISERVER_SEARCH_LOG) << "Agent" << resourceId << "is offline, skipping"; } else if (agentManager.agentInstanceStatus(resourceId) > 2) { // 2 == Broken, 3 == Not Configured - qCDebug(AKONADISERVER_LOG) << "Agent" << resourceId << "is broken or not configured"; + qCDebug(AKONADISERVER_SEARCH_LOG) << "Agent" << resourceId << "is broken or not configured"; } else { const qint64 collectionId = query.value(0).toLongLong(); - qCDebug(AKONADISERVER_LOG) << "Enqueued search query (" << resourceId << ", " << collectionId << ")"; + qCDebug(AKONADISERVER_SEARCH_LOG) << "Enqueued search query (" << resourceId << ", " << collectionId << ")"; task->queries << qMakePair(resourceId, collectionId); } } while (query.next()); @@ -155,18 +155,18 @@ { Q_UNUSED(searchId); - qCDebug(AKONADISERVER_LOG) << ids.count() << "results for search" << searchId << "pushed from" << connection->context()->resource().name(); + qCDebug(AKONADISERVER_SEARCH_LOG) << ids.count() << "results for search" << searchId << "pushed from" << connection->context()->resource().name(); QMutexLocker locker(&mLock); ResourceTask *task = mRunningTasks.take(connection->context()->resource().name()); if (!task) { - qCDebug(AKONADISERVER_LOG) << "No running task for" << connection->context()->resource().name() << " - maybe it has timed out?"; + qCDebug(AKONADISERVER_SEARCH_LOG) << "No running task for" << connection->context()->resource().name() << " - maybe it has timed out?"; return; } if (task->parentTask->id != searchId) { - qCDebug(AKONADISERVER_LOG) << "Received results for different search - maybe the original task has timed out?"; - qCDebug(AKONADISERVER_LOG) << "Search is" << searchId << ", but task is" << task->parentTask->id; + qCDebug(AKONADISERVER_SEARCH_LOG) << "Received results for different search - maybe the original task has timed out?"; + qCDebug(AKONADISERVER_SEARCH_LOG) << "Search is" << searchId << ", but task is" << task->parentTask->id; return; } @@ -217,7 +217,7 @@ QMutexLocker locker(&mLock); Q_FOREVER { - qCDebug(AKONADISERVER_LOG) << "Search loop is waiting, will wake again in" << timeout << "ms"; + qCDebug(AKONADISERVER_SEARCH_LOG) << "Search loop is waiting, will wake again in" << timeout << "ms"; mWait.wait(&mLock, timeout); if (mShouldStop) { @@ -244,7 +244,7 @@ while (!mPendingResults.isEmpty()) { ResourceTask *finishedTask = mPendingResults.first(); mPendingResults.remove(0); - qCDebug(AKONADISERVER_LOG) << "Pending results from" << finishedTask->resourceId << "for collection" << finishedTask->collectionId << "for search" << finishedTask->parentTask->id << "available!"; + qCDebug(AKONADISERVER_SEARCH_LOG) << "Pending results from" << finishedTask->resourceId << "for collection" << finishedTask->collectionId << "for search" << finishedTask->parentTask->id << "available!"; SearchTask *parentTask = finishedTask->parentTask; QMutexLocker locker(&parentTask->sharedLock); // We need to append, this agent search task is shared @@ -261,18 +261,18 @@ ResourceTask *task = it.value(); if (now - task->timestamp > 60 * 1000) { // Remove the task - and signal to parent task that it has "finished" without results - qCDebug(AKONADISERVER_LOG) << "Resource task" << task->resourceId << "for search" << task->parentTask->id << "timed out!"; + qCDebug(AKONADISERVER_SEARCH_LOG) << "Resource task" << task->resourceId << "for search" << task->parentTask->id << "timed out!"; it = cancelRunningTask(it); } else { ++it; } } if (!mTasklist.isEmpty()) { SearchTask *task = mTasklist.first(); - qCDebug(AKONADISERVER_LOG) << "Search task" << task->id << "available!"; + qCDebug(AKONADISERVER_SEARCH_LOG) << "Search task" << task->id << "available!"; if (task->queries.isEmpty()) { - qCDebug(AKONADISERVER_LOG) << "nothing to do for task"; + qCDebug(AKONADISERVER_SEARCH_LOG) << "nothing to do for task"; QMutexLocker locker(&task->sharedLock); //After this the AgentSearchTask will be destroyed task->complete = true; @@ -284,7 +284,7 @@ QVector >::iterator it = task->queries.begin(); for (; it != task->queries.end();) { if (!mRunningTasks.contains(it->first)) { - qCDebug(AKONADISERVER_LOG) << "\t Sending query for collection" << it->second << "to resource" << it->first; + qCDebug(AKONADISERVER_SEARCH_LOG) << "\t Sending query for collection" << it->second << "to resource" << it->first; ResourceTask *rTask = new ResourceTask; rTask->resourceId = it->first; rTask->collectionId = it->second; @@ -312,7 +312,7 @@ } // Yay! We managed to dispatch all requests! if (task->queries.isEmpty()) { - qCDebug(AKONADISERVER_LOG) << "All queries from task" << task->id << "dispatched!"; + qCDebug(AKONADISERVER_SEARCH_LOG) << "All queries from task" << task->id << "dispatched!"; mTasklist.remove(0); } diff --git a/src/server/storage/CMakeLists.txt b/src/server/storage/CMakeLists.txt deleted file mode 100644 --- a/src/server/storage/CMakeLists.txt +++ /dev/null @@ -1,8 +0,0 @@ -find_program(XMLLINT_EXECUTABLE xmllint) - -if(NOT XMLLINT_EXECUTABLE) - message(STATUS "xmllint not found, skipping akonadidb.xml schema validation") -else() - add_test(akonadidb-xmllint ${XMLLINT_EXECUTABLE} --noout --schema ${CMAKE_CURRENT_SOURCE_DIR}/akonadidb.xsd ${CMAKE_CURRENT_SOURCE_DIR}/akonadidb.xml) - add_test(akonadidbupdate-xmllint ${XMLLINT_EXECUTABLE} --noout --schema ${CMAKE_CURRENT_SOURCE_DIR}/dbupdate.xsd ${CMAKE_CURRENT_SOURCE_DIR}/dbupdate.xml) -endif() diff --git a/src/server/storage/collectionstatistics.cpp b/src/server/storage/collectionstatistics.cpp --- a/src/server/storage/collectionstatistics.cpp +++ b/src/server/storage/collectionstatistics.cpp @@ -195,16 +195,16 @@ Query::Condition seenCondition(Query::And); seenCondition.addColumnCondition(PimItem::idFullColumnName(), Query::Equals, FLAGS_COLUMN(SeenFlags, leftColumn)); seenCondition.addValueCondition(FLAGS_COLUMN(SeenFlags, rightColumn), Query::Equals, - Flag::retrieveByName(QStringLiteral(AKONADI_FLAG_SEEN)).id()); + Flag::retrieveByNameOrCreate(QStringLiteral(AKONADI_FLAG_SEEN)).id()); qb.addJoin(QueryBuilder::LeftJoin, QStringLiteral("%1 AS %2").arg(PimItemFlagRelation::tableName(), SeenFlagsTableName), seenCondition); } { Query::Condition ignoredCondition(Query::And); ignoredCondition.addColumnCondition(PimItem::idFullColumnName(), Query::Equals, FLAGS_COLUMN(IgnoredFlags, leftColumn)); ignoredCondition.addValueCondition(FLAGS_COLUMN(IgnoredFlags, rightColumn), Query::Equals, - Flag::retrieveByName(QStringLiteral(AKONADI_FLAG_IGNORED)).id()); + Flag::retrieveByNameOrCreate(QStringLiteral(AKONADI_FLAG_IGNORED)).id()); qb.addJoin(QueryBuilder::LeftJoin, QStringLiteral("%1 AS %2").arg(PimItemFlagRelation::tableName(), IgnoredFlagsTableName), ignoredCondition); diff --git a/src/server/storage/collectiontreecache.h b/src/server/storage/collectiontreecache.h --- a/src/server/storage/collectiontreecache.h +++ b/src/server/storage/collectiontreecache.h @@ -65,7 +65,7 @@ public: explicit CollectionTreeCache(); - ~CollectionTreeCache(); + ~CollectionTreeCache() override; QVector retrieveCollections(const Scope &scope, int depth, int ancestorDepth, diff --git a/src/server/storage/datastore.h b/src/server/storage/datastore.h --- a/src/server/storage/datastore.h +++ b/src/server/storage/datastore.h @@ -32,11 +32,21 @@ #include "entities.h" #include "notificationcollector.h" +#include + namespace Akonadi { namespace Server { +class DataStore; +class DataStoreFactory +{ +public: + virtual ~DataStoreFactory() = default; + virtual DataStore *createStore(); +}; + class NotificationCollector; /** @@ -96,7 +106,7 @@ /** Closes the database connection and destroys the DataStore object. */ - virtual ~DataStore(); + ~DataStore() override; /** Opens the database connection. @@ -106,7 +116,7 @@ /** Closes the database connection. */ - virtual void close(); + void close(); /** Initializes the database. Should be called during startup by the main thread. @@ -148,7 +158,7 @@ virtual bool invalidateItemCache(const PimItem &item); /* --- Collection ------------------------------------------------------ */ - virtual bool appendCollection(Collection &collection); + virtual bool appendCollection(Collection &collection, const QStringList &mimeTypes, const QMap &attributes); /// removes the given collection and all its content virtual bool cleanupCollection(Collection &collection); @@ -214,7 +224,7 @@ /* --- Collection attributes ------------------------------------------ */ virtual bool addCollectionAttribute(const Collection &col, const QByteArray &key, - const QByteArray &value); + const QByteArray &value, bool silent = false); /** * Removes the given collection attribute for @p col. * @throws HandlerException on database errors @@ -246,13 +256,13 @@ /** Returns true if there is a transaction in progress. */ - virtual bool inTransaction() const; + bool inTransaction() const; /** Returns the notification collector of this DataStore object. Use this to listen to change notification signals. */ - virtual NotificationCollector *notificationCollector(); + NotificationCollector *notificationCollector(); /** Returns the QSqlDatabase object. Use this for generating queries yourself. @@ -350,6 +360,7 @@ protected: static QThreadStorage sInstances; + static std::unique_ptr sFactory; QString m_connectionName; QSqlDatabase m_database; @@ -362,12 +373,13 @@ }; QVector m_transactionQueries; QByteArray mSessionId; - NotificationCollector *mNotificationCollector = nullptr; + std::unique_ptr mNotificationCollector; QTimer *m_keepAliveTimer = nullptr; static bool s_hasForeignKeyConstraints; // Gives QueryBuilder access to addQueryToTransaction() and retryLastTransaction() friend class QueryBuilder; + friend class DataStoreFactory; }; diff --git a/src/server/storage/datastore.cpp b/src/server/storage/datastore.cpp --- a/src/server/storage/datastore.cpp +++ b/src/server/storage/datastore.cpp @@ -75,18 +75,23 @@ } \ } +DataStore *DataStoreFactory::createStore() +{ + return new DataStore(); +} + +std::unique_ptr DataStore::sFactory = std::make_unique(); + + /*************************************************************************** * DataStore * ***************************************************************************/ DataStore::DataStore() : QObject() , m_dbOpened(false) , m_transactionLevel(0) - , mNotificationCollector(nullptr) , m_keepAliveTimer(nullptr) { - notificationCollector(); - if (DbConfig::configuredDatabase()->driverName() == QLatin1String("QMYSQL")) { // Send a dummy query to MySQL every 1 hour to keep the connection alive, // otherwise MySQL just drops the connection and our subsequent queries fail @@ -217,21 +222,17 @@ NotificationCollector *DataStore::notificationCollector() { - if (mNotificationCollector == nullptr) { - mNotificationCollector = new NotificationCollector(this); - NotificationManager *notificationManager = AkonadiServer::instance()->notificationManager(); - if (notificationManager) { - notificationManager->connectNotificationCollector(notificationCollector()); - } + if (!mNotificationCollector) { + mNotificationCollector = std::make_unique(this); } - return mNotificationCollector; + return mNotificationCollector.get(); } DataStore *DataStore::self() { if (!sInstances.hasLocalData()) { - sInstances.setLocalData(new DataStore()); + sInstances.setLocalData(sFactory->createStore()); } return sInstances.localData(); } @@ -267,7 +268,7 @@ } } - Q_FOREACH (const Flag &flag, flags) { + for (const Flag &flag : flags) { if (!itemFlags.contains(flag)) { addedFlags << flag.name().toLatin1(); insIds << item.id(); @@ -301,7 +302,7 @@ } if (!silent && (!addedFlags.isEmpty() || !removedFlags.isEmpty())) { - mNotificationCollector->itemsFlagsChanged(items, addedFlags, removedFlags, col); + notificationCollector()->itemsFlagsChanged(items, addedFlags, removedFlags, col); } setBoolPtr(flagsChanged, (addedFlags != removedFlags)); @@ -347,7 +348,7 @@ } if (!silent) { - mNotificationCollector->itemsFlagsChanged(appendItems, QSet() << flag.name().toLatin1(), + notificationCollector()->itemsFlagsChanged(appendItems, QSet() << flag.name().toLatin1(), QSet(), col); } @@ -448,7 +449,7 @@ if (qb.query().numRowsAffected() != 0) { setBoolPtr(flagsChanged, true); if (!silent) { - mNotificationCollector->itemsFlagsChanged(items, QSet(), removedFlags, col); + notificationCollector()->itemsFlagsChanged(items, QSet(), removedFlags, col); } } @@ -510,7 +511,7 @@ } if (!silent && (!addedTags.empty() || !removedTags.empty())) { - mNotificationCollector->itemsTagsChanged(items, addedTags, removedTags); + notificationCollector()->itemsTagsChanged(items, addedTags, removedTags); } setBoolPtr(tagsChanged, (addedTags != removedTags)); @@ -549,7 +550,7 @@ } if (!silent) { - mNotificationCollector->itemsTagsChanged(appendItems, QSet() << tag.id(), + notificationCollector()->itemsTagsChanged(appendItems, QSet() << tag.id(), QSet(), col); } @@ -643,7 +644,7 @@ if (qb.query().numRowsAffected() != 0) { setBoolPtr(tagsChanged, true); if (!silent) { - mNotificationCollector->itemsTagsChanged(items, QSet(), removedTags); + notificationCollector()->itemsTagsChanged(items, QSet(), removedTags); } } @@ -659,7 +660,7 @@ QSet removedTags; removedTagsIds.reserve(tags.count()); removedTags.reserve(tags.count()); - Q_FOREACH (const Tag &tag, tags) { + for (const Tag &tag : tags) { removedTagsIds << tag.id(); removedTags << tag.id(); } @@ -676,7 +677,7 @@ const PimItem::List items = itemsQuery.result(); if (!items.isEmpty()) { - DataStore::self()->notificationCollector()->itemsTagsChanged(items, QSet(), removedTags); + notificationCollector()->itemsTagsChanged(items, QSet(), removedTags); } Q_FOREACH (const Tag &tag, tags) { @@ -698,11 +699,11 @@ const QString rid = query.value(0).toString(); const QByteArray resource = query.value(1).toByteArray(); - DataStore::self()->notificationCollector()->tagRemoved(tag, resource, rid); + notificationCollector()->tagRemoved(tag, resource, rid); } // And one for clients - without RID - DataStore::self()->notificationCollector()->tagRemoved(tag, QByteArray(), QString()); + notificationCollector()->tagRemoved(tag, QByteArray(), QString()); } // Just remove the tags, table constraints will take care of the rest @@ -733,7 +734,7 @@ } } - mNotificationCollector->itemChanged(item, parts); + notificationCollector()->itemChanged(item, parts); return true; } @@ -764,15 +765,25 @@ } /* --- Collection ------------------------------------------------------ */ -bool DataStore::appendCollection(Collection &collection) +bool DataStore::appendCollection(Collection &collection, const QStringList &mimeTypes, const QMap &attributes) { // no need to check for already existing collection with the same name, // a unique index on parent + name prevents that in the database if (!collection.insert()) { return false; } - mNotificationCollector->collectionAdded(collection); + if (!appendMimeTypeForCollection(collection.id(), mimeTypes)) { + return false; + } + + for (auto it = attributes.cbegin(), end = attributes.cend(); it != end; ++it) { + if (!addCollectionAttribute(collection, it.key(), it.value(), true)) { + return false; + } + } + + notificationCollector()->collectionAdded(collection); return true; } @@ -791,7 +802,7 @@ // generate the notification before actually removing the data // TODO: we should try to get rid of this, requires client side changes to resources and Monitor though - mNotificationCollector->itemsRemoved(items, collection, resource); + notificationCollector()->itemsRemoved(items, collection, resource); // remove all external payload parts QueryBuilder qb(Part::tableName(), QueryBuilder::Select); @@ -816,7 +827,7 @@ } // delete the collection itself, referential actions will do the rest - mNotificationCollector->collectionRemoved(collection); + notificationCollector()->collectionRemoved(collection); return collection.remove(); } @@ -827,7 +838,7 @@ // delete the content const PimItem::List items = collection.items(); const QByteArray resource = collection.resource().name().toLatin1(); - mNotificationCollector->itemsRemoved(items, collection, resource); + notificationCollector()->itemsRemoved(items, collection, resource); for (const PimItem &item : items) { if (!item.clearFlags()) { // TODO: move out of loop and use only a single query @@ -858,7 +869,7 @@ } // delete the collection itself - mNotificationCollector->collectionRemoved(collection); + notificationCollector()->collectionRemoved(collection); return collection.remove(); } @@ -934,7 +945,7 @@ return false; } - mNotificationCollector->collectionMoved(collection, source, oldResource, newParent.resource().name().toLatin1()); + notificationCollector()->collectionMoved(collection, source, oldResource, newParent.resource().name().toLatin1()); return true; } @@ -1017,7 +1028,7 @@ } else { QVariantList ids; ids.reserve(items.count()); - Q_FOREACH (const PimItem &item, items) { + for (const PimItem &item : items) { ids << item.id(); } qb.addValueCondition(CollectionPimItemRelation::rightFullColumnName(), Query::In, ids); @@ -1108,7 +1119,7 @@ // qCDebug(AKONADISERVER_LOG) << "appendPimItem: " << pimItem; - mNotificationCollector->itemAdded(pimItem, seen, collection); + notificationCollector()->itemAdded(pimItem, seen, collection); return true; } @@ -1155,12 +1166,12 @@ } const Relation::List relations = relationQuery.result(); for (const Relation &relation : relations) { - DataStore::self()->notificationCollector()->relationRemoved(relation); + notificationCollector()->relationRemoved(relation); } } // generate the notification before actually removing the data - mNotificationCollector->itemsRemoved(items); + notificationCollector()->itemsRemoved(items); // FIXME: Create a single query to do this Q_FOREACH (const PimItem &item, items) { @@ -1182,7 +1193,7 @@ return true; } -bool DataStore::addCollectionAttribute(const Collection &col, const QByteArray &key, const QByteArray &value) +bool DataStore::addCollectionAttribute(const Collection &col, const QByteArray &key, const QByteArray &value, bool silent) { SelectQueryBuilder qb; qb.addValueCondition(CollectionAttribute::collectionIdColumn(), Query::Equals, col.id()); @@ -1205,7 +1216,9 @@ return false; } - mNotificationCollector->collectionChanged(col, QList() << key); + if (!silent) { + notificationCollector()->collectionChanged(col, QList() << key); + } return true; } @@ -1226,7 +1239,7 @@ } if (!result.isEmpty()) { - mNotificationCollector->collectionChanged(col, QList() << key); + notificationCollector()->collectionChanged(col, QList() << key); return true; } return false; @@ -1316,7 +1329,7 @@ for (auto q = m_transactionQueries.begin(), qEnd = m_transactionQueries.end(); q != qEnd; ++q) { QSqlQuery query(database()); query.prepare(q->query); - for (int i = 0; i < q->boundValues.count(); ++i) { + for (int i = 0, total = q->boundValues.count(); i < total; ++i) { query.bindValue(QLatin1Char(':') + QString::number(i), q->boundValues.at(i)); } @@ -1446,7 +1459,8 @@ if (m_transactionLevel == 1) { QSqlDriver *driver = m_database.driver(); - QElapsedTimer timer; timer.start(); + QElapsedTimer timer; + timer.start(); driver->commitTransaction(); StorageDebugger::instance()->removeTransaction(reinterpret_cast(this), true, timer.elapsed(), @@ -1457,13 +1471,14 @@ return false; } else { TRANSACTION_MUTEX_UNLOCK; + m_transactionLevel--; Q_EMIT transactionCommitted(); } m_transactionQueries.clear(); + } else { + m_transactionLevel--; } - - m_transactionLevel--; return true; } diff --git a/src/server/storage/dbconfig.cpp b/src/server/storage/dbconfig.cpp --- a/src/server/storage/dbconfig.cpp +++ b/src/server/storage/dbconfig.cpp @@ -25,7 +25,6 @@ #include "akonadiserver_debug.h" #include -#include #include #include @@ -38,7 +37,7 @@ DbConfig::DbConfig() { - const QString serverConfigFile = StandardDirs::serverConfigFile(XdgBaseDirs::ReadWrite); + const QString serverConfigFile = StandardDirs::serverConfigFile(StandardDirs::ReadWrite); QSettings settings(serverConfigFile, QSettings::IniFormat); mSizeThreshold = 4096; @@ -66,7 +65,7 @@ DbConfig *DbConfig::configuredDatabase() { if (!s_DbConfigInstance) { - const QString serverConfigFile = StandardDirs::serverConfigFile(XdgBaseDirs::ReadWrite); + const QString serverConfigFile = StandardDirs::serverConfigFile(StandardDirs::ReadWrite); QSettings settings(serverConfigFile, QSettings::IniFormat); // determine driver to use diff --git a/src/server/storage/dbconfigmysql.cpp b/src/server/storage/dbconfigmysql.cpp --- a/src/server/storage/dbconfigmysql.cpp +++ b/src/server/storage/dbconfigmysql.cpp @@ -22,7 +22,6 @@ #include "akonadiserver_debug.h" #include -#include #include #include @@ -33,6 +32,7 @@ #include #include #include +#include using namespace Akonadi; using namespace Akonadi::Server; @@ -86,10 +86,10 @@ << QStringLiteral("/opt/local/lib/mysql5/bin") << QStringLiteral("/opt/mysql/sbin"); if (defaultServerPath.isEmpty()) { - defaultServerPath = XdgBaseDirs::findExecutableFile(QStringLiteral("mysqld"), mysqldSearchPath); + defaultServerPath = QStandardPaths::findExecutable(QStringLiteral("mysqld"), mysqldSearchPath); } - const QString mysqladminPath = XdgBaseDirs::findExecutableFile(QStringLiteral("mysqladmin"), mysqldSearchPath); + const QString mysqladminPath = QStandardPaths::findExecutable(QStringLiteral("mysqladmin"), mysqldSearchPath); if (!mysqladminPath.isEmpty()) { #ifndef Q_OS_WIN defaultCleanShutdownCommand = QStringLiteral("%1 --defaults-file=%2/mysql.conf --socket=%3/mysql.socket shutdown") @@ -99,10 +99,10 @@ #endif } - mMysqlInstallDbPath = XdgBaseDirs::findExecutableFile(QStringLiteral("mysql_install_db"), mysqldSearchPath); + mMysqlInstallDbPath = QStandardPaths::findExecutable(QStringLiteral("mysql_install_db"), mysqldSearchPath); qCDebug(AKONADISERVER_LOG) << "Found mysql_install_db: " << mMysqlInstallDbPath; - mMysqlCheckPath = XdgBaseDirs::findExecutableFile(QStringLiteral("mysqlcheck"), mysqldSearchPath); + mMysqlCheckPath = QStandardPaths::findExecutable(QStringLiteral("mysqlcheck"), mysqldSearchPath); qCDebug(AKONADISERVER_LOG) << "Found mysqlcheck: " << mMysqlCheckPath; mInternalServer = settings.value(QStringLiteral("QMYSQL/StartServer"), defaultInternalServer).toBool(); @@ -196,8 +196,8 @@ #endif // generate config file - const QString globalConfig = XdgBaseDirs::findResourceFile("config", QStringLiteral("akonadi/mysql-global.conf")); - const QString localConfig = XdgBaseDirs::findResourceFile("config", QStringLiteral("akonadi/mysql-local.conf")); + const QString globalConfig = StandardDirs::locateResourceFile("config", QStringLiteral("mysql-global.conf")); + const QString localConfig = StandardDirs::locateResourceFile("config", QStringLiteral("mysql-local.conf")); const QString actualConfig = StandardDirs::saveDir("data") + QLatin1String("/mysql.conf"); if (globalConfig.isEmpty()) { qCCritical(AKONADISERVER_LOG) << "Did not find MySQL server default configuration (mysql-global.conf)"; @@ -357,8 +357,8 @@ } // first run, some MySQL versions need a mysql_install_db run for that - const QString confFile = XdgBaseDirs::findResourceFile("config", QStringLiteral("akonadi/mysql-global.conf")); - if (QDir(dataDir).entryList(QDir::NoDotAndDotDot | QDir::AllEntries).isEmpty() && !mMysqlInstallDbPath.isEmpty()) { + const QString confFile = StandardDirs::locateResourceFile("config", QStringLiteral("akonadi/mysql-global.conf")); + if (QDir(dataDir).entryList(QDir::NoDotAndDotDot | QDir::AllEntries).isEmpty()) { if (isMariaDB) { initializeMariaDBDatabase(confFile, dataDir); } else if (localVersion >= MYSQL_VERSION_CHECK(5, 7, 6)) { @@ -564,6 +564,13 @@ bool DbConfigMysql::initializeMariaDBDatabase(const QString &confFile, const QString &dataDir) const { + // KDE Neon (and possible others) don't ship mysql_install_db, but it seems + // that MariaDB can initialize itself automatically on first start, it only + // needs that the datadir directory exists + if (mMysqlInstallDbPath.isEmpty()) { + return QDir().mkpath(dataDir); + } + QFileInfo fi(mMysqlInstallDbPath); QDir dir = fi.dir(); dir.cdUp(); @@ -591,6 +598,12 @@ bool DbConfigMysql::initializeMySQLDatabase(const QString &confFile, const QString &dataDir) const { + // On FreeBSD MySQL 5.6 is also installed without mysql_install_db, so this + // might do the trick there as well. + if (mMysqlInstallDbPath.isEmpty()) { + return QDir().mkpath(dataDir); + } + QFileInfo fi(mMysqlInstallDbPath); QDir dir = fi.dir(); dir.cdUp(); diff --git a/src/server/storage/dbconfigpostgresql.cpp b/src/server/storage/dbconfigpostgresql.cpp --- a/src/server/storage/dbconfigpostgresql.cpp +++ b/src/server/storage/dbconfigpostgresql.cpp @@ -21,14 +21,14 @@ #include "utils.h" #include "akonadiserver_debug.h" -#include #include #include #include #include #include #include +#include #include #ifdef HAVE_UNISTD_H @@ -96,9 +96,8 @@ } } postgresSearchPath.append(postgresVersionedSearchPaths); - - defaultServerPath = XdgBaseDirs::findExecutableFile(QStringLiteral("pg_ctl"), postgresSearchPath); - defaultInitDbPath = XdgBaseDirs::findExecutableFile(QStringLiteral("initdb"), postgresSearchPath); + defaultServerPath = QStandardPaths::findExecutable(QStringLiteral("pg_ctl"), postgresSearchPath); + defaultInitDbPath = QStandardPaths::findExecutable(QStringLiteral("initdb"), postgresSearchPath); defaultHostName = Utils::preferredSocketDirectory(StandardDirs::saveDir("data", QStringLiteral("db_misc"))); defaultPgData = StandardDirs::saveDir("data", QStringLiteral("db_data")); } diff --git a/src/server/storage/dbconfigsqlite.cpp b/src/server/storage/dbconfigsqlite.cpp --- a/src/server/storage/dbconfigsqlite.cpp +++ b/src/server/storage/dbconfigsqlite.cpp @@ -21,7 +21,6 @@ #include "utils.h" #include "akonadiserver_debug.h" -#include #include #include @@ -35,10 +34,12 @@ static QString dataDir() { QString akonadiHomeDir = StandardDirs::saveDir("data"); - if (akonadiHomeDir.isEmpty()) { - qCCritical(AKONADISERVER_LOG) << "Unable to create directory 'akonadi' in " << XdgBaseDirs::homePath("data") - << "during database initialization"; - return QString(); + if (!QDir(akonadiHomeDir).exists()) { + if (!QDir().mkpath(akonadiHomeDir)) { + qCCritical(AKONADISERVER_LOG) << "Unable to create" << akonadiHomeDir + << "during database initialization"; + return QString(); + } } akonadiHomeDir += QDir::separator(); diff --git a/src/server/storage/itemretrievaljob.h b/src/server/storage/itemretrievaljob.h --- a/src/server/storage/itemretrievaljob.h +++ b/src/server/storage/itemretrievaljob.h @@ -37,7 +37,7 @@ Q_OBJECT public: AbstractItemRetrievalJob(ItemRetrievalRequest *req, QObject *parent); - virtual ~AbstractItemRetrievalJob(); + ~AbstractItemRetrievalJob() override; virtual void start() = 0; virtual void kill() = 0; diff --git a/src/server/storage/itemretrievalmanager.h b/src/server/storage/itemretrievalmanager.h --- a/src/server/storage/itemretrievalmanager.h +++ b/src/server/storage/itemretrievalmanager.h @@ -56,7 +56,7 @@ public: explicit ItemRetrievalManager(QObject *parent = nullptr); explicit ItemRetrievalManager(AbstractItemRetrievalJobFactory *factory, QObject *parent = nullptr); - ~ItemRetrievalManager(); + ~ItemRetrievalManager() override; /** * Added for convenience. ItemRetrievalManager takes ownership over the diff --git a/src/server/storage/itemretriever.cpp b/src/server/storage/itemretriever.cpp --- a/src/server/storage/itemretriever.cpp +++ b/src/server/storage/itemretriever.cpp @@ -356,7 +356,7 @@ try { // Request is deleted inside ItemRetrievalManager, so we need to take // a copy here - const auto ids = request->ids; + //const auto ids = request->ids; ItemRetrievalManager::instance()->requestItemDelivery(request); } catch (const ItemRetrieverException &e) { qCCritical(AKONADISERVER_LOG) << e.type() << ": " << e.what(); @@ -366,7 +366,7 @@ ++it; } if (!requests.isEmpty()) { - if (eventLoop.exec(QEventLoop::ExcludeSocketNotifiers)) { + if (eventLoop.exec()) { return false; } } @@ -408,7 +408,7 @@ } if (!qb.exec()) { - mLastError = "Unable to query parts."; + mLastError = QByteArrayLiteral("Unable to query parts."); throw ItemRetrieverException(mLastError); } diff --git a/src/server/storage/notificationcollector.h b/src/server/storage/notificationcollector.h --- a/src/server/storage/notificationcollector.h +++ b/src/server/storage/notificationcollector.h @@ -41,17 +41,9 @@ them after the current transaction has been successfully committed. Where possible, notifications are compressed. */ -class NotificationCollector : public QObject +class NotificationCollector { - Q_OBJECT - public: - /** - Create a new notification collector that is not attached to - a DataStore and just collects notifications until you emit them manually. - */ - explicit NotificationCollector(QObject *parent = nullptr); - /** Create a new notification collector for the given DataStore @p db. @param db The datastore using this notification collector. @@ -61,7 +53,7 @@ /** Destroys this notification collector. */ - ~NotificationCollector(); + virtual ~NotificationCollector() = default; /** Sets the identifier of the session causing the changes. @@ -219,9 +211,6 @@ */ void dispatchNotifications(); -Q_SIGNALS: - void notify(const Akonadi::Protocol::ChangeNotificationList &msgs); - private: void itemNotification(Protocol::ItemChangeNotification::Operation op, const PimItem::List &items, @@ -256,13 +245,15 @@ void dispatchNotification(const Protocol::ChangeNotificationPtr &msg); void clear(); -private Q_SLOTS: - void transactionCommitted(); - void transactionRolledBack(); + void completeNotification(const Protocol::ChangeNotificationPtr &msg); + +protected: + virtual void notify(Protocol::ChangeNotificationList ntfs); private: DataStore *mDb; QByteArray mSessionId; + bool mIgnoreTransactions = false; Protocol::ChangeNotificationList mNotifications; }; diff --git a/src/server/storage/notificationcollector.cpp b/src/server/storage/notificationcollector.cpp --- a/src/server/storage/notificationcollector.cpp +++ b/src/server/storage/notificationcollector.cpp @@ -27,27 +27,33 @@ #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 "akonadiserver_debug.h" + +#include using namespace Akonadi; using namespace Akonadi::Server; -NotificationCollector::NotificationCollector(QObject *parent) - : QObject(parent) - , mDb(nullptr) -{ -} - NotificationCollector::NotificationCollector(DataStore *db) - : QObject(db) - , mDb(db) -{ - connect(db, &DataStore::transactionCommitted, this, &NotificationCollector::transactionCommitted); - connect(db, &DataStore::transactionRolledBack, this, &NotificationCollector::transactionRolledBack); -} - -NotificationCollector::~NotificationCollector() -{ + : 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, @@ -231,16 +237,6 @@ relationNotification(Protocol::RelationChangeNotification::Remove, relation); } -void NotificationCollector::transactionCommitted() -{ - dispatchNotifications(); -} - -void NotificationCollector::transactionRolledBack() -{ - clear(); -} - void NotificationCollector::clear() { mNotifications.clear(); @@ -317,14 +313,28 @@ 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.value().size()); - Q_FOREACH (const PimItem &item, iter.value()) { - items.push_back({ item.id(), item.remoteId(), item.remoteRevision(), item.mimeType().name() }); + 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()); @@ -334,11 +344,6 @@ dispatchNotification(copy); } - QVector ntfItems; - ntfItems.reserve(items.size()); - Q_FOREACH (const PimItem &item, items) { - ntfItems.push_back({ item.id(), item.remoteId(), item.remoteRevision(), item.mimeType().name() }); - } msg->setItems(ntfItems); Collection col; @@ -379,14 +384,59 @@ auto msg = Protocol::CollectionChangeNotificationPtr::create(); msg->setOperation(op); msg->setSessionId(mSessionId); - msg->setId(collection.id()); - msg->setRemoteId(collection.remoteId()); - msg->setRemoteRevision(collection.remoteRevision()); 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) << "Failed to obtain collection attributes!"; + } + 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"); } @@ -403,15 +453,43 @@ void NotificationCollector::tagNotification(Protocol::TagChangeNotification::Operation op, const Tag &tag, const QByteArray &resource, - const QString &remoteId - ) + const QString &remoteId) { auto msg = Protocol::TagChangeNotificationPtr::create(); msg->setOperation(op); msg->setSessionId(mSessionId); msg->setResource(resource); - msg->setId(tag.id()); - msg->setRemoteId(remoteId); + auto msgTag = HandlerHelper::fetchTagsResponse(tag, false); + 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()), false); + } + + 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) << "Failed to obtain tag attributes!"; + } + 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); } @@ -422,14 +500,80 @@ auto msg = Protocol::RelationChangeNotificationPtr::create(); msg->setOperation(op); msg->setSessionId(mSessionId); - msg->setLeftItem(relation.leftId()); - msg->setRightItem(relation.rightId()); - msg->setRemoteId(relation.remoteId()); - msg->setType(relation.relationType().name()); + 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) << "FetchHelper requested 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 (allHaveRID || msg->operation() != Protocol::ItemChangeNotification::Add) { + + // Prevent transactions inside FetchHelper to recursively call our slot + QScopedValueRollback ignoreTransactions(mIgnoreTransactions); + mIgnoreTransactions = true; + CommandContext context; + auto scope = fetchScope->toFetchScope(); + scope.setFetch(Protocol::ItemFetchScope::CacheOnly); + FetchHelper helper(Connection::self(), &context, Scope(ids), scope); + 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) << "Failed 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()) { @@ -439,14 +583,26 @@ mNotifications.append(msg); } } else { - Q_EMIT notify({ msg }); + completeNotification(msg); + notify({msg}); } } void NotificationCollector::dispatchNotifications() { if (!mNotifications.isEmpty()) { - Q_EMIT notify(mNotifications); + 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)); + } +} diff --git a/src/server/storage/partstreamer.h b/src/server/storage/partstreamer.h --- a/src/server/storage/partstreamer.h +++ b/src/server/storage/partstreamer.h @@ -20,7 +20,7 @@ #ifndef AKONADI_SERVER_PARTSTREAMER_H #define AKONADI_SERVER_PARTSTREAMER_H -#include +#include #include "entities.h" @@ -41,22 +41,17 @@ class Part; class Connection; -class PartStreamer : public QObject +class PartStreamer { - Q_OBJECT - public: - explicit PartStreamer(Connection *connection, const PimItem &pimItem, QObject *parent = nullptr); + explicit PartStreamer(Connection *connection, const PimItem &pimItem); ~PartStreamer(); bool stream(bool checkExists, const QByteArray &partName, qint64 &partSize, bool *changed = nullptr); bool streamAttribute(bool checkExists, const QByteArray &partName, const QByteArray &value, bool *changed = nullptr); QString error() const; -Q_SIGNALS: - void responseAvailable(const Protocol::CommandPtr &response); - private: bool streamPayload(Part &part, const QByteArray &partName); bool streamPayloadToFile(Part &part, const Protocol::PartMetaData &metaPart); diff --git a/src/server/storage/partstreamer.cpp b/src/server/storage/partstreamer.cpp --- a/src/server/storage/partstreamer.cpp +++ b/src/server/storage/partstreamer.cpp @@ -42,9 +42,8 @@ using namespace Akonadi::Server; PartStreamer::PartStreamer(Connection *connection, - const PimItem &pimItem, QObject *parent) - : QObject(parent) - , mConnection(connection) + const PimItem &pimItem) + : mConnection(connection) , mItem(pimItem) { // Make sure the file_db_data path exists @@ -63,10 +62,10 @@ Protocol::PartMetaData PartStreamer::requestPartMetaData(const QByteArray &partName) { { - auto resp = Protocol::StreamPayloadCommandPtr::create(); - resp->setPayloadName(partName); - resp->setRequest(Protocol::StreamPayloadCommand::MetaData); - Q_EMIT responseAvailable(resp); + Protocol::StreamPayloadCommand resp; + resp.setPayloadName(partName); + resp.setRequest(Protocol::StreamPayloadCommand::MetaData); + mConnection->sendResponse(std::move(resp)); } const auto cmd = mConnection->readCommand(); @@ -115,10 +114,10 @@ // Request the actual data { - auto resp = Protocol::StreamPayloadCommandPtr::create(); - resp->setPayloadName(metaPart.name()); - resp->setRequest(Protocol::StreamPayloadCommand::Data); - Q_EMIT responseAvailable(resp); + Protocol::StreamPayloadCommand resp; + resp.setPayloadName(metaPart.name()); + resp.setRequest(Protocol::StreamPayloadCommand::Data); + mConnection->sendResponse(std::move(resp)); } const auto cmd = mConnection->readCommand(); @@ -211,11 +210,11 @@ } { - auto cmd = Protocol::StreamPayloadCommandPtr::create(); - cmd->setPayloadName(metaPart.name()); - cmd->setRequest(Protocol::StreamPayloadCommand::Data); - cmd->setDestination(QString::fromUtf8(filename)); - Q_EMIT responseAvailable(cmd); + Protocol::StreamPayloadCommand cmd; + cmd.setPayloadName(metaPart.name()); + cmd.setRequest(Protocol::StreamPayloadCommand::Data); + cmd.setDestination(QString::fromUtf8(filename)); + mConnection->sendResponse(std::move(cmd)); } const auto cmd = mConnection->readCommand(); @@ -226,7 +225,7 @@ return false; } - QFile file(ExternalPartStorage::resolveAbsolutePath(filename), this); + QFile file(ExternalPartStorage::resolveAbsolutePath(filename)); if (!file.exists()) { mError = QStringLiteral("External payload file does not exist"); qCCritical(AKONADISERVER_LOG) << mError; @@ -256,10 +255,10 @@ } { - auto cmd = Protocol::StreamPayloadCommandPtr::create(); - cmd->setPayloadName(metaPart.name()); - cmd->setRequest(Protocol::StreamPayloadCommand::Data); - Q_EMIT responseAvailable(cmd); + Protocol::StreamPayloadCommand cmd; + cmd.setPayloadName(metaPart.name()); + cmd.setRequest(Protocol::StreamPayloadCommand::Data); + mConnection->sendResponse(std::move(cmd)); } const auto cmd = mConnection->readCommand(); @@ -361,7 +360,7 @@ } bool ok = streamPayload(part, partName); - if (ok && mCheckChanged) { + if (changed && ok && mCheckChanged) { *changed = mDataChanged; } diff --git a/src/server/storage/querybuilder.cpp b/src/server/storage/querybuilder.cpp --- a/src/server/storage/querybuilder.cpp +++ b/src/server/storage/querybuilder.cpp @@ -101,6 +101,7 @@ : mTable(table) #ifndef QUERYBUILDER_UNITTEST , mDatabaseType(DbType::type(DataStore::self()->database())) + , mQuery(DataStore::self()->database()) #else , mDatabaseType(DbType::Unknown) #endif @@ -356,7 +357,7 @@ if (QueryCache::contains(statement)) { mQuery = QueryCache::query(statement); } else { - mQuery = QSqlQuery(DataStore::self()->database()); + mQuery.clear(); mQuery.prepare(statement); QueryCache::insert(statement, mQuery); } diff --git a/src/server/storagejanitor.h b/src/server/storagejanitor.h --- a/src/server/storagejanitor.h +++ b/src/server/storagejanitor.h @@ -41,7 +41,7 @@ public: explicit StorageJanitor(QObject *parent = nullptr); - ~StorageJanitor(); + ~StorageJanitor() override; public Q_SLOTS: /** Triggers a consistency check of the internal storage. */ diff --git a/src/server/storagejanitor.cpp b/src/server/storagejanitor.cpp --- a/src/server/storagejanitor.cpp +++ b/src/server/storagejanitor.cpp @@ -84,7 +84,7 @@ // Make sure all childrens are deleted within context of this thread qDeleteAll(children()); - qDebug() << "chainup()"; + qCDebug(AKONADISERVER_LOG) << "chainup()"; AkThread::quit(); } @@ -243,7 +243,7 @@ for (const Resource &resource : orphanResources) { resourceNames.append(resource.name()); } - inform(QStringLiteral("Found %1 orphan resources: %2").arg(orphanResources.size()). arg(resourceNames.join(QLatin1Char(',')))); + inform(QStringLiteral("Found %1 orphan resources: %2").arg(orphanResourcesSize). arg(resourceNames.join(QLatin1Char(',')))); for (const QString &resourceName : qAsConst(resourceNames)) { inform(QStringLiteral("Removing resource %1").arg(resourceName)); ResourceManager::self()->removeResourceInstance(resourceName); @@ -410,7 +410,7 @@ while (it.hasNext()) { existingFiles.insert(it.next()); } - existingFiles.remove(dataDir + QDir::separator() + QLatin1String(".")); + existingFiles.remove(dataDir + QDir::separator() + QLatin1Char('.')); existingFiles.remove(dataDir + QDir::separator() + QLatin1String("..")); inform(QLatin1Literal("Found ") + QString::number(existingFiles.size()) + QLatin1Literal(" external files.")); @@ -426,16 +426,22 @@ return; } while (qb.query().next()) { - QString partPath = ExternalPartStorage::resolveAbsolutePath(qb.query().value(0).toByteArray()); + const auto filename = qb.query().value(0).toByteArray(); const Entity::Id pimItemId = qb.query().value(1).value(); - const Entity::Id id = qb.query().value(2).value(); + const Entity::Id partId = qb.query().value(2).value(); + QString partPath; + if (!filename.isEmpty()) { + partPath = ExternalPartStorage::resolveAbsolutePath(filename); + } else { + partPath = ExternalPartStorage::resolveAbsolutePath(ExternalPartStorage::nameForPartId(partId)); + } if (existingFiles.contains(partPath)) { usedFiles.insert(partPath); } else { - inform(QLatin1Literal("Cleaning up missing external file: ") + partPath + QLatin1Literal(" for item: ") + QString::number(pimItemId) + QLatin1Literal(" on part: ") + QString::number(id)); + inform(QLatin1Literal("Cleaning up missing external file: ") + partPath + QLatin1Literal(" for item: ") + QString::number(pimItemId) + QLatin1Literal(" on part: ") + QString::number(partId)); Part part; - part.setId(id); + part.setId(partId); part.setPimItemId(pimItemId); part.setData(QByteArray()); part.setDatasize(0); @@ -487,7 +493,8 @@ } const PimItem::List ridLessItems = iqb1.result(); for (const PimItem &item : ridLessItems) { - inform(QLatin1Literal("Item \"") + QString::number(item.id()) + QLatin1Literal("\" has no RID.")); + inform(QLatin1Literal("Item \"") + QString::number(item.id()) + QLatin1Literal("\" in collection \"") + + QString::number(item.collectionId()) + QLatin1Literal("\" has no RID.")); } inform(QLatin1Literal("Found ") + QString::number(ridLessItems.size()) + QLatin1Literal(" items without RID.")); diff --git a/src/server/tracer.h b/src/server/tracer.h --- a/src/server/tracer.h +++ b/src/server/tracer.h @@ -22,13 +22,23 @@ #include #include +#include +#include #include "tracerinterface.h" +#include class QSettings; namespace Akonadi { + +namespace Protocol +{ +class Command; +using CommandPtr = QSharedPointer; +} + namespace Server { @@ -51,7 +61,24 @@ /** * Destroys the global tracer instance. */ - virtual ~Tracer(); + ~Tracer() override; + + template + typename std::enable_if::value>::type + connectionOutput(const QString &identifier, qint64 tag, const T &cmd) { + QByteArray msg; + if (mTracerBackend->connectionFormat() == TracerInterface::Json) { + QJsonObject json; + json[QStringLiteral("tag")] = tag; + cmd.toJson(json); + QJsonDocument doc(json); + + msg = doc.toJson(QJsonDocument::Indented); + } else { + msg = QByteArray::number(tag) + ' ' + Protocol::debugString(cmd).toUtf8(); + } + connectionOutput(identifier, msg); + } public Q_SLOTS: /** @@ -84,6 +111,8 @@ */ void connectionInput(const QString &identifier, const QByteArray &msg) override; + void connectionInput(const QString &identifier, qint64 tag, const Protocol::CommandPtr &cmd); + /** * This method is called whenever the akonadi server sends some data out to a client. * @@ -93,6 +122,8 @@ */ void connectionOutput(const QString &identifier, const QByteArray &msg) override; + void connectionOutput(const QString &identifier, qint64 tag, const Protocol::CommandPtr &cmd); + /** * This method is called whenever a dbus signal is emitted on the bus. * diff --git a/src/server/tracer.cpp b/src/server/tracer.cpp --- a/src/server/tracer.cpp +++ b/src/server/tracer.cpp @@ -32,6 +32,7 @@ // #define DEFAULT_TRACER QLatin1String( "dbus" ) #define DEFAULT_TRACER QStringLiteral( "null" ) +using namespace Akonadi; using namespace Akonadi::Server; Tracer *Tracer::mSelf = nullptr; @@ -83,13 +84,47 @@ mMutex.unlock(); } +void Akonadi::Server::Tracer::connectionInput(const QString& identifier, qint64 tag, const Protocol::CommandPtr &cmd) +{ + QByteArray msg; + if (mTracerBackend->connectionFormat() == TracerInterface::Json) { + QJsonObject json; + json[QStringLiteral("tag")] = tag; + Akonadi::Protocol::toJson(cmd.data(), json); + + QJsonDocument doc(json); + + msg = doc.toJson(QJsonDocument::Indented); + } else { + msg = QByteArray::number(tag) + ' ' + Protocol::debugString(cmd).toUtf8(); + } + connectionInput(identifier, msg); +} + void Tracer::connectionOutput(const QString &identifier, const QByteArray &msg) { mMutex.lock(); mTracerBackend->connectionOutput(identifier, msg); mMutex.unlock(); } +void Tracer::connectionOutput(const QString &identifier, qint64 tag, const Protocol::CommandPtr &cmd) +{ + QByteArray msg; + if (mTracerBackend->connectionFormat() == TracerInterface::Json) { + QJsonObject json; + json[QStringLiteral("tag")] = tag; + Protocol::toJson(cmd.data(), json); + QJsonDocument doc(json); + + msg = doc.toJson(QJsonDocument::Indented); + } else { + msg = QByteArray::number(tag) + ' ' + Protocol::debugString(cmd).toUtf8(); + } + connectionOutput(identifier, msg); +} + + void Tracer::signal(const QString &signalName, const QString &msg) { mMutex.lock(); diff --git a/src/server/tracerinterface.h b/src/server/tracerinterface.h --- a/src/server/tracerinterface.h +++ b/src/server/tracerinterface.h @@ -40,6 +40,11 @@ class TracerInterface { public: + enum ConnectionFormat { + DebugString, + Json + }; + virtual ~TracerInterface() { } @@ -100,6 +105,8 @@ * This method is called whenever a component wants to output an error. */ virtual void error(const QString &componentName, const QString &msg) = 0; + + virtual ConnectionFormat connectionFormat() const {return DebugString;} }; } // namespace Server diff --git a/src/server/utils.cpp b/src/server/utils.cpp --- a/src/server/utils.cpp +++ b/src/server/utils.cpp @@ -23,7 +23,6 @@ #include "akonadiserver_debug.h" #include -#include #include #include @@ -54,7 +53,7 @@ QString Utils::preferredSocketDirectory(const QString &defaultDirectory) { - const QString serverConfigFile = StandardDirs::serverConfigFile(XdgBaseDirs::ReadWrite); + const QString serverConfigFile = StandardDirs::serverConfigFile(StandardDirs::ReadWrite); const QSettings serverSettings(serverConfigFile, QSettings::IniFormat); #if defined(Q_OS_WIN) diff --git a/src/shared/CMakeLists.txt b/src/shared/CMakeLists.txt --- a/src/shared/CMakeLists.txt +++ b/src/shared/CMakeLists.txt @@ -1,6 +1,7 @@ set(akonadi_shared_srcs akapplication.cpp akdebug.cpp + akremotelog.cpp ) add_library(akonadi_shared STATIC ${akonadi_shared_srcs}) diff --git a/src/shared/akapplication.cpp b/src/shared/akapplication.cpp --- a/src/shared/akapplication.cpp +++ b/src/shared/akapplication.cpp @@ -19,6 +19,7 @@ #include "akapplication.h" #include "akdebug.h" +#include "akremotelog.h" #include #include @@ -59,6 +60,7 @@ void AkApplicationBase::init() { akInit(QString::fromLatin1(mArgv[0])); + akInitRemoteLog(); if (!QDBusConnection::sessionBus().isConnected()) { qFatal("D-Bus session bus is not available!"); diff --git a/src/shared/akdebug.cpp b/src/shared/akdebug.cpp --- a/src/shared/akdebug.cpp +++ b/src/shared/akdebug.cpp @@ -93,15 +93,21 @@ ~DebugPrivate() { + qInstallMessageHandler(origHandler); file.close(); } - QString errorLogFileName() const + static QString errorLogFileName(const QString &name) { return Akonadi::StandardDirs::saveDir("data") - + QDir::separator() - + name - + QLatin1String(".error"); + + QDir::separator() + + name + + QLatin1String(".error"); + } + + QString errorLogFileName() const + { + return errorLogFileName(name); } void log(QtMsgType type, const QMessageLogContext &context, const QString &msg) @@ -114,6 +120,7 @@ #endif const QByteArray buf = msg.toUtf8(); file.write(buf.constData(), buf.size()); + file.write("\n"); file.flush(); if (origHandler) { @@ -123,14 +130,17 @@ void setName(const QString &appName) { - // Keep only the executable name, e.g. akonadi_control - name = appName.mid(appName.lastIndexOf(QLatin1Char('/')) + 1); + name = appName; if (file.isOpen()) { file.close(); } + QFileInfo finfo(errorLogFileName()); + if (!finfo.absoluteDir().exists()) { + QDir().mkpath(finfo.absolutePath()); + } file.setFileName(errorLogFileName()); - file.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Unbuffered); + file.open(QIODevice::WriteOnly | QIODevice::Unbuffered); } void setOrigHandler(QtMessageHandler origHandler_) @@ -185,26 +195,35 @@ void akInit(const QString &appName) { KCrash::initialize(); - sInstance()->setName(appName); - QFileInfo infoOld(sInstance()->errorLogFileName() + QLatin1String(".old")); + const QString name = QFileInfo(appName).fileName(); + const auto errorLogFile = DebugPrivate::errorLogFileName(name); + QFileInfo infoOld(errorLogFile + QLatin1String(".old")); if (infoOld.exists()) { QFile fileOld(infoOld.absoluteFilePath()); const bool success = fileOld.remove(); if (!success) { - qFatal("Cannot remove old log file - running on a readonly filesystem maybe?"); + qFatal("Cannot remove old log file '%s': %s", + qUtf8Printable(fileOld.fileName()), + qUtf8Printable(fileOld.errorString())); } } - QFileInfo info(sInstance()->errorLogFileName()); + + QFileInfo info(errorLogFile); if (info.exists()) { QFile file(info.absoluteFilePath()); - const bool success = file.rename(sInstance()->errorLogFileName() + QLatin1String(".old")); + const QString oldName = errorLogFile + QLatin1String(".old"); + const bool success = file.copy(oldName); if (!success) { - qFatal("Cannot rename log file - running on a readonly filesystem maybe?"); + qFatal("Cannot rename log file '%s' to '%s': %s", + qUtf8Printable(file.fileName()), + qUtf8Printable(oldName), + qUtf8Printable(file.errorString())); } } QtMessageHandler origHandler = qInstallMessageHandler(akMessageHandler); + sInstance()->setName(name); sInstance()->setOrigHandler(origHandler); } diff --git a/src/server/handler/tagstore.h b/src/shared/akremotelog.h copy from src/server/handler/tagstore.h copy to src/shared/akremotelog.h --- a/src/server/handler/tagstore.h +++ b/src/shared/akremotelog.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2014 Daniel Vrátil + Copyright (c) 2018 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 @@ -17,25 +17,9 @@ 02110-1301, USA. */ -#ifndef AKONADI_TAGSTORE_H -#define AKONADI_TAGSTORE_H +#ifndef AKREMOTELOG_H_ +#define AKREMOTELOG_H_ -#include "handler.h" +void akInitRemoteLog(); -namespace Akonadi -{ -namespace Server -{ - -class TagStore : public Handler -{ - Q_OBJECT - -public: - bool parseStream() override; -}; - -} // namespace Server -} // namespace Akonadi - -#endif // AKONADI_TAGSTORE_H +#endif diff --git a/src/shared/akremotelog.cpp b/src/shared/akremotelog.cpp new file mode 100644 --- /dev/null +++ b/src/shared/akremotelog.cpp @@ -0,0 +1,221 @@ +/* + Copyright (c) 2018 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 "akremotelog.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define AKONADICONSOLE_SERVICE "org.kde.akonadiconsole" +#define AKONADICONSOLE_LOGGER_PATH "/logger" +#define AKONADICONSOLE_LOGGER_INTERFACE "org.kde.akonadiconsole.logger" + +namespace { + +class RemoteLogger : public QObject +{ + Q_OBJECT +public: + explicit RemoteLogger() + : mWatcher(akonadiConsoleServiceName(), QDBusConnection::sessionBus(), + QDBusServiceWatcher::WatchForRegistration | QDBusServiceWatcher::WatchForUnregistration) + { + connect(qApp, &QCoreApplication::aboutToQuit, + this, &RemoteLogger::deleteLater); + + sInstance = this; + + // Don't do remote logging for Akonadi Console because it deadlocks it + if (QCoreApplication::applicationName() == QLatin1String("akonadiconsole")) { + return; + } + + connect(&mWatcher, &QDBusServiceWatcher::serviceRegistered, + this, &RemoteLogger::serviceRegistered); + connect(&mWatcher, &QDBusServiceWatcher::serviceUnregistered, + this, &RemoteLogger::serviceUnregistered); + + mOldHandler = qInstallMessageHandler(dbusLogger); + } + + ~RemoteLogger() + { + sInstance = nullptr; + + QLoggingCategory::installFilter(mOldFilter); + qInstallMessageHandler(mOldHandler); + + mEnabled = false; + delete mAkonadiConsoleInterface; + } + + static RemoteLogger *self() + { + return sInstance; + } + +private Q_SLOTS: + void serviceRegistered(const QString &service) + { + delete mAkonadiConsoleInterface; + mAkonadiConsoleInterface = new QDBusInterface(service, + QStringLiteral(AKONADICONSOLE_LOGGER_PATH), + QStringLiteral(AKONADICONSOLE_LOGGER_INTERFACE), + QDBusConnection::sessionBus(), this); + if (!mAkonadiConsoleInterface->isValid()) { + delete mAkonadiConsoleInterface; + return; + } + + connect(mAkonadiConsoleInterface, SIGNAL(enabledChanged(bool)), + this, SLOT(onAkonadiConsoleLoggingEnabled(bool))); + + QTimer::singleShot(0, this, [this]() { + auto watcher = new QDBusPendingCallWatcher(mAkonadiConsoleInterface->asyncCall(QStringLiteral("enabled"))); + connect(watcher, &QDBusPendingCallWatcher::finished, + this, [this](QDBusPendingCallWatcher *watcher) { + watcher->deleteLater(); + QDBusPendingReply reply = *watcher; + if (reply.isError()) { + return; + } + onAkonadiConsoleLoggingEnabled(reply.argumentAt<0>()); + }); + }); + } + + void serviceUnregistered(const QString &) + { + onAkonadiConsoleLoggingEnabled(false); + delete mAkonadiConsoleInterface; + mAkonadiConsoleInterface = nullptr; + } + + void onAkonadiConsoleLoggingEnabled(bool enabled) + { + if (mEnabled == enabled) { + return; + } + + mEnabled = enabled; + if (mEnabled) { + // FIXME: Qt calls our categoryFilter from installFilter() but at that + // point we cannot refer to mOldFilter yet (as we only receive it after + // this call returns. So we set our category filter twice: once to get + // the original Qt filter and second time to force our category filter + // to be called when we already know the old filter. + mOldFilter = QLoggingCategory::installFilter(categoryFilter); + QLoggingCategory::installFilter(categoryFilter); + } else { + QLoggingCategory::installFilter(mOldFilter); + mOldFilter = nullptr; + } + } + +private: + QString akonadiConsoleServiceName() + { + QString service = QStringLiteral(AKONADICONSOLE_SERVICE); + if (Akonadi::Instance::hasIdentifier()) { + service += QStringLiteral("-%1").arg(Akonadi::Instance::identifier()); + } + return service; + } + + static void categoryFilter(QLoggingCategory *cat) + { + const auto that = self(); + if (!that) { + return; + } + + if (qstrncmp(cat->categoryName(), "org.kde.pim.", 12) == 0) { + cat->setEnabled(QtDebugMsg, true); + cat->setEnabled(QtInfoMsg, true); + cat->setEnabled(QtWarningMsg, true); + cat->setEnabled(QtCriticalMsg, true); + } else if (that->mOldFilter) { + that->mOldFilter(cat); + } + } + + static void dbusLogger(QtMsgType type, const QMessageLogContext &ctx, const QString &msg) + { + const auto that = self(); + if (!that) { + return; + } + + // Log to previous logger + that->mOldHandler(type, ctx, msg); + + if (that->mEnabled) { + that->mAkonadiConsoleInterface->asyncCallWithArgumentList( + QStringLiteral("message"), + QList{ + QDateTime::currentMSecsSinceEpoch(), qAppName(), + qApp->applicationPid(), static_cast(type), + QString::fromUtf8(ctx.category), QString::fromUtf8(ctx.file), + QString::fromUtf8(ctx.function), ctx.line, ctx.version, msg }); + } + } + +private: + QDBusServiceWatcher mWatcher; + QLoggingCategory::CategoryFilter mOldFilter = nullptr; + QtMessageHandler mOldHandler = nullptr; + QDBusInterface *mAkonadiConsoleInterface = nullptr; + bool mEnabled = false; + + static RemoteLogger *sInstance; +}; + +RemoteLogger *RemoteLogger::sInstance = nullptr; + +} + +void akInitRemoteLog() +{ + Q_ASSERT(qApp->thread() == QThread::currentThread()); + + if (!RemoteLogger::self()) { + new RemoteLogger(); + } +} + + +#include "akremotelog.moc" + diff --git a/src/shared/aktest.h b/src/shared/aktest.h --- a/src/shared/aktest.h +++ b/src/shared/aktest.h @@ -24,14 +24,17 @@ #include "akapplication.h" #include +#include + #include #define AKTEST_MAIN(TestObject) \ int main(int argc, char **argv) \ { \ qputenv("XDG_DATA_HOME", ".local-unit-test/share"); \ qputenv("XDG_CONFIG_HOME", ".config-unit-test"); \ AkCoreApplication app(argc, argv); \ + KCrash::setDrKonqiEnabled(false); \ app.parseCommandLine(); \ TestObject tc; \ return QTest::qExec(&tc, argc, argv); \ @@ -42,6 +45,7 @@ { \ FakeAkonadiServer::instance(); \ AkCoreApplication app(argc, argv); \ + KCrash::setDrKonqiEnabled(false); \ app.addCommandLineOptions(QCommandLineOption( \ QLatin1String("no-cleanup"), QLatin1String("Don't clean up the temporary runtime environment"))); \ app.parseCommandLine(); \ diff --git a/src/core/vectorhelper.h b/src/shared/vectorhelper.h copy from src/core/vectorhelper.h copy to src/shared/vectorhelper.h --- a/src/core/vectorhelper.h +++ b/src/shared/vectorhelper.h @@ -1,5 +1,5 @@ /* - Copyright (C) 2015-2017 Laurent Montel + Copyright (C) 2015-2018 Laurent Montel 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 @@ -23,15 +23,17 @@ #include #include +#include + namespace Akonadi { template class Container> QVector valuesToVector(const Container &container) { QVector values; values.reserve(container.size()); - for (const Value &value : container) { - values << value; + for (const auto &value : container) { + values.append(value); } return values; } @@ -41,12 +43,23 @@ { QSet set; set.reserve(container.size()); - for (const T &value : container) { + for (const auto &value : container) { set.insert(value); } return set; } +template class Container> +QVector setToVector(const Container &container) +{ + QVector values; + values.reserve(container.size()); + for (const auto &value : container) { + values.append(value); + } + return values; } +} // namespace Akonadi + #endif // VECTORHELPER_H diff --git a/src/widgets/CMakeLists.txt b/src/widgets/CMakeLists.txt --- a/src/widgets/CMakeLists.txt +++ b/src/widgets/CMakeLists.txt @@ -134,6 +134,7 @@ DESTINATION ${ECM_MKSPECS_INSTALL_DIR} ) + ######### Build and install QtDesigner plugin ############# if (KF5DesignerPlugin_FOUND) diff --git a/src/widgets/actionstatemanager.cpp b/src/widgets/actionstatemanager.cpp --- a/src/widgets/actionstatemanager.cpp +++ b/src/widgets/actionstatemanager.cpp @@ -74,7 +74,7 @@ mReceiver = object; } -void ActionStateManager::updateState(const Collection::List &collections, const Item::List &items) +void ActionStateManager::updateState(const Collection::List &collections, const Collection::List &favoriteCollections, const Item::List &items) { const int collectionCount = collections.count(); const bool singleCollectionSelected = (collectionCount == 1); @@ -86,7 +86,7 @@ const bool multipleItemsSelected = (itemCount > 1); const bool atLeastOneItemSelected = (singleItemSelected || multipleItemsSelected); - const bool listOfCollectionNotEmpty = collections.isEmpty() ? false : true; + const bool listOfCollectionNotEmpty = !collections.isEmpty(); bool canDeleteCollections = listOfCollectionNotEmpty; if (canDeleteCollections) { for (const Collection &collection : collections) { @@ -161,13 +161,6 @@ } } } - bool canRemoveFromFavoriteCollections = listOfCollectionNotEmpty; - for (const Collection &collection : collections) { - if (!isFavoriteCollection(collection)) { - canRemoveFromFavoriteCollections = false; - break; - } - } bool collectionsAreFolders = listOfCollectionNotEmpty; @@ -193,6 +186,12 @@ break; } } + for (const Collection &collection : favoriteCollections) { + if (collectionCanHaveItems(collection)) { + atLeastOneCollectionCanHaveItems = true; + break; + } + } const Collection collection = (!collections.isEmpty() ? collections.first() : Collection()); @@ -235,10 +234,10 @@ // favorite collections specific actions enableAction(StandardActionManager::AddToFavoriteCollections, canAddToFavoriteCollections); + const bool canRemoveFromFavoriteCollections = !favoriteCollections.isEmpty(); enableAction(StandardActionManager::RemoveFromFavoriteCollections, canRemoveFromFavoriteCollections); - enableAction(StandardActionManager::RenameFavoriteCollection, singleCollectionSelected && // we can rename only one collection at a time - isFavoriteCollection(collection)); // it must be a favorite collection already + enableAction(StandardActionManager::RenameFavoriteCollection, favoriteCollections.count() == 1); // we can rename only one collection at a time // resource specific actions int resourceCollectionCount = 0; diff --git a/src/widgets/actionstatemanager_p.h b/src/widgets/actionstatemanager_p.h --- a/src/widgets/actionstatemanager_p.h +++ b/src/widgets/actionstatemanager_p.h @@ -47,9 +47,12 @@ virtual ~ActionStateManager(); /** - * Updates the states according to the selected @p collections and @p items. + * Updates the states according to the selected collections and items. + * @param collections selected collections (from the folder tree) + * @param favoriteCollections selected collections (among the ones marked as favorites) + * @param items selected items */ - void updateState(const Collection::List &collections, const Item::List &items); + void updateState(const Collection::List &collections, const Collection::List &favoriteCollections, const Item::List &items); /** * Sets the @p receiver object that will actually update the states. diff --git a/src/widgets/agentactionmanager.h b/src/widgets/agentactionmanager.h --- a/src/widgets/agentactionmanager.h +++ b/src/widgets/agentactionmanager.h @@ -103,7 +103,7 @@ * connected to its default implementation provided by this class. * @param type action type */ - QAction *createAction(Type type); + Q_REQUIRED_RESULT QAction *createAction(Type type); /** * Convenience method to create all standard actions. @@ -114,7 +114,7 @@ /** * Returns the action of the given type, 0 if it has not been created (yet). */ - QAction *action(Type type) const; + Q_REQUIRED_RESULT QAction *action(Type type) const; /** * Sets whether the default implementation for the given action @p type @@ -133,7 +133,7 @@ * * @since 4.6 */ - Akonadi::AgentInstance::List selectedAgentInstances() const; + Q_REQUIRED_RESULT Akonadi::AgentInstance::List selectedAgentInstances() const; /** * Sets the @p text of the action @p type for the given @p context. diff --git a/src/widgets/agentactionmanager.cpp b/src/widgets/agentactionmanager.cpp --- a/src/widgets/agentactionmanager.cpp +++ b/src/widgets/agentactionmanager.cpp @@ -169,7 +169,7 @@ dlg->agentFilterProxyModel()->addCapabilityFilter(capability); } - if (dlg->exec() == QDialog::Accepted && dlg != nullptr) { + if (dlg->exec() == QDialog::Accepted) { const AgentType agentType = dlg->agentType(); if (agentType.isValid()) { @@ -319,7 +319,7 @@ void AgentActionManager::createAllActions() { for (int type = 0; type < LastType; ++type) { - createAction((Type)type); + createAction(static_cast(type)); } } diff --git a/src/widgets/agentinstancewidget.h b/src/widgets/agentinstancewidget.h --- a/src/widgets/agentinstancewidget.h +++ b/src/widgets/agentinstancewidget.h @@ -1,6 +1,6 @@ /* Copyright (c) 2006-2008 Tobias Koenig - Copyright (C) 2012-2017 Laurent Montel + Copyright (C) 2012-2018 Laurent Montel 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 @@ -85,25 +85,25 @@ * Returns the current agent instance or an invalid agent instance * if no agent instance is selected. */ - AgentInstance currentAgentInstance() const; + Q_REQUIRED_RESULT AgentInstance currentAgentInstance() const; /** * Returns the selected agent instances. * @since 4.5 */ - QVector selectedAgentInstances() const; + Q_REQUIRED_RESULT QVector selectedAgentInstances() const; /** * Returns the agent filter proxy model, use this to filter by * agent mimetype or capabilities. */ - AgentFilterProxyModel *agentFilterProxyModel() const; + Q_REQUIRED_RESULT AgentFilterProxyModel *agentFilterProxyModel() const; /** * Returns the view used in the widget. * @since 4.5 */ - QAbstractItemView *view() const; + Q_REQUIRED_RESULT QAbstractItemView *view() const; Q_SIGNALS: /** diff --git a/src/widgets/agenttypedialog.h b/src/widgets/agenttypedialog.h --- a/src/widgets/agenttypedialog.h +++ b/src/widgets/agenttypedialog.h @@ -66,20 +66,20 @@ /** * Destroys the agent type dialog. */ - ~AgentTypeDialog(); + ~AgentTypeDialog() override; /** * Returns the agent type that was selected by the user, * or an empty agent type object if no agent type has been selected. */ - AgentType agentType() const; + Q_REQUIRED_RESULT AgentType agentType() const; /** * Returns the agent filter proxy model that can be used * to filter the agent types that shall be shown in the * dialog. */ - AgentFilterProxyModel *agentFilterProxyModel() const; + Q_REQUIRED_RESULT AgentFilterProxyModel *agentFilterProxyModel() const; public Q_SLOTS: void done(int result) override; diff --git a/src/widgets/agenttypedialog.cpp b/src/widgets/agenttypedialog.cpp --- a/src/widgets/agenttypedialog.cpp +++ b/src/widgets/agenttypedialog.cpp @@ -36,8 +36,7 @@ { public: Private(AgentTypeDialog *qq) - : Widget(nullptr), - q(qq) + : q(qq) { } diff --git a/src/widgets/agenttypewidget.h b/src/widgets/agenttypewidget.h --- a/src/widgets/agenttypewidget.h +++ b/src/widgets/agenttypewidget.h @@ -71,13 +71,13 @@ * Returns the current agent type or an invalid agent type * if no agent type is selected. */ - AgentType currentAgentType() const; + Q_REQUIRED_RESULT AgentType currentAgentType() const; /** * Returns the agent filter proxy model, use this to filter by * agent mimetype or capabilities. */ - AgentFilterProxyModel *agentFilterProxyModel() const; + Q_REQUIRED_RESULT AgentFilterProxyModel *agentFilterProxyModel() const; Q_SIGNALS: /** @@ -98,8 +98,6 @@ //@cond PRIVATE class Private; Private *const d; - - Q_PRIVATE_SLOT(d, void currentAgentTypeChanged(const QModelIndex &, const QModelIndex &)) //@endcond }; diff --git a/src/widgets/agenttypewidget.cpp b/src/widgets/agenttypewidget.cpp --- a/src/widgets/agenttypewidget.cpp +++ b/src/widgets/agenttypewidget.cpp @@ -39,7 +39,7 @@ class AgentTypeWidgetDelegate : public QAbstractItemDelegate { public: - AgentTypeWidgetDelegate(QObject *parent = nullptr); + explicit AgentTypeWidgetDelegate(QObject *parent = nullptr); void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; diff --git a/src/widgets/autotests/CMakeLists.txt b/src/widgets/autotests/CMakeLists.txt new file mode 100644 diff --git a/src/widgets/cachepolicypage.h b/src/widgets/cachepolicypage.h --- a/src/widgets/cachepolicypage.h +++ b/src/widgets/cachepolicypage.h @@ -63,12 +63,12 @@ /** * Destroys the cache policy page. */ - ~CachePolicyPage(); + ~CachePolicyPage() override; /** * Checks if the cache policy page can actually handle the given @p collection. */ - bool canHandle(const Collection &collection) const override; + Q_REQUIRED_RESULT bool canHandle(const Collection &collection) const override; /** * Loads the page content from the given @p collection. @@ -84,11 +84,6 @@ //@cond PRIVATE class Private; Private *const d; - - Q_PRIVATE_SLOT(d, void slotIntervalValueChanged(int)) - Q_PRIVATE_SLOT(d, void slotCacheValueChanged(int)) - Q_PRIVATE_SLOT(d, void slotRetrievalOptionsGroupBoxDisabled(bool)) - //@endcond }; diff --git a/src/widgets/cachepolicypage.cpp b/src/widgets/cachepolicypage.cpp --- a/src/widgets/cachepolicypage.cpp +++ b/src/widgets/cachepolicypage.cpp @@ -76,12 +76,9 @@ setPageTitle(i18n("Retrieval")); d->mUi->setupUi(this); - connect(d->mUi->checkInterval, SIGNAL(valueChanged(int)), - SLOT(slotIntervalValueChanged(int))); - connect(d->mUi->localCacheTimeout, SIGNAL(valueChanged(int)), - SLOT(slotCacheValueChanged(int))); - connect(d->mUi->inherit, SIGNAL(toggled(bool)), - SLOT(slotRetrievalOptionsGroupBoxDisabled(bool))); + connect(d->mUi->checkInterval, QOverload::of(&QSpinBox::valueChanged), this, [this](int value) { d->slotIntervalValueChanged(value); }); + connect(d->mUi->localCacheTimeout, QOverload::of(&QSpinBox::valueChanged), this, [this](int value) { d->slotCacheValueChanged(value); }); + connect(d->mUi->inherit, &QCheckBox::toggled, this, [this](bool checked) { d->slotRetrievalOptionsGroupBoxDisabled(checked); }); if (mode == AdvancedMode) { d->mUi->stackedWidget->setCurrentWidget(d->mUi->rawPage); } @@ -117,7 +114,7 @@ d->mUi->syncOnDemand->setChecked(policy.syncOnDemand()); d->mUi->localParts->setItems(policy.localParts()); - const bool fetchBodies = policy.localParts().contains(QStringLiteral("RFC822")); + const bool fetchBodies = policy.localParts().contains(QLatin1String("RFC822")); d->mUi->retrieveFullMessages->setChecked(fetchBodies); //done explicitly to disable/enabled widgets @@ -152,10 +149,10 @@ // view. if (d->mUi->stackedWidget->currentWidget() != d->mUi->rawPage) { if (d->mUi->retrieveFullMessages->isChecked() && - !localParts.contains(QStringLiteral("RFC822"))) { + !localParts.contains(QLatin1String("RFC822"))) { localParts.append(QStringLiteral("RFC822")); } else if (!d->mUi->retrieveFullMessages->isChecked() && - localParts.contains(QStringLiteral("RFC822"))) { + localParts.contains(QLatin1String("RFC822"))) { localParts.removeAll(QStringLiteral("RFC822")); } } diff --git a/src/widgets/collectioncombobox.h b/src/widgets/collectioncombobox.h --- a/src/widgets/collectioncombobox.h +++ b/src/widgets/collectioncombobox.h @@ -97,7 +97,7 @@ * Returns the content mimetype the collections are filtered by. * Don't assume this list has the original order. */ - QStringList mimeTypeFilter() const; + Q_REQUIRED_RESULT QStringList mimeTypeFilter() const; /** * Sets the access @p rights the collections shall be filtered by. @@ -107,7 +107,7 @@ /** * Returns the access rights the collections are filtered by. */ - Collection::Rights accessRightsFilter() const; + Q_REQUIRED_RESULT Collection::Rights accessRightsFilter() const; /** * Sets the @p collection that shall be selected by default. @@ -117,16 +117,16 @@ /** * Returns the current selection. */ - Akonadi::Collection currentCollection() const; + Q_REQUIRED_RESULT Akonadi::Collection currentCollection() const; /** * @since 4.12 */ void setExcludeVirtualCollections(bool b); /** * @since 4.12 */ - bool excludeVirtualCollections() const; + Q_REQUIRED_RESULT bool excludeVirtualCollections() const; Q_SIGNALS: /** diff --git a/src/widgets/collectioncombobox.cpp b/src/widgets/collectioncombobox.cpp --- a/src/widgets/collectioncombobox.cpp +++ b/src/widgets/collectioncombobox.cpp @@ -43,8 +43,6 @@ public: Private(QAbstractItemModel *customModel, CollectionComboBox *parent) : mParent(parent) - , mMonitor(nullptr) - , mModel(nullptr) { if (customModel) { mBaseModel = customModel; diff --git a/src/widgets/collectiondialog.h b/src/widgets/collectiondialog.h --- a/src/widgets/collectiondialog.h +++ b/src/widgets/collectiondialog.h @@ -1,6 +1,6 @@ /* Copyright 2008 Ingo Klöcker - Copyright 2010 Laurent Montel + Copyright 2010-2018 Laurent Montel 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 @@ -129,7 +129,7 @@ /** * Returns the mime types any of which the selected collection(s) shall support. */ - QStringList mimeTypeFilter() const; + Q_REQUIRED_RESULT QStringList mimeTypeFilter() const; /** * Sets the access @p rights that the listed collections shall match with. @@ -143,7 +143,7 @@ * * @since 4.4 */ - Collection::Rights accessRightsFilter() const; + Q_REQUIRED_RESULT Collection::Rights accessRightsFilter() const; /** * Sets the @p text that will be shown in the dialog. @@ -171,19 +171,19 @@ * Returns the selection mode. * @see QAbstractItemView::selectionMode() */ - QAbstractItemView::SelectionMode selectionMode() const; + Q_REQUIRED_RESULT QAbstractItemView::SelectionMode selectionMode() const; /** * Returns the selected collection if the selection mode is * QAbstractItemView::SingleSelection. If another selection mode was set, * or nothing is selected, an invalid collection is returned. */ - Akonadi::Collection selectedCollection() const; + Q_REQUIRED_RESULT Akonadi::Collection selectedCollection() const; /** * Returns the list of selected collections. */ - Akonadi::Collection::List selectedCollections() const; + Q_REQUIRED_RESULT Akonadi::Collection::List selectedCollections() const; /** * Change collection dialog options. @@ -199,7 +199,7 @@ /** * @since 4.13 */ - bool useFolderByDefault() const; + Q_REQUIRED_RESULT bool useFolderByDefault() const; /** * Allow to specify collection content mimetype when we create new one. * @since 4.14.6 @@ -213,7 +213,6 @@ Q_PRIVATE_SLOT(d, void slotSelectionChanged()) Q_PRIVATE_SLOT(d, void slotAddChildCollection()) - Q_PRIVATE_SLOT(d, void slotCollectionCreationResult(KJob *job)) Q_PRIVATE_SLOT(d, void slotDoubleClicked()) //@endcond }; diff --git a/src/widgets/collectiondialog.cpp b/src/widgets/collectiondialog.cpp --- a/src/widgets/collectiondialog.cpp +++ b/src/widgets/collectiondialog.cpp @@ -1,6 +1,6 @@ /* Copyright 2008 Ingo Klöcker - Copyright 2010 Laurent Montel + Copyright 2010-2018 Laurent Montel 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 @@ -53,28 +53,26 @@ public: Private(QAbstractItemModel *customModel, CollectionDialog *parent, CollectionDialogOptions options) : mParent(parent) - , mMonitor(nullptr) - , mNewSubfolderButton(nullptr) { // setup GUI QVBoxLayout *layout = new QVBoxLayout(mParent); - mTextLabel = new QLabel; + mTextLabel = new QLabel(mParent); layout->addWidget(mTextLabel); mTextLabel->hide(); - QLineEdit *filterCollectionLineEdit = new QLineEdit(); + QLineEdit *filterCollectionLineEdit = new QLineEdit(mParent); filterCollectionLineEdit->setClearButtonEnabled(true); filterCollectionLineEdit->setPlaceholderText(i18nc("@info Displayed grayed-out inside the " "textbox, verb to search", "Search")); layout->addWidget(filterCollectionLineEdit); - mView = new EntityTreeView; + mView = new EntityTreeView(mParent); mView->setDragDropMode(QAbstractItemView::NoDragDrop); mView->header()->hide(); layout->addWidget(mView); - mUseByDefault = new QCheckBox(i18n("Use folder by default")); + mUseByDefault = new QCheckBox(i18n("Use folder by default"), mParent); mUseByDefault->hide(); layout->addWidget(mUseByDefault); @@ -175,13 +173,13 @@ EntityTreeView *mView = nullptr; AsyncSelectionHandler *mSelectionHandler = nullptr; QLabel *mTextLabel = nullptr; - bool mAllowToCreateNewChildCollection; - bool mKeepTreeExpanded; KRecursiveFilterProxyModel *mFilterCollection = nullptr; QCheckBox *mUseByDefault = nullptr; QStringList mContentMimeTypes; QDialogButtonBox *mButtonBox = nullptr; QPushButton *mNewSubfolderButton = nullptr; + bool mAllowToCreateNewChildCollection = false; + bool mKeepTreeExpanded = false; void slotDoubleClicked(); void slotSelectionChanged(); @@ -280,7 +278,7 @@ collection.setContentMimeTypes(mContentMimeTypes); } Akonadi::CollectionCreateJob *job = new Akonadi::CollectionCreateJob(collection); - connect(job, SIGNAL(result(KJob*)), mParent, SLOT(slotCollectionCreationResult(KJob*))); + connect(job, &Akonadi::CollectionCreateJob::result, mParent, [this](KJob *job) {slotCollectionCreationResult(job);}); } } diff --git a/src/widgets/collectionmaintenancepage.h b/src/widgets/collectionmaintenancepage.h --- a/src/widgets/collectionmaintenancepage.h +++ b/src/widgets/collectionmaintenancepage.h @@ -1,5 +1,5 @@ /* - Copyright (C) 2009-2017 Montel Laurent + Copyright (C) 2009-2018 Montel Laurent This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public diff --git a/src/widgets/collectionmaintenancepage.cpp b/src/widgets/collectionmaintenancepage.cpp --- a/src/widgets/collectionmaintenancepage.cpp +++ b/src/widgets/collectionmaintenancepage.cpp @@ -1,5 +1,5 @@ /* - Copyright (C) 2009-2017 Montel Laurent + Copyright (C) 2009-2018 Montel Laurent This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public @@ -40,8 +40,7 @@ class CollectionMaintenancePage::Private { public: - Private(CollectionMaintenancePage *q) - : q(q) + Private() {} void slotReindexCollection() @@ -55,7 +54,7 @@ QStringLiteral("/"), QStringLiteral("org.freedesktop.Akonadi.Indexer")); if (indexingAgentIface.isValid()) { - indexingAgentIface.call(QStringLiteral("reindexCollection"), (qlonglong)currentCollection.id()); + indexingAgentIface.call(QStringLiteral("reindexCollection"), static_cast(currentCollection.id())); ui.indexedCountLbl->setText(i18n("Remember that indexing can take some minutes.")); } else { qCWarning(AKONADIWIDGETS_LOG) << "indexer interface not valid"; @@ -74,14 +73,11 @@ Akonadi::Monitor *monitor = nullptr; Ui::CollectionMaintenancePage ui; - -private: - CollectionMaintenancePage * const q; }; CollectionMaintenancePage::CollectionMaintenancePage(QWidget *parent) : CollectionPropertiesPage(parent) - , d(new Private(this)) + , d(new Private) { setObjectName(QStringLiteral("Akonadi::CollectionMaintenancePage")); setPageTitle(i18n("Maintenance")); @@ -121,7 +117,7 @@ // Check if the resource caches full payloads or at least has local storage // (so that the indexer can retrieve the payloads on demand) const auto resource = Akonadi::AgentManager::self()->instance(col.resource()).type(); - if (!col.cachePolicy().localParts().contains(QStringLiteral("RFC822")) + if (!col.cachePolicy().localParts().contains(QLatin1String("RFC822")) && resource.customProperties().value(QStringLiteral("HasLocalStorage"), QString()) != QLatin1String("true")) { d->ui.indexingGroup->hide(); } diff --git a/src/widgets/collectionpropertiesdialog.cpp b/src/widgets/collectionpropertiesdialog.cpp --- a/src/widgets/collectionpropertiesdialog.cpp +++ b/src/widgets/collectionpropertiesdialog.cpp @@ -120,8 +120,7 @@ void CollectionPropertiesDialog::Private::init() { - QVBoxLayout *mainLayout = new QVBoxLayout; - q->setLayout(mainLayout); + QVBoxLayout *mainLayout = new QVBoxLayout(q); q->setAttribute(Qt::WA_DeleteOnClose); mTabWidget = new QTabWidget(q); mainLayout->addWidget(mTabWidget); diff --git a/src/widgets/collectionrequester.h b/src/widgets/collectionrequester.h --- a/src/widgets/collectionrequester.h +++ b/src/widgets/collectionrequester.h @@ -79,13 +79,13 @@ /** * Destroys the collection requester. */ - ~CollectionRequester(); + ~CollectionRequester() override; /** * Returns the currently chosen collection, or an empty collection if none * none was chosen. */ - Akonadi::Collection collection() const; + Q_REQUIRED_RESULT Akonadi::Collection collection() const; /** * Sets the mime types any of which the selected collection shall support. @@ -95,7 +95,7 @@ /** * Returns the mime types any of which the selected collection shall support. */ - QStringList mimeTypeFilter() const; + Q_REQUIRED_RESULT QStringList mimeTypeFilter() const; /** * Sets the access @p rights that the listed collections shall match with. @@ -108,7 +108,7 @@ * Returns the access rights that the listed collections shall match with. * @since 4.4 */ - Collection::Rights accessRightsFilter() const; + Q_REQUIRED_RESULT Collection::Rights accessRightsFilter() const; /** * @param options new collection dialog options diff --git a/src/widgets/collectionrequester.cpp b/src/widgets/collectionrequester.cpp --- a/src/widgets/collectionrequester.cpp +++ b/src/widgets/collectionrequester.cpp @@ -39,9 +39,6 @@ public: Private(CollectionRequester *parent) : q(parent) - , edit(nullptr) - , button(nullptr) - , collectionDialog(nullptr) { } @@ -76,8 +73,11 @@ void CollectionRequester::Private::_k_collectionReceived(KJob *job) { CollectionFetchJob *fetch = qobject_cast(job); - Collection::List chain; + if (!fetch) { + return; + } if (fetch->collections().size() == 1) { + Collection::List chain; Collection currentCollection = fetch->collections().at(0); while (currentCollection.isValid()) { chain << currentCollection; @@ -115,9 +115,8 @@ void CollectionRequester::Private::init() { - QHBoxLayout *hbox = new QHBoxLayout; + QHBoxLayout *hbox = new QHBoxLayout(q); hbox->setMargin(0); - q->setLayout(hbox); edit = new QLineEdit(q); edit->setReadOnly(true); diff --git a/src/widgets/collectionstatisticsdelegate.h b/src/widgets/collectionstatisticsdelegate.h --- a/src/widgets/collectionstatisticsdelegate.h +++ b/src/widgets/collectionstatisticsdelegate.h @@ -87,7 +87,7 @@ /** * Destroys the collection statistics delegate. */ - ~CollectionStatisticsDelegate(); + ~CollectionStatisticsDelegate() override; /** * @since 4.9.1 diff --git a/src/widgets/collectionstatisticsdelegate.cpp b/src/widgets/collectionstatisticsdelegate.cpp --- a/src/widgets/collectionstatisticsdelegate.cpp +++ b/src/widgets/collectionstatisticsdelegate.cpp @@ -1,6 +1,6 @@ /* Copyright (c) 2008 Thomas McGuire - Copyright (C) 2012-2017 Laurent Montel + Copyright (C) 2012-2018 Laurent Montel 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 @@ -223,7 +223,9 @@ QTreeView *treeView = qobject_cast(d->parent); bool expanded = treeView && treeView->isExpanded(firstColumn); - if (option.state & QStyle::State_Selected) { + if (index.data(EntityTreeModel::PendingCutRole).toBool()) { + painter->setPen(option.palette.color(QPalette::Disabled, QPalette::Text)); + } else if (option.state & QStyle::State_Selected) { painter->setPen(textColor.isValid() ? textColor : option.palette.highlightedText().color()); } else { painter->setPen(textColor.isValid() ? textColor : option.palette.text().color()); diff --git a/src/widgets/collectionview.h b/src/widgets/collectionview.h --- a/src/widgets/collectionview.h +++ b/src/widgets/collectionview.h @@ -86,7 +86,7 @@ /** * Destroys the collection view. */ - virtual ~CollectionView(); + ~CollectionView() override; /** * Sets the KXMLGUIClient which the view is used in. diff --git a/src/widgets/collectionview.cpp b/src/widgets/collectionview.cpp --- a/src/widgets/collectionview.cpp +++ b/src/widgets/collectionview.cpp @@ -48,7 +48,6 @@ public: Private(CollectionView *parent) : mParent(parent) - , xmlGuiClient(nullptr) { } @@ -184,7 +183,7 @@ const Collection collection = Collection::fromUrl(url); if (collection.isValid()) { - if (!supportedContentTypes.contains(QStringLiteral("inode/directory"))) { + if (!supportedContentTypes.contains(QLatin1String("inode/directory"))) { break; } diff --git a/src/widgets/conflictresolvedialog.cpp b/src/widgets/conflictresolvedialog.cpp --- a/src/widgets/conflictresolvedialog.cpp +++ b/src/widgets/conflictresolvedialog.cpp @@ -28,6 +28,9 @@ #include #include #include +#include +#include +#include #include #include @@ -56,6 +59,11 @@ return header() + mContent + footer(); } + QString plainText() const + { + return mTextContent; + } + void setPropertyNameTitle(const QString &title) override { mNameTitle = title; } @@ -76,22 +84,26 @@ .arg(name, textToHTML(leftValue), textToHTML(rightValue))); + mTextContent.append(QStringLiteral("%1:\n%2\n%3\n\n").arg(name, leftValue, rightValue)); break; case ConflictMode: mContent.append(QStringLiteral("
%1:%2%3
%1:%2
%1:%2