diff --git a/CMakeLists.txt b/CMakeLists.txt index 8091270ed..02c25f3ec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,361 +1,361 @@ cmake_minimum_required(VERSION 3.5) set(PIM_VERSION "5.14.44") project(Akonadi VERSION ${PIM_VERSION}) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # ECM setup set(KF5_MIN_VERSION "5.70.0") find_package(ECM ${KF5_MIN_VERSION} CONFIG REQUIRED) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules) include(KDEInstallDirs) include(KDECMakeSettings) include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE) include(GenerateExportHeader) include(ECMGenerateHeaders) include(ECMGeneratePriFile) include(ECMSetupVersion) include(FeatureSummary) include(CheckIncludeFiles) include(ECMQtDeclareLoggingCategory) include(CheckSymbolExists) include(KDEPackageAppTemplates) include(ECMMarkNonGuiExecutable) include(ECMAddTests) include(ECMSetupQtPluginMacroNames) include(AkonadiMacros) set(QT_REQUIRED_VERSION "5.13.0") set(RELEASE_SERVICE_VERSION "20.07.40") set(AKONADI_FULL_VERSION "${PIM_VERSION} (${RELEASE_SERVICE_VERSION})") configure_file(akonadifull-version.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/akonadifull-version.h @ONLY) ecm_setup_version(PROJECT VARIABLE_PREFIX AKONADI VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/akonadi_version.h" PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KF5AkonadiConfigVersion.cmake" SOVERSION 5) # Find packages find_package(Qt5Core ${QT_REQUIRED_VERSION} REQUIRED COMPONENTS Private) find_package(Qt5Sql ${QT_REQUIRED_VERSION} REQUIRED COMPONENTS Private) find_package(Qt5DBus ${QT_REQUIRED_VERSION} REQUIRED) find_package(Qt5Network ${QT_REQUIRED_VERSION} REQUIRED) find_package(Qt5Test ${QT_REQUIRED_VERSION} REQUIRED) find_package(Qt5Widgets ${QT_REQUIRED_VERSION} REQUIRED) find_package(Qt5Xml ${QT_REQUIRED_VERSION} REQUIRED) find_package(KF5Config ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5ConfigWidgets ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5CoreAddons ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5I18n ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5IconThemes ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5ItemModels ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5ItemViews ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5KIO ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5WidgetsAddons ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5WindowSystem ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5XmlGui ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5Crash ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(Qt5Designer NO_MODULE) set_package_properties(Qt5Designer PROPERTIES PURPOSE "Required to build the Qt Designer plugins" TYPE OPTIONAL ) option(BUILD_DESIGNERPLUGIN "Build plugin for Qt Designer" ON) add_feature_info(DESIGNERPLUGIN ${BUILD_DESIGNERPLUGIN} "Build plugin for Qt Designer") set(Boost_MINIMUM_VERSION "1.34.0") find_package(Boost ${Boost_MINIMUM_VERSION}) set_package_properties(Boost PROPERTIES DESCRIPTION "Boost C++ Libraries" URL "https://www.boost.org" TYPE REQUIRED ) set(AccountsQt5_MINIMUM_VERSION "1.15") find_package(AccountsQt5 ${AccountsQt5_MINIMUM_VERSION}) set_package_properties(AccountsQt5 PROPERTIES DESCRIPTION "Qt bindings for the Accounts framework" URL "https://gitlab.com/accounts-sso/libaccounts-qt" TYPE OPTIONAL ) set(KAccounts_MINIMUM_VERSION "19.08.0") find_package(KAccounts ${KAccounts_MINIMUM_VERSION}) set_package_properties(KAccounts PROPERTIES DESCRIPTION "KDE library for Accounts framework integration" URL "https://cgit.kde.org/kaccounts-integration.git" TYPE OPTIONAL ) if (${AccountsQt5_FOUND} AND ${KAccounts_FOUND}) set(WITH_ACCOUNTS TRUE) endif() if(BUILD_TESTING) set(AKONADI_TESTS_EXPORT AKONADICORE_EXPORT) set(AKONADIWIDGET_TESTS_EXPORT AKONADIWIDGETS_EXPORT) add_definitions(-DBUILD_TESTING) endif() ecm_setup_qtplugin_macro_names( JSON_ARG2 "AKONADI_AGENTCONFIG_FACTORY" CONFIG_CODE_VARIABLE PACKAGE_SETUP_AUTOMOC_VARIABLES ) # 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 if (IS_ABSOLUTE "${KDE_INSTALL_DATADIR_KF5}") set(KF5Akonadi_DATA_DIR "${KDE_INSTALL_DATADIR_KF5}/akonadi") else() set(KF5Akonadi_DATA_DIR "${CMAKE_INSTALL_PREFIX}/${KDE_INSTALL_DATADIR_KF5}/akonadi") endif() check_symbol_exists(malloc_trim "malloc.h" HAVE_MALLOC_TRIM) ############### Build Options ############### option(AKONADI_BUILD_QSQLITE "Build the Sqlite backend." TRUE) option(BUILD_TOOLS "Build and install tools for development and testing purposes." TRUE) option(NO_REGENERATE_MIME "Don't regenerate mime file (developer-only option)" FALSE ) option(INSTALL_APPARMOR "Install AppArmor profiles" TRUE) if(BUILD_TESTING) set(BUILD_TOOLS TRUE) endif() set(SMI_MIN_VERSION "1.3") find_package(SharedMimeInfo ${SMI_MIN_VERSION} REQUIRED) find_program(XSLTPROC_EXECUTABLE xsltproc) if(NOT XSLTPROC_EXECUTABLE) message(FATAL_ERROR "\nThe command line XSLT processor program 'xsltproc' could not be found.\nPlease install xsltproc.\n") endif() find_program(MYSQLD_EXECUTABLE NAMES mysqld PATHS /usr/sbin /usr/local/sbin /usr/libexec /usr/local/libexec /opt/mysql/libexec /usr/mysql/bin /opt/mysql/sbin DOC "The mysqld executable path. ONLY needed at runtime" ) if(MYSQLD_EXECUTABLE) message(STATUS "MySQL Server found: ${MYSQLD_EXECUTABLE}") else() message(STATUS "MySQL Server wasn't found. it is required to use the MySQL backend.") endif() find_path(POSTGRES_PATH NAMES pg_ctl HINTS /usr/lib${LIB_SUFFIX}/postgresql/8.4/bin /usr/lib${LIB_SUFFIX}/postgresql/9.0/bin /usr/lib${LIB_SUFFIX}/postgresql/9.1/bin DOC "The pg_ctl executable path. ONLY needed at runtime by the PostgreSQL backend" ) if(POSTGRES_PATH) message(STATUS "PostgreSQL Server found.") else() message(STATUS "PostgreSQL wasn't found. it is required to use the Postgres backend.") endif() if("${DATABASE_BACKEND}" STREQUAL "SQLITE") set(SQLITE_TYPE "REQUIRED") else() set(SQLITE_TYPE "OPTIONAL") endif() if(AKONADI_BUILD_QSQLITE) set(SQLITE_MIN_VERSION 3.6.23) find_package(Sqlite ${SQLITE_MIN_VERSION}) set_package_properties(Sqlite PROPERTIES URL "https://www.sqlite.org" DESCRIPTION "The Sqlite database library" TYPE ${SQLITE_TYPE} PURPOSE "Required by the Sqlite backend" ) endif() find_program(XMLLINT_EXECUTABLE xmllint) if(NOT XMLLINT_EXECUTABLE) message(STATUS "xmllint not found, skipping akonadidb.xml schema validation") endif() check_include_files(unistd.h HAVE_UNISTD_H) if(HAVE_UNISTD_H) add_definitions(-DHAVE_UNISTD_H) endif() if (IS_ABSOLUTE "${DBUS_INTERFACES_INSTALL_DIR}") set(AKONADI_DBUS_INTERFACES_INSTALL_DIR "${DBUS_INTERFACES_INSTALL_DIR}") else() set(AKONADI_DBUS_INTERFACES_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${DBUS_INTERFACES_INSTALL_DIR}") endif() if (IS_ABSOLUTE "${KDE_INSTALL_INCLUDEDIR_KF5}") set(AKONADI_INCLUDE_DIR "${KDE_INSTALL_INCLUDEDIR_KF5}") else() set(AKONADI_INCLUDE_DIR "${CMAKE_INSTALL_PREFIX}/${KDE_INSTALL_INCLUDEDIR_KF5}") endif() ############### Build Options ############### include(CTest) # Calls enable_testing(). include(CTestConfig.cmake) if(NOT DEFINED DATABASE_BACKEND) set(DATABASE_BACKEND "MYSQL" CACHE STRING "The default database backend to use for Akonadi. Can be either MYSQL, POSTGRES or SQLITE") endif() ############### CTest options ############### # Set a timeout value of 1 minute per test set(DART_TESTING_TIMEOUT 60) # CTestCustom.cmake has to be in the CTEST_BINARY_DIR. # in the KDE build system, this is the same as CMAKE_BINARY_DIR. configure_file(${CMAKE_CURRENT_SOURCE_DIR}/CTestCustom.cmake ${CMAKE_CURRENT_BINARY_DIR}/CTestCustom.cmake COPYONLY) ############### Macros ############### macro(SET_DEFAULT_DB_BACKEND) set(_backend ${ARGV0}) if("${_backend}" STREQUAL "SQLITE") set(AKONADI_DATABASE_BACKEND QSQLITE3) set(AKONADI_BUILD_QSQLITE TRUE) else() if("${_backend}" STREQUAL "POSTGRES") set(AKONADI_DATABASE_BACKEND QPSQL) else() set(AKONADI_DATABASE_BACKEND QMYSQL) endif() endif() message(STATUS "Using default db backend ${AKONADI_DATABASE_BACKEND}") add_definitions(-DAKONADI_DATABASE_BACKEND="${AKONADI_DATABASE_BACKEND}") endmacro() #### DB BACKEND DEFAULT #### set_default_db_backend(${DATABASE_BACKEND}) ############### Compilers flags ############### if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_C_COMPILER MATCHES "icc" OR (CMAKE_CXX_COMPILER_ID MATCHES "Clang")) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-long-long -std=iso9899:1990 -Wundef -Wcast-align -Werror-implicit-function-declaration -Wchar-subscripts -Wall -Wextra -Wpointer-arith -Wwrite-strings -Wformat-security -Wmissing-format-attribute -fno-common") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wnon-virtual-dtor -Wundef -Wcast-align -Wchar-subscripts -Wall -Wextra -Wpointer-arith -Wformat-security -fno-common -pedantic") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wnon-virtual-dtor -Wundef -Wcast-align -Wchar-subscripts -Wall -Wextra -Wpointer-arith -Wformat-security -fno-common -pedantic -Wno-deprecated-copy") set(CMAKE_CXX_FLAGS_DEBUGFULL "-g3 -fno-inline" CACHE STRING "Flags used by the C++ compiler during debugfull builds." FORCE) set(CMAKE_C_FLAGS_DEBUGFULL "-g3 -fno-inline" CACHE STRING "Flags used by the C compiler during debugfull builds." FORCE) mark_as_advanced(CMAKE_CXX_FLAGS_DEBUGFULL CMAKE_C_FLAGS_DEBUGFULL) elseif (MSVC) # This sets the __cplusplus macro to a real value based on the version of C++ specified by # the /std switch. Without it MSVC keeps reporting C++98, so feature detection doesn't work. set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Zc:__cplusplus") endif() if(MSVC) set(_ENABLE_EXCEPTIONS -EHsc) else() set(_ENABLE_EXCEPTIONS -fexceptions) endif() set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${_ENABLE_EXCEPTIONS}") ############### Configure files ############# configure_file(config-akonadi.h.cmake ${Akonadi_BINARY_DIR}/config-akonadi.h) ############### build targets ############### add_definitions(-DTRANSLATION_DOMAIN=\"libakonadi5\") include_directories( ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} src ) #add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x050f00) add_definitions(-DQT_NO_SIGNALS_SLOTS_KEYWORDS) add_definitions(-DQT_NO_EMIT) add_subdirectory(src) add_subdirectory(icons) add_subdirectory(templates) if(BUILD_TOOLS) # add testrunner (application for managing a self-contained # test environment) add_subdirectory(autotests/libs/testrunner) add_subdirectory(autotests/libs/testresource) add_subdirectory(autotests/libs/testsearchplugin) endif() if(BUILD_TESTING) add_subdirectory(autotests) add_subdirectory(tests) endif() if(INSTALL_APPARMOR) add_subdirectory(apparmor) endif() ############### install stuff ############### install(FILES akonadi-mime.xml DESTINATION ${XDG_MIME_INSTALL_DIR}) if (NOT NO_REGENERATE_MIME) update_xdg_mimetypes(${XDG_MIME_INSTALL_DIR}) endif() ############### CMake Config Files ############### set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/KF5Akonadi") configure_package_config_file( "${CMAKE_CURRENT_SOURCE_DIR}/KF5AkonadiConfig.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/KF5AkonadiConfig.cmake" INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR} PATH_VARS AKONADI_DBUS_INTERFACES_INSTALL_DIR AKONADI_INCLUDE_DIR KF5Akonadi_DATA_DIR ) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/KF5AkonadiConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/KF5AkonadiConfigVersion.cmake" "${CMAKE_CURRENT_SOURCE_DIR}/KF5AkonadiMacros.cmake" DESTINATION "${CMAKECONFIG_INSTALL_DIR}" COMPONENT Devel ) install(EXPORT KF5AkonadiTargets DESTINATION "${CMAKECONFIG_INSTALL_DIR}" FILE KF5AkonadiTargets.cmake NAMESPACE KF5::) ecm_qt_install_logging_categories( EXPORT AKONADI FILE akonadi.categories DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR} ) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/akonadi_version.h DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5} COMPONENT Devel ) feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/src/private/instance.cpp b/src/private/instance.cpp index fd99ea847..730a4c8bc 100644 --- a/src/private/instance.cpp +++ b/src/private/instance.cpp @@ -1,66 +1,68 @@ /* * Copyright (C) 2015 Daniel Vrátil * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include "instance_p.h" #include +#include using namespace Akonadi; namespace { -static QString sIdentifier; + +Q_GLOBAL_STATIC(QString, sIdentifier); static void loadIdentifier() { - sIdentifier = QString::fromUtf8(qgetenv("AKONADI_INSTANCE")); - if (sIdentifier.isNull()) { + *sIdentifier = QString::fromUtf8(qgetenv("AKONADI_INSTANCE")); + if (sIdentifier->isNull()) { // QString is null by default, which means it wasn't initialized // yet. Set it to empty when it is initialized - sIdentifier = QStringLiteral(""); + *sIdentifier = QStringLiteral(""); // clazy:exclude=empty-qstringliteral } } } bool Instance::hasIdentifier() { - if (::sIdentifier.isNull()) { + if (::sIdentifier->isNull()) { ::loadIdentifier(); } - return !sIdentifier.isEmpty(); + return !sIdentifier->isEmpty(); } void Instance::setIdentifier(const QString &identifier) { if (identifier.isNull()) { qunsetenv("AKONADI_INSTANCE"); - ::sIdentifier = QStringLiteral(""); + *::sIdentifier = QStringLiteral(""); // clazy:exclude=empty-qstringliteral } else { - ::sIdentifier = identifier; + *::sIdentifier = identifier; qputenv("AKONADI_INSTANCE", identifier.toUtf8()); } } QString Instance::identifier() { - if (::sIdentifier.isNull()) { + if (::sIdentifier->isNull()) { ::loadIdentifier(); } - return ::sIdentifier; + return *::sIdentifier; } diff --git a/src/private/protocol.cpp b/src/private/protocol.cpp index 385734a69..54c8b6f73 100644 --- a/src/private/protocol.cpp +++ b/src/private/protocol.cpp @@ -1,919 +1,921 @@ /* * Copyright (c) 2015 Daniel Vrátil * Copyright (c) 2016 Daniel Vrátil * * This library is free software; you can redistribute it and/or modify it * under the terms of the GNU Library General Public License as published by * the Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * * This library is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public * License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #include "protocol_p.h" #include "scope_p.h" #include "imapset_p.h" #include "datastream_p_p.h" #include #include #include #include #include #include +// clazy:excludeall=function-args-by-value + #undef AKONADI_DECLARE_PRIVATE #define AKONADI_DECLARE_PRIVATE(Class) \ inline Class##Private* Class::d_func() {\ return reinterpret_cast(d_ptr.data()); \ } \ inline const Class##Private* Class::d_func() const {\ return reinterpret_cast(d_ptr.constData()); \ } #define COMPARE(prop) \ (prop == ((decltype(this)) other)->prop) namespace Akonadi { namespace Protocol { QDebug operator<<(QDebug _dbg, Command::Type type) { QDebug dbg(_dbg.noquote()); switch (type) { case Command::Invalid: return dbg << "Invalid"; case Command::Hello: return dbg << "Hello"; case Command::Login: return dbg << "Login"; case Command::Logout: return dbg << "Logout"; case Command::Transaction: return dbg << "Transaction"; case Command::CreateItem: return dbg << "CreateItem"; case Command::CopyItems: return dbg << "CopyItems"; case Command::DeleteItems: return dbg << "DeleteItems"; case Command::FetchItems: return dbg << "FetchItems"; case Command::LinkItems: return dbg << "LinkItems"; case Command::ModifyItems: return dbg << "ModifyItems"; case Command::MoveItems: return dbg << "MoveItems"; case Command::CreateCollection: return dbg << "CreateCollection"; case Command::CopyCollection: return dbg << "CopyCollection"; case Command::DeleteCollection: return dbg << "DeleteCollection"; case Command::FetchCollections: return dbg << "FetchCollections"; case Command::FetchCollectionStats: return dbg << "FetchCollectionStats"; case Command::ModifyCollection: return dbg << "ModifyCollection"; case Command::MoveCollection: return dbg << "MoveCollection"; case Command::Search: return dbg << "Search"; case Command::SearchResult: return dbg << "SearchResult"; case Command::StoreSearch: return dbg << "StoreSearch"; case Command::CreateTag: return dbg << "CreateTag"; case Command::DeleteTag: return dbg << "DeleteTag"; case Command::FetchTags: return dbg << "FetchTags"; case Command::ModifyTag: return dbg << "ModifyTag"; case Command::FetchRelations: return dbg << "FetchRelations"; case Command::ModifyRelation: return dbg << "ModifyRelation"; case Command::RemoveRelations: return dbg << "RemoveRelations"; case Command::SelectResource: return dbg << "SelectResource"; case Command::StreamPayload: return dbg << "StreamPayload"; case Command::ItemChangeNotification: return dbg << "ItemChangeNotification"; case Command::CollectionChangeNotification: return dbg << "CollectionChangeNotification"; case Command::TagChangeNotification: return dbg << "TagChangeNotification"; case Command::RelationChangeNotification: return dbg << "RelationChangeNotification"; case Command::SubscriptionChangeNotification: return dbg << "SubscriptionChangeNotification"; case Command::DebugChangeNotification: return dbg << "DebugChangeNotification"; case Command::CreateSubscription: return dbg << "CreateSubscription"; case Command::ModifySubscription: return dbg << "ModifySubscription"; case Command::_ResponseBit: Q_ASSERT(false); return dbg << static_cast(type); } Q_ASSERT(false); return dbg << static_cast(type); } template DataStream &operator<<(DataStream &stream, const QSharedPointer &ptr) { Protocol::serialize(stream, ptr); return stream; } template DataStream &operator>>(DataStream &stream, QSharedPointer &ptr) { ptr = Protocol::deserialize(stream.device()).staticCast(); return stream; } /******************************************************************************/ Command::Command(quint8 type) : mType(type) { } bool Command::operator==(const Command &other) const { return mType == other.mType; } void Command::toJson(QJsonObject &json) const { json[QStringLiteral("response")] = static_cast(mType & Command::_ResponseBit); #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) { return stream << cmd.mType; } DataStream &operator>>(DataStream &stream, Command &cmd) { return stream >> cmd.mType; } QDebug operator<<(QDebug dbg, const Command &cmd) { return dbg.noquote() << ((cmd.mType & Command::_ResponseBit) ? "Response:" : "Command:") << static_cast(cmd.mType & ~Command::_ResponseBit) << "\n"; } 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() : Command(Command::Invalid | Command::_ResponseBit) , mErrorCode(0) { } Response::Response(Command::Type type) : Command(type | Command::_ResponseBit) , mErrorCode(0) { } 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) << cmd.mErrorCode << cmd.mErrorMsg; } DataStream &operator>>(DataStream &stream, Response &cmd) { return stream >> static_cast(cmd) >> cmd.mErrorCode >> cmd.mErrorMsg; } QDebug operator<<(QDebug dbg, const Response &resp) { return dbg.noquote() << static_cast(resp) << "Error code:" << resp.mErrorCode << "\n" << "Error msg:" << resp.mErrorMsg << "\n"; } /******************************************************************************/ class FactoryPrivate { public: typedef CommandPtr (*CommandFactoryFunc)(); typedef ResponsePtr (*ResponseFactoryFunc)(); FactoryPrivate() { // Session management registerType(); registerType(); registerType(); // Transactions registerType(); // Items registerType(); registerType(); registerType(); registerType(); registerType(); registerType(); registerType(); // Collections registerType(); registerType(); registerType(); registerType(); registerType(); registerType(); registerType(); // Search registerType(); registerType(); registerType(); // Tag registerType(); registerType(); registerType(); registerType(); // Relation registerType(); registerType(); registerType(); // Resources registerType(); // Other...? registerType(); registerType(); registerType(); registerType(); registerType(); registerType(); registerType(); registerType(); registerType(); } // clang has problem resolving the right qHash() overload for Command::Type, // so use its underlying integer type instead QHash::type, QPair> registrar; private: template static CommandPtr commandFactoryFunc() { return QSharedPointer::create(); } template static ResponsePtr responseFactoryFunc() { return QSharedPointer::create(); } template void registerType() { CommandFactoryFunc cmdFunc = &commandFactoryFunc; ResponseFactoryFunc respFunc = &responseFactoryFunc; registrar.insert(T, qMakePair(cmdFunc, respFunc)); } }; Q_GLOBAL_STATIC(FactoryPrivate, sFactoryPrivate) CommandPtr Factory::command(Command::Type type) { auto iter = sFactoryPrivate->registrar.constFind(type); if (iter == sFactoryPrivate->registrar.constEnd()) { return QSharedPointer::create(); } return iter->first(); } ResponsePtr Factory::response(Command::Type type) { auto iter = sFactoryPrivate->registrar.constFind(type); if (iter == sFactoryPrivate->registrar.constEnd()) { return QSharedPointer::create(); } return iter->second(); } /******************************************************************************/ /******************************************************************************/ bool ItemFetchScope::operator==(const ItemFetchScope &other) const { return mRequestedParts == other.mRequestedParts && mChangedSince == other.mChangedSince && mAncestorDepth == other.mAncestorDepth && mFlags == other.mFlags; } 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 ItemFetchScope::setFetch(FetchFlags attributes, bool fetch) { if (fetch) { mFlags |= attributes; if (attributes & FullPayload) { if (!mRequestedParts.contains(AKONADI_PARAM_PLD_RFC822)) { mRequestedParts << AKONADI_PARAM_PLD_RFC822; } } } else { mFlags &= ~attributes; } } bool ItemFetchScope::fetch(FetchFlags flags) const { if (flags == None) { return mFlags == None; } else { return mFlags & flags; } } void ItemFetchScope::toJson(QJsonObject &json) const { json[QStringLiteral("flags")] = static_cast(mFlags); 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 ItemFetchScope::NoAncestor: return dbg << "No ancestor"; case ItemFetchScope::ParentAncestor: return dbg << "Parent ancestor"; case ItemFetchScope::AllAncestors: return dbg << "All ancestors"; } Q_UNREACHABLE(); } DataStream &operator<<(DataStream &stream, const ItemFetchScope &scope) { return stream << scope.mRequestedParts << scope.mChangedSince << scope.mAncestorDepth << scope.mFlags; } DataStream &operator>>(DataStream &stream, ItemFetchScope &scope) { return stream >> scope.mRequestedParts >> scope.mChangedSince >> scope.mAncestorDepth >> scope.mFlags; } QDebug operator<<(QDebug dbg, const ItemFetchScope &scope) { return dbg.noquote() << "FetchScope(\n" << "Fetch Flags:" << scope.mFlags << "\n" << "Changed Since:" << scope.mChangedSince << "\n" << "Ancestor Depth:" << scope.mAncestorDepth << "\n" << "Requested Parts:" << scope.mRequestedParts << ")\n"; } /******************************************************************************/ ScopeContext::ScopeContext(Type type, qint64 id) { if (type == ScopeContext::Tag) { mTagCtx = id; } else if (type == ScopeContext::Collection) { mColCtx = id; } } ScopeContext::ScopeContext(Type type, const QString &ctx) { if (type == ScopeContext::Tag) { mTagCtx = ctx; } else if (type == ScopeContext::Collection) { mColCtx = ctx; } } 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 // because it's very hard, esp. without access to QVariant private // stuff, so we have to decompose it manually here. QVariant::Type vType = context.mColCtx.type(); stream << vType; if (vType == QVariant::LongLong) { stream << context.mColCtx.toLongLong(); } else if (vType == QVariant::String) { stream << context.mColCtx.toString(); } vType = context.mTagCtx.type(); stream << vType; if (vType == QVariant::LongLong) { stream << context.mTagCtx.toLongLong(); } else if (vType == QVariant::String) { stream << context.mTagCtx.toString(); } return stream; } DataStream &operator>>(DataStream &stream, ScopeContext &context) { QVariant::Type vType; qint64 id; QString rid; for (ScopeContext::Type type : { ScopeContext::Collection, ScopeContext::Tag }) { stream >> vType; if (vType == QVariant::LongLong) { stream >> id; context.setContext(type, id); } else if (vType == QVariant::String) { stream >> rid; context.setContext(type, rid); } } return stream; } QDebug operator<<(QDebug _dbg, const ScopeContext &ctx) { QDebug dbg(_dbg.noquote()); dbg << "ScopeContext("; if (ctx.isEmpty()) { dbg << "empty"; } else if (ctx.hasContextId(ScopeContext::Tag)) { dbg << "Tag ID:" << ctx.contextId(ScopeContext::Tag); } else if (ctx.hasContextId(ScopeContext::Collection)) { dbg << "Col ID:" << ctx.contextId(ScopeContext::Collection); } else if (ctx.hasContextRID(ScopeContext::Tag)) { dbg << "Tag RID:" << ctx.contextRID(ScopeContext::Tag); } else if (ctx.hasContextRID(ScopeContext::Collection)) { dbg << "Col RID:" << ctx.contextRID(ScopeContext::Collection); } return dbg << ")\n"; } /******************************************************************************/ ChangeNotification::ChangeNotification(Command::Type type) : Command(type) { } 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()) { case Command::Invalid: return false; case Command::ItemChangeNotification: return static_cast(this)->operation() == ItemChangeNotification::Remove; case Command::CollectionChangeNotification: return static_cast(this)->operation() == CollectionChangeNotification::Remove; case Command::TagChangeNotification: return static_cast(this)->operation() == TagChangeNotification::Remove; case Command::RelationChangeNotification: return static_cast(this)->operation() == RelationChangeNotification::Remove; case Command::SubscriptionChangeNotification: return static_cast(this)->operation() == SubscriptionChangeNotification::Remove; case Command::DebugChangeNotification: return false; default: Q_ASSERT_X(false, __FUNCTION__, "Unknown ChangeNotification type"); } return false; } bool ChangeNotification::isMove() const { switch (type()) { case Command::Invalid: return false; case Command::ItemChangeNotification: return static_cast(this)->operation() == ItemChangeNotification::Move; case Command::CollectionChangeNotification: return static_cast(this)->operation() == CollectionChangeNotification::Move; case Command::TagChangeNotification: case Command::RelationChangeNotification: case Command::SubscriptionChangeNotification: case Command::DebugChangeNotification: return false; default: Q_ASSERT_X(false, __FUNCTION__, "Unknown ChangeNotification type"); } return false; } bool ChangeNotification::appendAndCompress(ChangeNotificationList &list, const ChangeNotificationPtr &msg) { //It is likely that compressable notifications are within the last few notifications, so avoid searching a list that is potentially huge static const int maxCompressionSearchLength = 10; int searchCounter = 0; // There are often multiple Collection Modify notifications in the queue, // so we optimize for this case. if (msg->type() == Command::CollectionChangeNotification) { const auto &cmsg = Protocol::cmdCast(msg); if (cmsg.operation() == CollectionChangeNotification::Modify) { // We are iterating from end, since there's higher probability of finding // matching notification for (auto iter = list.end(), begin = list.begin(); iter != begin;) { --iter; 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++; if (searchCounter >= maxCompressionSearchLength) { break; } } } } // All other cases are just append, as the compression becomes too expensive in large batches list.append(msg); 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) << ntf.mSessionId; } DataStream &operator>>(DataStream &stream, ChangeNotification &ntf) { return stream >> static_cast(ntf) >> ntf.mSessionId; } QDebug operator<<(QDebug dbg, const ChangeNotification &ntf) { return dbg.noquote() << static_cast(ntf) << "Session:" << ntf.mSessionId << "\n" << "MetaData:" << ntf.mMetaData << "\n"; } DataStream &operator>>(DataStream &stream, ChangeNotification::Relation &relation) { return stream >> relation.type >> relation.leftId >> relation.rightId; } DataStream &operator<<(DataStream &stream, const ChangeNotification::Relation &relation) { return stream << relation.type << relation.leftId << relation.rightId; } QDebug operator<<(QDebug _dbg, const ChangeNotification::Relation &rel) { QDebug dbg(_dbg.noquote()); 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 #include "protocol_gen.cpp" /******************************************************************************/ diff --git a/src/private/protocol_exception_p.h b/src/private/protocol_exception_p.h index 144c7d383..23d00e673 100644 --- a/src/private/protocol_exception_p.h +++ b/src/private/protocol_exception_p.h @@ -1,54 +1,57 @@ /* Copyright (c) 2015 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. */ //krazy:excludeall=dpointer,inline #ifndef AKONADI_PROTOCOLEXCEPTION_P_H #define AKONADI_PROTOCOLEXCEPTION_P_H #include "akonadiprivate_export.h" #include #include #include namespace Akonadi { class AKONADIPRIVATE_EXPORT ProtocolException : public std::exception { public: explicit ProtocolException(const char *what) : std::exception() , mWhat(what) { std::cerr << "ProtocolException thrown:" << what << std::endl; } + ProtocolException(const ProtocolException &) = delete; + ProtocolException &operator=(const ProtocolException &) = delete; + const char *what() const throw() override { return mWhat.constData(); } private: QByteArray mWhat; }; } // namespace Akonadi #endif diff --git a/src/private/protocol_p.h b/src/private/protocol_p.h index a4b1c6440..b2d448f20 100644 --- a/src/private/protocol_p.h +++ b/src/private/protocol_p.h @@ -1,654 +1,655 @@ /* Copyright (c) 2007 Volker Krause Copyright (c) 2015 Daniel Vrátil Copyright (c) 2016 Daniel Vrátil This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef AKONADI_PROTOCOL_P_H #define AKONADI_PROTOCOL_P_H #include "akonadiprivate_export.h" #include #include #include #include #include #include #include "tristate_p.h" #include "scope_p.h" +//clazy:excludeall=function-args-by-value /** @file protocol_p.h Shared constants used in the communication protocol between the Akonadi server and its clients. */ namespace Akonadi { namespace Protocol { class Factory; class DataStream; class Command; class Response; class ItemFetchScope; class ScopeContext; class ChangeNotification; using Attributes = QMap; } // namespace Protocol } // namespace Akonadi namespace Akonadi { namespace Protocol { AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator<<( Akonadi::Protocol::DataStream &stream, const Akonadi::Protocol::Command &cmd); AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator>>( Akonadi::Protocol::DataStream &stream, 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 { public: enum Type : quint8 { Invalid = 0, // Session management Hello = 1, Login, Logout, // Transactions Transaction = 10, // Items CreateItem = 20, CopyItems, DeleteItems, FetchItems, LinkItems, ModifyItems, MoveItems, // Collections CreateCollection = 40, CopyCollection, DeleteCollection, FetchCollections, FetchCollectionStats, ModifyCollection, MoveCollection, // Search Search = 60, SearchResult, StoreSearch, // Tag CreateTag = 70, DeleteTag, FetchTags, ModifyTag, // Relation FetchRelations = 80, ModifyRelation, RemoveRelations, // Resources SelectResource = 90, // Other StreamPayload = 100, // Notifications ItemChangeNotification = 110, CollectionChangeNotification, TagChangeNotification, RelationChangeNotification, SubscriptionChangeNotification, DebugChangeNotification, CreateSubscription, ModifySubscription, // _MaxValue = 127 _ResponseBit = 0x80 // reserved }; explicit Command() = default; explicit Command(const Command &) = default; Command(Command &&) = default; ~Command() = default; 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 = Invalid; // unused 7 bytes private: friend class Factory; friend AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator<<(Akonadi::Protocol::DataStream &stream, const Akonadi::Protocol::Command &cmd); 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); }; AKONADIPRIVATE_EXPORT QDebug operator<<(QDebug dbg, Command::Type type); } // namespace Protocol } // namespace Akonadi Q_DECLARE_METATYPE(Akonadi::Protocol::Command::Type) Q_DECLARE_METATYPE(Akonadi::Protocol::CommandPtr) namespace Akonadi { namespace Protocol { AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator<<( Akonadi::Protocol::DataStream &stream, const Akonadi::Protocol::Response &cmd); AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator>>( Akonadi::Protocol::DataStream &stream, Akonadi::Protocol::Response &cmd); AKONADIPRIVATE_EXPORT QDebug operator<<(QDebug dbg, const Akonadi::Protocol::Response &response); using ResponsePtr = QSharedPointer; class AKONADIPRIVATE_EXPORT Response : public Command { public: explicit Response(); 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) { mErrorCode = code; mErrorMsg = message; } bool operator==(const Response &other) const; inline bool operator!=(const Response &other) const { return !operator==(other); } inline bool isError() const { return mErrorCode > 0; } inline int errorCode() const { return mErrorCode; } inline QString errorMessage() const { return mErrorMsg; } void toJson(QJsonObject &json) const; protected: explicit Response(Command::Type type); int mErrorCode; QString mErrorMsg; private: friend class Factory; friend AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator<<(Akonadi::Protocol::DataStream &stream, const Akonadi::Protocol::Response &cmd); friend AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator>>(Akonadi::Protocol::DataStream &stream, Akonadi::Protocol::Response &cmd); friend AKONADIPRIVATE_EXPORT QDebug operator<<(QDebug dbg, const Akonadi::Protocol::Response &cmd); }; } // namespace Protocol } // namespace Akonadi namespace Akonadi { namespace Protocol { template inline const X &cmdCast(const QSharedPointer &p) { return static_cast(*p); } template inline X &cmdCast(QSharedPointer &p) { return static_cast(*p); } class AKONADIPRIVATE_EXPORT Factory { public: static CommandPtr command(Command::Type type); static ResponsePtr response(Command::Type type); private: template friend AKONADIPRIVATE_EXPORT CommandPtr deserialize(QIODevice *device); }; AKONADIPRIVATE_EXPORT void serialize(DataStream &stream, const CommandPtr &command); AKONADIPRIVATE_EXPORT CommandPtr deserialize(QIODevice *device); AKONADIPRIVATE_EXPORT QString debugString(const Command &command); AKONADIPRIVATE_EXPORT inline QString debugString(const CommandPtr &command) { return debugString(*command); } } // namespace Protocol } // namespace Akonadi namespace Akonadi { namespace Protocol { AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator<<( Akonadi::Protocol::DataStream &stream, const Akonadi::Protocol::ItemFetchScope &scope); AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator>>( Akonadi::Protocol::DataStream &stream, Akonadi::Protocol::ItemFetchScope &scope); AKONADIPRIVATE_EXPORT QDebug operator<<(QDebug dbg, const Akonadi::Protocol::ItemFetchScope &scope); class AKONADIPRIVATE_EXPORT ItemFetchScope { public: enum FetchFlag : int { None = 0, CacheOnly = 1 << 0, CheckCachedPayloadPartsOnly = 1 << 1, FullPayload = 1 << 2, AllAttributes = 1 << 3, Size = 1 << 4, MTime = 1 << 5, RemoteRevision = 1 << 6, IgnoreErrors = 1 << 7, Flags = 1 << 8, RemoteID = 1 << 9, GID = 1 << 10, Tags = 1 << 11, Relations = 1 << 12, VirtReferences = 1 << 13 }; Q_DECLARE_FLAGS(FetchFlags, FetchFlag) enum AncestorDepth : ushort { NoAncestor, ParentAncestor, AllAncestors }; explicit ItemFetchScope() = default; ItemFetchScope(const ItemFetchScope &) = default; ItemFetchScope(ItemFetchScope &&other) = default; ~ItemFetchScope() = default; ItemFetchScope &operator=(const ItemFetchScope &) = default; ItemFetchScope &operator=(ItemFetchScope &&) = default; bool operator==(const ItemFetchScope &other) const; inline bool operator!=(const ItemFetchScope &other) const { return !operator==(other); } inline void setRequestedParts(const QVector &requestedParts) { mRequestedParts = requestedParts; } inline QVector requestedParts() const { return mRequestedParts; } QVector requestedPayloads() const; inline void setChangedSince(const QDateTime &changedSince) { mChangedSince = changedSince; } inline QDateTime changedSince() const { return mChangedSince; } inline void setAncestorDepth(AncestorDepth depth) { mAncestorDepth = depth; } inline AncestorDepth ancestorDepth() const { return mAncestorDepth; } inline bool cacheOnly() const { return mFlags & CacheOnly; } inline bool checkCachedPayloadPartsOnly() const { return mFlags & CheckCachedPayloadPartsOnly; } inline bool fullPayload() const { return mFlags & FullPayload; } inline bool allAttributes() const { return mFlags & AllAttributes; } inline bool fetchSize() const { return mFlags & Size; } inline bool fetchMTime() const { return mFlags & MTime; } inline bool fetchRemoteRevision() const { return mFlags & RemoteRevision; } inline bool ignoreErrors() const { return mFlags & IgnoreErrors; } inline bool fetchFlags() const { return mFlags & Flags; } inline bool fetchRemoteId() const { return mFlags & RemoteID; } inline bool fetchGID() const { return mFlags & GID; } inline bool fetchTags() const { return mFlags & Tags; } inline bool fetchRelations() const { return mFlags & Relations; } inline bool fetchVirtualReferences() const { return mFlags & VirtReferences; } void setFetch(FetchFlags attributes, bool fetch = true); bool fetch(FetchFlags flags) const; void toJson(QJsonObject &json) const; private: AncestorDepth mAncestorDepth = NoAncestor; // 2 bytes free FetchFlags mFlags = None; QVector mRequestedParts; QDateTime mChangedSince; friend AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator<<(Akonadi::Protocol::DataStream &stream, const Akonadi::Protocol::ItemFetchScope &scope); friend AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator>>(Akonadi::Protocol::DataStream &stream, 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::ItemFetchScope::FetchFlags) namespace Akonadi { namespace Protocol { AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator<<( Akonadi::Protocol::DataStream &stream, const Akonadi::Protocol::ScopeContext &ctx); AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator>>( Akonadi::Protocol::DataStream &stream, Akonadi::Protocol::ScopeContext &ctx); AKONADIPRIVATE_EXPORT QDebug operator<<(QDebug dbg, const Akonadi::Protocol::ScopeContext &ctx); class AKONADIPRIVATE_EXPORT ScopeContext { public: enum Type : uchar { Any = 0, Collection, Tag }; explicit ScopeContext() = default; ScopeContext(Type type, qint64 id); ScopeContext(Type type, const QString &id); ScopeContext(const ScopeContext &) = default; ScopeContext(ScopeContext &&) = default; ~ScopeContext() = default; 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); } inline bool isEmpty() const { return mColCtx.isNull() && mTagCtx.isNull(); } inline void setContext(Type type, qint64 id) { setCtx(type, id); } inline void setContext(Type type, const QString &id) { setCtx(type, id); } inline void clearContext(Type type) { setCtx(type, QVariant()); } inline bool hasContextId(Type type) const { return ctx(type).type() == QVariant::LongLong; } inline qint64 contextId(Type type) const { return hasContextId(type) ? ctx(type).toLongLong() : 0; } inline bool hasContextRID(Type type) const { return ctx(type).type() == QVariant::String; } inline QString contextRID(Type type) const { return hasContextRID(type) ? ctx(type).toString() : QString(); } void toJson(QJsonObject &json) const; private: QVariant mColCtx; QVariant mTagCtx; inline QVariant ctx(Type type) const { return type == Collection ? mColCtx : type == Tag ? mTagCtx : QVariant(); } inline void setCtx(Type type, const QVariant &v) { if (type == Collection) { mColCtx = v; } else if (type == Tag) { mTagCtx = v; } } friend AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator<<(Akonadi::Protocol::DataStream &stream, const Akonadi::Protocol::ScopeContext &context); friend AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator>>(Akonadi::Protocol::DataStream &stream, Akonadi::Protocol::ScopeContext &context); friend AKONADIPRIVATE_EXPORT QDebug operator<<(QDebug dbg, const Akonadi::Protocol::ScopeContext &ctx); }; } // 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); AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator>>( Akonadi::Protocol::DataStream &stream, Akonadi::Protocol::ChangeNotification &ntf); AKONADIPRIVATE_EXPORT QDebug operator<<(QDebug dbg, const Akonadi::Protocol::ChangeNotification &ntf); using ChangeNotificationPtr = QSharedPointer; using ChangeNotificationList = QVector; class AKONADIPRIVATE_EXPORT ChangeNotification : public Command { public: static QList itemsToUids(const QVector &items); class Relation { public: 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; } 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 &) = default; ChangeNotification &operator=(ChangeNotification &&) = default; bool operator==(const ChangeNotification &other) const; inline bool operator!=(const ChangeNotification &other) const { return !operator==(other); } bool isRemove() const; bool isMove() const; inline QByteArray sessionId() const { return mSessionId; } inline void setSessionId(const QByteArray &sessionId) { mSessionId = sessionId; } inline void addMetadata(const QByteArray &metadata) { mMetaData << metadata; } inline void removeMetadata(const QByteArray &metadata) { mMetaData.removeAll(metadata); } QVector metadata() const { return mMetaData; } 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 &) = default; ChangeNotification(ChangeNotification &&) = default; QByteArray mSessionId; // For internal use only: Akonadi server can add some additional information // that might be useful when evaluating the notification for example, but // it is never transferred to clients QVector mMetaData; friend AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator<<(Akonadi::Protocol::DataStream &stream, const Akonadi::Protocol::ChangeNotification &ntf); friend AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator>>(Akonadi::Protocol::DataStream &stream, Akonadi::Protocol::ChangeNotification &ntf); friend AKONADIPRIVATE_EXPORT QDebug operator<<(QDebug dbg, const Akonadi::Protocol::ChangeNotification &ntf); }; inline uint qHash(const ChangeNotification::Relation &rel) { return ::qHash(rel.leftId + rel.rightId); } // TODO: Internalize? AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator<<( Akonadi::Protocol::DataStream &stream, const Akonadi::Protocol::ChangeNotification::Relation &relation); AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator>>( Akonadi::Protocol::DataStream &stream, Akonadi::Protocol::ChangeNotification::Relation &relation); } // namespace Protocol } // namespace Akonadi Q_DECLARE_METATYPE(Akonadi::Protocol::ChangeNotificationPtr) Q_DECLARE_METATYPE(Akonadi::Protocol::ChangeNotificationList) /******************************************************************************/ // Here comes the actual generated Protocol. See protocol.xml for definitions, // and genprotocol folder for the generator. #include "protocol_gen.h" /******************************************************************************/ // Command parameters #define AKONADI_PARAM_ATR "ATR:" #define AKONADI_PARAM_CACHEPOLICY "CACHEPOLICY" #define AKONADI_PARAM_DISPLAY "DISPLAY" #define AKONADI_PARAM_ENABLED "ENABLED" #define AKONADI_PARAM_FLAGS "FLAGS" #define AKONADI_PARAM_TAGS "TAGS" #define AKONADI_PARAM_GID "GID" #define AKONADI_PARAM_INDEX "INDEX" #define AKONADI_PARAM_MIMETYPE "MIMETYPE" #define AKONADI_PARAM_NAME "NAME" #define AKONADI_PARAM_PARENT "PARENT" #define AKONADI_PARAM_PERSISTENTSEARCH "PERSISTENTSEARCH" #define AKONADI_PARAM_PLD "PLD:" #define AKONADI_PARAM_PLD_RFC822 "PLD:RFC822" #define AKONADI_PARAM_RECURSIVE "RECURSIVE" #define AKONADI_PARAM_REMOTE "REMOTE" #define AKONADI_PARAM_REMOTEID "REMOTEID" #define AKONADI_PARAM_REMOTEREVISION "REMOTEREVISION" #define AKONADI_PARAM_REVISION "REV" #define AKONADI_PARAM_SIZE "SIZE" #define AKONADI_PARAM_SYNC "SYNC" #define AKONADI_PARAM_TAG "TAG" #define AKONADI_PARAM_TYPE "TYPE" #define AKONADI_PARAM_VIRTUAL "VIRTUAL" // Flags #define AKONADI_FLAG_GID "\\Gid" #define AKONADI_FLAG_IGNORED "$IGNORED" #define AKONADI_FLAG_MIMETYPE "\\MimeType" #define AKONADI_FLAG_REMOTEID "\\RemoteId" #define AKONADI_FLAG_REMOTEREVISION "\\RemoteRevision" #define AKONADI_FLAG_TAG "\\Tag" #define AKONADI_FLAG_RTAG "\\RTag" #define AKONADI_FLAG_SEEN "\\SEEN" // Attributes #define AKONADI_ATTRIBUTE_HIDDEN "ATR:HIDDEN" #define AKONADI_ATTRIBUTE_MESSAGES "MESSAGES" #define AKONADI_ATTRIBUTE_UNSEEN "UNSEEN" // special resource names #define AKONADI_SEARCH_RESOURCE "akonadi_search_resource" namespace Akonadi { static const QString CollectionMimeType = QStringLiteral("inode/directory"); static const QString VirtualCollectionMimeType = QStringLiteral("application/x-vnd.akonadi.collection.virtual"); } #endif diff --git a/src/private/protocolgen/cppgenerator.cpp b/src/private/protocolgen/cppgenerator.cpp index 5c1ca09b5..e4c19ec28 100644 --- a/src/private/protocolgen/cppgenerator.cpp +++ b/src/private/protocolgen/cppgenerator.cpp @@ -1,738 +1,742 @@ /* 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 "cppgenerator.h" #include "nodetree.h" #include "typehelper.h" #include "cpphelper.h" #include #include CppGenerator::CppGenerator() { } CppGenerator::~CppGenerator() { } bool CppGenerator::generate(Node const *node) { Q_ASSERT(node->type() == Node::Document); mHeaderFile.setFileName(QStringLiteral("protocol_gen.h")); if (!mHeaderFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) { std::cerr << qPrintable(mHeaderFile.errorString()) << std::endl; return false; } mHeader.setDevice(&mHeaderFile); mImplFile.setFileName(QStringLiteral("protocol_gen.cpp")); if (!mImplFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) { std::cerr << qPrintable(mImplFile.errorString()) << std::endl; return false; } mImpl.setDevice(&mImplFile); return generateDocument(static_cast(node)); } void CppGenerator::writeHeaderHeader(DocumentNode const *node) { mHeader << "// This is an auto-generated file.\n" "// Any changes to this file will be overwritten\n" "\n" + "// clazy:excludeall=function-args-by-value\n" + "\n" "namespace Akonadi {\n" "namespace Protocol {\n" "\n" "AKONADIPRIVATE_EXPORT int version();\n" "\n"; // Forward declarations for (auto child : qAsConst(node->children())) { if (child->type() == Node::Class) { mHeader << "class " << static_cast(child)->className() << ";\n"; } } mHeader << "\n" "} // namespace Protocol\n" "} // namespace Akonadi\n\n"; } void CppGenerator::writeHeaderFooter(DocumentNode const * /*node*/) { // Nothing to do } void CppGenerator::writeImplHeader(DocumentNode const *node) { mImpl << "// This is an auto-generated file.\n" "// Any changes to this file will be overwritten\n" "\n" + "// clazy:excludeall=function-args-by-value\n" + "\n" "namespace Akonadi {\n" "namespace Protocol {\n" "\n" "int version()\n" "{\n" " return " << node->version() << ";\n" "}\n" "\n"; } void CppGenerator::writeImplFooter(DocumentNode const *) { mImpl << "} // namespace Protocol\n" "} // namespace Akonadi\n"; } bool CppGenerator::generateDocument(DocumentNode const *node) { writeHeaderHeader(node); writeImplHeader(node); writeImplSerializer(node); for (auto classNode : node->children()) { if (!generateClass(static_cast(classNode))) { return false; } } writeHeaderFooter(node); writeImplFooter(node); return true; } void CppGenerator::writeImplSerializer(DocumentNode const *node) { mImpl << "void serialize(DataStream &stream, const CommandPtr &cmd)\n" "{\n" " switch (static_cast(cmd->type() | (cmd->isResponse() ? Command::_ResponseBit : 0))) {\n" " case Command::Invalid:\n" " stream << cmdCast(cmd);\n" " break;\n" " case Command::Invalid | Command::_ResponseBit:\n" " stream << cmdCast(cmd);\n" " break;\n"; for (auto child : qAsConst(node->children())) { auto classNode = static_cast(child); if (classNode->classType() == ClassNode::Response) { mImpl << " case Command::" << classNode->name() << " | Command::_ResponseBit:\n" " stream << cmdCast<" << classNode->className() << ">(cmd);\n" " break;\n"; } else if (classNode->classType() == ClassNode::Command) { mImpl << " case Command::" << classNode->name() << ":\n" " stream << cmdCast<" << classNode->className() << ">(cmd);\n" " break;\n"; } else if (classNode->classType() == ClassNode::Notification) { mImpl << " case Command::" << classNode->name() << "Notification:\n" " stream << cmdCast<" << classNode->className() << ">(cmd);\n" " break;\n"; } } mImpl << " }\n" "}\n\n"; mImpl << "CommandPtr deserialize(QIODevice *device)\n" "{\n" " DataStream stream(device);\n" " stream.waitForData(sizeof(Command::Type));\n" " Command::Type cmdType;\n" " if (Q_UNLIKELY(device->peek((char *) &cmdType, sizeof(Command::Type)) != sizeof(Command::Type))) {\n" " throw ProtocolException(\"Failed to peek command type\");\n" " }\n" " CommandPtr cmd;\n" " if (cmdType & Command::_ResponseBit) {\n" " cmd = Factory::response(Command::Type(cmdType & ~Command::_ResponseBit));\n" " } else {\n" " cmd = Factory::command(cmdType);\n" " }\n\n" " switch (static_cast(cmdType)) {\n" " case Command::Invalid:\n" " stream >> cmdCast(cmd);\n" " return cmd;\n" " case Command::Invalid | Command::_ResponseBit:\n" " stream >> cmdCast(cmd);\n" " return cmd;\n"; for (auto child : qAsConst(node->children())) { auto classNode = static_cast(child); if (classNode->classType() == ClassNode::Response) { mImpl << " case Command::" << classNode->name() << " | Command::_ResponseBit:\n" " stream >> cmdCast<" << classNode->className() << ">(cmd);\n" " return cmd;\n"; } else if (classNode->classType() == ClassNode::Command) { mImpl << " case Command::" << classNode->name() << ":\n" " stream >> cmdCast<" << classNode->className() << ">(cmd);\n" " return cmd;\n"; } else if (classNode->classType() == ClassNode::Notification) { mImpl << " case Command::" << classNode->name() << "Notification:\n" " stream >> cmdCast<" << classNode->className() << ">(cmd);\n" " return cmd;\n"; } } mImpl << " }\n" " return CommandPtr::create();\n" "}\n" "\n"; mImpl << "QString debugString(const Command &cmd)\n" "{\n" " QString out;\n" " switch (static_cast(cmd.type() | (cmd.isResponse() ? Command::_ResponseBit : 0))) {\n" " case Command::Invalid:\n" " QDebug(&out).noquote() << static_cast(cmd);\n" " return out;\n" " case Command::Invalid | Command::_ResponseBit:\n" " QDebug(&out).noquote() << static_cast(cmd);\n" " return out;\n"; for (auto child : qAsConst(node->children())) { auto classNode = static_cast(child); if (classNode->classType() == ClassNode::Response) { mImpl << " case Command::" << classNode->name() << " | Command::_ResponseBit:\n" " QDebug(&out).noquote() << static_castclassName() << " &>(cmd);\n" " return out;\n"; } else if (classNode->classType() == ClassNode::Command) { mImpl << " case Command::" << classNode->name() << ":\n" " QDebug(&out).noquote() << static_castclassName() << " &>(cmd);\n" " return out;\n"; } else if (classNode->classType() == ClassNode::Notification) { mImpl << " case Command::" << classNode->name() << "Notification:\n" " QDebug(&out).noquote() << static_castclassName() << " &>(cmd);\n" " return out;\n"; } } mImpl << " }\n" " return QString();\n" "}\n" "\n"; } void CppGenerator::writeHeaderEnum(EnumNode const *node) { mHeader << " enum " << node->name() << " {\n"; for (auto enumChild : node->children()) { Q_ASSERT(enumChild->type() == Node::EnumValue); const auto valueNode = static_cast(enumChild); mHeader << " " << valueNode->name(); if (!valueNode->value().isEmpty()) { mHeader << " = " << valueNode->value(); } mHeader << ",\n"; } mHeader << " };\n"; if (node->enumType() == EnumNode::TypeFlag) { mHeader << " Q_DECLARE_FLAGS(" << node->name() << "s, " << node->name() << ")\n\n"; } } void CppGenerator::writeHeaderClass(ClassNode const *node) { // Begin class const QString parentClass = node->parentClassName(); const bool isTypeClass = node->classType() == ClassNode::Class; mHeader << "namespace Akonadi {\n" "namespace Protocol {\n\n" "AKONADIPRIVATE_EXPORT DataStream &operator<<(DataStream &stream, const " << node->className() << " &obj);\n" "AKONADIPRIVATE_EXPORT DataStream &operator>>(DataStream &stream, " << node->className() << " &obj);\n" "AKONADIPRIVATE_EXPORT QDebug operator<<(QDebug dbg, const " << node->className() << " &obj);\n" "\n" "using " << node->className() << "Ptr = QSharedPointer<" << node->className() << ">;\n" "\n"; if (isTypeClass) { mHeader << "class AKONADIPRIVATE_EXPORT " << node->className() << "\n"; } else { mHeader << "class AKONADIPRIVATE_EXPORT " << node->className() << " : public " << parentClass << "\n"; } mHeader << "{\n\n" "public:\n"; // Enums for (auto child : node->children()) { if (child->type() == Node::Enum) { const auto enumNode = static_cast(child); writeHeaderEnum(enumNode); } } // Ctors, dtor for (auto child : qAsConst(node->children())) { if (child->type() == Node::Ctor) { const auto ctor = static_cast(child); const auto args = ctor->arguments(); mHeader << " explicit " << node->className() << "("; for (int i = 0; i < args.count(); ++i) { const auto &arg = args[i]; if (TypeHelper::isNumericType(arg.type) || TypeHelper::isBoolType(arg.type)) { mHeader << arg.type << " " << arg.name; } else { mHeader << "const " << arg.type << " &" << arg.name; } if (!arg.defaultValue.isEmpty()) { mHeader << " = " << arg.defaultValue; } if (i < args.count() - 1) { mHeader << ", "; } } mHeader << ");\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() << " &) = 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"; // Properties for (auto child : node->children()) { if (child->type() == Node::Property) { const auto prop = static_cast(child); if (prop->asReference()) { mHeader << " inline const " << prop->type() << " &" << prop->name() << "() const { return " << prop->mVariableName() << "; }\n" " inline " << prop->type() << " &" << prop->name() << "() { return " << prop->mVariableName() << "; }\n"; } else { mHeader << " inline " << prop->type() << " " << prop->name() << "() const { return " << prop->mVariableName() << "; }\n"; } if (!prop->readOnly()) { if (auto setter = prop->setter()) { mHeader << " void " << setter->name << "(const " << setter->type << " &" << prop->name() << ");\n"; } else if (!prop->dependencies().isEmpty()) { QString varType; if (TypeHelper::isNumericType(prop->type()) || TypeHelper::isBoolType(prop->type())) { varType = QLatin1Char('(') + prop->type() + QLatin1Char(' '); } else { varType = QLatin1String("(const ") + prop->type() + QLatin1String(" &"); } mHeader << " void " << prop->setterName() << varType << prop->name() << ");\n"; } else { QString varType; if (TypeHelper::isNumericType(prop->type()) || TypeHelper::isBoolType(prop->type())) { varType = QLatin1Char('(') + prop->type() + QLatin1Char(' '); } else { varType = QLatin1String("(const ") + prop->type() + QLatin1String(" &"); } 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(); 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" "private:\n" " friend AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator<<(Akonadi::Protocol::DataStream &stream, const Akonadi::Protocol::" << node->className() << " &obj);\n" " friend AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator>>(Akonadi::Protocol::DataStream &stream, Akonadi::Protocol::" << node->className() << " &obj);\n" " friend AKONADIPRIVATE_EXPORT QDebug operator<<(QDebug dbg, const Akonadi::Protocol::" << node->className() << " &obj);\n" "};\n\n" "} // namespace Protocol\n" "} // namespace Akonadi\n" "\n"; mHeader << "Q_DECLARE_METATYPE(Akonadi::Protocol::" << node->className() << ")\n\n"; if (node->classType() != ClassNode::Class) { mHeader << "Q_DECLARE_METATYPE(Akonadi::Protocol::" << node->className() << "Ptr)\n\n"; } } void CppGenerator::writeImplSerializer(PropertyNode const *node, const char *streamingOperator) { const auto deps = node->dependencies(); if (deps.isEmpty()) { mImpl << " stream " << streamingOperator << " obj." << node->mVariableName() << ";\n"; } else { mImpl << " if ("; auto it = deps.cend(); while (1 + 1 == 2) { --it; const QString mVar = it.key(); mImpl << "(obj." << "m" << mVar[0].toUpper() << mVar.midRef(1) << " & " << it.value() << ")"; if (it == deps.cbegin()) { break; } else { mImpl << " && "; } } mImpl << ") {\n" " stream " << streamingOperator << " obj." << node->mVariableName() << ";\n" " }\n"; } } void CppGenerator::writeImplClass(ClassNode const *node) { const QString parentClass = node->parentClassName(); const auto children = node->children(); const auto properties = node->properties(); // Ctors for (auto child : children) { if (child->type() == Node::Ctor) { const auto ctor = static_cast(child); const auto args = ctor->arguments(); mImpl << node->className() << "::" << node->className() << "("; for (int i = 0; i < args.count(); ++i) { const auto &arg = args[i]; if (TypeHelper::isNumericType(arg.type) || TypeHelper::isBoolType(arg.type)) { mImpl << arg.type << " " << arg.name; } else { mImpl << "const " << arg.type << " &" << arg.name; } if (i < args.count() - 1) { mImpl << ", "; } } mImpl << ")\n"; char startChar = ','; if (!parentClass.isEmpty()) { const QString type = node->name() + ((node->classType() == ClassNode::Notification) ? QStringLiteral("Notification") : QString()); mImpl << " : " << parentClass << "(Command::" << type << ")\n"; } else { startChar = ':'; } for (auto prop : properties) { auto arg = std::find_if(args.cbegin(), args.cend(), [prop](const CtorNode::Argument &arg) { return arg.name == prop->name(); }); if (arg != args.cend()) { mImpl << " " << startChar << " " << prop->mVariableName() << "(" << arg->name << ")\n"; startChar = ','; } } mImpl << "{\n" "}\n" "\n"; } } // Comparison operator mImpl << "bool " << node->className() << "::operator==(const " << node->className() << " &other) const\n" "{\n"; mImpl << " return true // simplifies generation\n"; if (!parentClass.isEmpty()) { mImpl << " && " << parentClass << "::operator==(other)\n"; } for (auto prop : properties) { 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" "\n"; // non-trivial setters for (auto prop : properties) { if (prop->readOnly()) { continue; } if (const auto setter = prop->setter()) { mImpl << "void " << node->className() << "::" << setter->name << "(const " << setter->type << " &val)\n" "{\n"; if (!setter->append.isEmpty()) { mImpl << " m" << setter->append[0].toUpper() << setter->append.midRef(1) << " << val;\n"; } if (!setter->remove.isEmpty()) { const QString mVar = QLatin1String("m") + setter->remove[0].toUpper() + setter->remove.midRef(1); mImpl << " auto it = std::find(" << mVar << ".begin(), " << mVar << ".end(), val);\n" " if (it != " << mVar << ".end()) {\n" " " << mVar << ".erase(it);\n" " }\n"; } writeImplPropertyDependencies(prop); mImpl << "}\n\n"; } else if (!prop->dependencies().isEmpty()) { if (TypeHelper::isNumericType(prop->type()) || TypeHelper::isBoolType(prop->type())) { mImpl << "void " << node->className() << "::" << prop->setterName() << "(" << prop->type() << " val)\n" "{\n" " " << prop->mVariableName() << " = val;\n"; } else { mImpl << "void " << node->className() << "::" << prop->setterName() << "(const " << prop->type() << " &val)\n" "{\n" " " << prop->mVariableName() << " = val;\n"; } writeImplPropertyDependencies(prop); mImpl << "}\n\n"; } } // serialize auto serializeProperties = properties; CppHelper::sortMembersForSerialization(serializeProperties); mImpl << "DataStream &operator<<(DataStream &stream, const " << node->className() << " &obj)\n" "{\n"; if (!parentClass.isEmpty()) { mImpl << " stream << static_cast(obj);\n"; } for (auto prop : qAsConst(serializeProperties)) { writeImplSerializer(prop, "<<"); } mImpl << " return stream;\n" "}\n" "\n"; // deserialize mImpl << "DataStream &operator>>(DataStream &stream, " << node->className() << " &obj)\n" "{\n"; if (!parentClass.isEmpty()) { mImpl << " stream >> static_cast<" << parentClass << " &>(obj);\n"; } for (auto prop : qAsConst(serializeProperties)) { writeImplSerializer(prop, ">>"); } mImpl << " return stream;\n" "}\n" "\n"; // debug mImpl << "QDebug operator<<(QDebug dbg, const " << node->className() << " &obj)\n" "{\n"; if (!parentClass.isEmpty()) { mImpl << " dbg.noquote() << static_cast(obj)\n"; } else { 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 == QLatin1String("QByteArray")) { mImpl << " jsonArray.append(QString::fromUtf8(type)); /* "<< containerType << "*/\n"; } else if (TypeHelper::isBuiltInType(containerType)) { if (TypeHelper::containerType(prop->type()) == QLatin1String("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() == QLatin1String("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() == QLatin1String("QStringList")) { mImpl << " json[QStringLiteral(\"" << prop->name() << "\")] = QJsonArray::fromStringList(" << prop->mVariableName() << ");/* "<< prop->type() << " */\n"; } else if (prop->type() == QLatin1String("QDateTime")) { mImpl << " json[QStringLiteral(\"" << prop->name() << "\")] = " << prop->mVariableName() << ".toString()/* "<< prop->type() << " */;\n"; } else if (prop->type() == QLatin1String("QByteArray")) { mImpl << " json[QStringLiteral(\"" << prop->name() << "\")] = QString::fromUtf8(" << prop->mVariableName() << ")/* "<< prop->type() << " */;\n"; } else if (prop->type() == QLatin1String("Scope")) { mImpl << " {\n" " QJsonObject jsonObject;\n" " " << prop->mVariableName() << ".toJson(jsonObject); /* " << prop->type() << " */\n" " json[QStringLiteral(\"" << prop->name() << "\")] = " << "jsonObject;\n" " }\n"; } else if (prop->type() == QLatin1String("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() == QLatin1String("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() == QLatin1String("ModifySubscriptionCommand::ModifiedParts") || prop->type() == QLatin1String("ModifyTagCommand::ModifiedParts") || prop->type() == QLatin1String("ModifyCollectionCommand::ModifiedParts") || prop->type() == QLatin1String("ModifyItemsCommand::ModifiedParts") || prop->type() == QLatin1String("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) { const auto deps = node->dependencies(); QString key; QStringList values; QString enumType; for (auto it = deps.cbegin(), end = deps.cend(); it != end; ++it) { if (key != it.key()) { key = it.key(); const auto children = node->parent()->children(); for (auto child : children) { if (child->type() == Node::Property && child != node) { auto prop = static_cast(child); if (prop->name() == key) { enumType = prop->type(); break; } } } if (!values.isEmpty()) { mImpl << " m" << key[0].toUpper() << key.midRef(1) << " |= " << enumType << "(" << values.join(QLatin1String(" | ")) << ");\n"; values.clear(); } } values << *it; } if (!values.isEmpty()) { mImpl << " m" << key[0].toUpper() << key.midRef(1) << " |= " << enumType << "(" << values.join(QLatin1String(" | ")) << ");\n"; } } bool CppGenerator::generateClass(ClassNode const *node) { writeHeaderClass(node); mImpl << "\n\n/************************* " << node->className() << " *************************/\n\n"; writeImplClass(node); return true; } diff --git a/src/private/protocolgen/cpphelper.cpp b/src/private/protocolgen/cpphelper.cpp index a5c94c604..e24d4035c 100644 --- a/src/private/protocolgen/cpphelper.cpp +++ b/src/private/protocolgen/cpphelper.cpp @@ -1,88 +1,88 @@ /* 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 "cpphelper.h" #include "typehelper.h" #include "nodetree.h" #include #include #include #include #include #include namespace { class Dummy; -static QHash typeSizeLookup = { - { "Scope", sizeof(QSharedDataPointer) }, - { "ScopeContext", sizeof(QSharedDataPointer) }, - { "QSharedPointer", sizeof(QSharedPointer) }, - { "Tristate", sizeof(qint8) }, - { "Akonadi::Protocol::Attributes", sizeof(QMap) }, - { "QSet", sizeof(QSet) }, - { "QVector", sizeof(QVector) } -}; - // FIXME: This is based on hacks and guesses, does not work for generated types // and does not consider alignment. It should be good enough (TM) for our needs, // but it would be nice to make it smarter, for example by looking up type sizes // from the Node tree and understanding enums and QFlags types. size_t typeSize(const QString &typeName) { + static QHash typeSizeLookup = { + { "Scope", sizeof(QSharedDataPointer) }, + { "ScopeContext", sizeof(QSharedDataPointer) }, + { "QSharedPointer", sizeof(QSharedPointer) }, + { "Tristate", sizeof(qint8) }, + { "Akonadi::Protocol::Attributes", sizeof(QMap) }, + { "QSet", sizeof(QSet) }, + { "QVector", sizeof(QVector) } + }; + QByteArray tn; // Don't you just loooove hacks? // TODO: Extract underlying type during XML parsing if (typeName.startsWith(QLatin1String("Akonadi::Protocol")) && typeName.endsWith(QLatin1String("Ptr"))) { tn = "QSharedPointer"; } else { tn = TypeHelper::isContainer(typeName) ? TypeHelper::containerName(typeName).toUtf8() : typeName.toUtf8(); } auto it = typeSizeLookup.find(tn); if (it == typeSizeLookup.end()) { const auto typeId = QMetaType::type(tn); const int size = QMetaType(typeId).sizeOf(); // for types of unknown size int it = typeSizeLookup.insert(tn, size ? size_t(size) : sizeof(int)); } return *it; } } void CppHelper::sortMembers(QVector &props) { std::sort(props.begin(), props.end(), [](PropertyNode const *lhs, PropertyNode const *rhs) { return typeSize(lhs->type()) > typeSize(rhs->type()); }); } void CppHelper::sortMembersForSerialization(QVector &props) { std::sort(props.begin(), props.end(), [](PropertyNode const *lhs, PropertyNode const *rhs) { return lhs->dependencies().isEmpty() > rhs->dependencies().isEmpty(); }); } diff --git a/src/private/protocolgen/nodetree.h b/src/private/protocolgen/nodetree.h index ad927ec1e..0708e8fd7 100644 --- a/src/private/protocolgen/nodetree.h +++ b/src/private/protocolgen/nodetree.h @@ -1,202 +1,207 @@ /* 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 NODETREE_H #define NODETREE_H #include #include class Node { public: enum NodeType { Document, Class, Ctor, Enum, EnumValue, Property }; Node(NodeType type, Node *parent); + Node(const Node &) = delete; + Node(Node &&) = delete; virtual ~Node(); + Node &operator=(const Node &) = delete; + Node &operator=(Node &&) = delete; + NodeType type() const; Node *parent() const; void appendNode(Node *child); const QVector &children() const; protected: Node *mParent; QVector mChildren; NodeType mType; }; class DocumentNode : public Node { public: DocumentNode(int version); int version() const; private: int mVersion; }; class PropertyNode; class ClassNode : public Node { public: enum ClassType { Invalid, Class, Command, Response, Notification }; ClassNode(const QString &name, ClassType type, DocumentNode *parent); QString name() const; ClassType classType() const; QString className() const; QString parentClassName() const; QVector properties() const; static ClassType elementNameToType(const QStringRef &name); private: QString mName; ClassType mClassType; }; class CtorNode : public Node { public: struct Argument { QString name; QString type; QString defaultValue; QString mVariableName() const { return QStringLiteral("m") + name[0].toUpper() + name.midRef(1); } }; CtorNode(const QVector &args, ClassNode *parent); ~CtorNode(); QVector arguments() const; void setArgumentType(const QString &name, const QString &type); private: QVector mArgs; }; class EnumNode : public Node { public: enum EnumType { TypeInvalid, TypeEnum, TypeFlag }; EnumNode(const QString &name, EnumType type, ClassNode *parent); QString name() const; EnumType enumType() const; static EnumType elementNameToType(const QStringRef &name); private: QString mName; EnumType mEnumType; }; class EnumValueNode : public Node { public: EnumValueNode(const QString &name, EnumNode *parent); QString name() const; void setValue(const QString &value); QString value() const; private: QString mName; QString mValue; }; class PropertyNode : public Node { public: struct Setter { QString name; QString type; QString append; QString remove; }; PropertyNode(const QString &name, const QString &type, ClassNode *parent); ~PropertyNode(); QString type() const; QString name() const; void setDefaultValue(const QString &defaultValue); QString defaultValue() const; bool readOnly() const; void setReadOnly(bool readOnly); bool asReference() const; void setAsReference(bool asReference); bool isPointer() const; QMultiMap dependencies() const; void addDependency(const QString &enumVar, const QString &enumValue); Setter *setter() const; void setSetter(Setter *setter); QString mVariableName() const; QString setterName() const; private: QString mName; QString mType; QString mDefaultValue; QMultiMap mDepends; Setter *mSetter; bool mReadOnly; bool mAsReference; }; #endif // NODETREE_H diff --git a/src/private/scope_p.h b/src/private/scope_p.h index 9c5be3da0..ec28fc6fd 100644 --- a/src/private/scope_p.h +++ b/src/private/scope_p.h @@ -1,129 +1,131 @@ /* * Copyright (c) 2009 Volker Krause * Copyright (c) 2015 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. */ //krazy:excludeall=dpointer #ifndef AKONADI_PRIVATE_SCOPE_P_H #define AKONADI_PRIVATE_SCOPE_P_H #include "akonadiprivate_export.h" #include #include class QJsonObject; class QStringList; namespace Akonadi { class ImapSet; class ImapInterval; namespace Protocol { class DataStream; } class ScopePrivate; class AKONADIPRIVATE_EXPORT Scope { public: enum SelectionScope : uchar { Invalid = 0, Uid = 1 << 0, Rid = 1 << 1, HierarchicalRid = 1 << 2, Gid = 1 << 3 }; class AKONADIPRIVATE_EXPORT HRID { public: HRID(); explicit HRID(qint64 id, const QString &remoteId = QString()); HRID(const HRID &other); HRID(HRID &&other); HRID &operator=(const HRID &other); HRID &operator=(HRID &&other); + ~HRID() = default; + bool isEmpty() const; bool operator==(const HRID &other) const; void toJson(QJsonObject &json) const; qint64 id; QString remoteId; }; explicit Scope(); Scope(SelectionScope scope, const QStringList &ids); /* UID */ Scope(qint64 id); //krazy:exclude=explicit Scope(const ImapSet &uidSet); //krazy:exclude=explicit Scope(const ImapInterval &interval); //krazy:exclude=explicit Scope(const QVector &interval); //krazy:exclude=explicit Scope(const QVector &hridChain); //krazy:exclude=explicit Scope(const Scope &other); Scope(Scope &&other); ~Scope(); Scope &operator=(const Scope &other); Scope &operator=(Scope &&other); bool operator==(const Scope &other) const; bool operator!=(const Scope &other) const; SelectionScope scope() const; bool isEmpty() const; ImapSet uidSet() const; void setUidSet(const ImapSet &uidSet); void setRidSet(const QStringList &ridSet); QStringList ridSet() const; void setHRidChain(const QVector &ridChain); QVector hridChain() const; void setGidSet(const QStringList &gidChain); QStringList gidSet() const; qint64 uid() const; QString rid() const; QString gid() const; void toJson(QJsonObject &json) const; private: QSharedDataPointer d; friend class ScopePrivate; friend Protocol::DataStream &operator<<(Protocol::DataStream &stream, const Akonadi::Scope &scope); friend Protocol::DataStream &operator>>(Protocol::DataStream &stream, Akonadi::Scope &scope); }; } // namespace Akonadi AKONADIPRIVATE_EXPORT QDebug operator<<(QDebug debug, const Akonadi::Scope &scope); #endif diff --git a/src/server/akonadi.cpp b/src/server/akonadi.cpp index 9d5f30437..19022c3c6 100644 --- a/src/server/akonadi.cpp +++ b/src/server/akonadi.cpp @@ -1,472 +1,475 @@ /*************************************************************************** * Copyright (C) 2006 by Till Adam * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "akonadi.h" #include "handler.h" #include "connection.h" #include "serveradaptor.h" #include "akonadiserver_debug.h" #include "cachecleaner.h" #include "intervalcheck.h" #include "storagejanitor.h" #include "storage/dbconfig.h" #include "storage/datastore.h" #include "notificationmanager.h" #include "resourcemanager.h" #include "tracer.h" #include "utils.h" #include "debuginterface.h" #include "storage/itemretrievalmanager.h" #include "storage/collectionstatistics.h" #include "preprocessormanager.h" #include "search/searchmanager.h" #include "search/searchtaskmanager.h" #include "aklocalserver.h" #include #include #include #include #include #include #include #include #include #include #include #include using namespace Akonadi; using namespace Akonadi::Server; namespace { class AkonadiDataStore : public DataStore { + Q_OBJECT public: AkonadiDataStore(AkonadiServer &server): DataStore(server) {} }; class AkonadiDataStoreFactory : public DataStoreFactory { public: AkonadiDataStoreFactory(AkonadiServer &akonadi) : DataStoreFactory() , m_akonadi(akonadi) {} DataStore *createStore() override { return new AkonadiDataStore(m_akonadi); } private: AkonadiServer &m_akonadi; }; } AkonadiServer::AkonadiServer() : QObject() { // Register bunch of useful types qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType("quintptr"); DataStore::setFactory(std::make_unique(*this)); } bool AkonadiServer::init() { qCInfo(AKONADISERVER_LOG) << "Starting up the Akonadi Server..."; 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); const QString connectionSettingsFile = StandardDirs::connectionConfigFile(StandardDirs::WriteOnly); QSettings connectionSettings(connectionSettingsFile, QSettings::IniFormat); const QByteArray dbusAddress = qgetenv("DBUS_SESSION_BUS_ADDRESS"); if (!dbusAddress.isEmpty()) { connectionSettings.setValue(QStringLiteral("DBUS/Address"), QLatin1String(dbusAddress)); } // Setup database if (!setupDatabase()) { quit(); return false; } // Create local servers and start listening if (!createServers(settings, connectionSettings)) { quit(); return false; } const auto searchManagers = settings.value(QStringLiteral("Search/Manager"), QStringList{QStringLiteral("Agent")}).toStringList(); mTracer = std::make_unique(); mCollectionStats = std::make_unique(); mCacheCleaner = std::make_unique(); mItemRetrieval = std::make_unique(); mAgentSearchManager = std::make_unique(); mDebugInterface = std::make_unique(*mTracer.get()); mResourceManager = std::make_unique(*mTracer.get()); mPreprocessorManager = std::make_unique(*mTracer.get()); mIntervalCheck = std::make_unique(*mItemRetrieval.get()); mSearchManager = std::make_unique(searchManagers, *mAgentSearchManager.get()); mStorageJanitor = std::make_unique(*this); if (settings.value(QStringLiteral("General/DisablePreprocessing"), false).toBool()) { mPreprocessorManager->setEnabled(false); } new ServerAdaptor(this); QDBusConnection::sessionBus().registerObject(QStringLiteral("/Server"), this); mControlWatcher = std::make_unique( DBus::serviceName(DBus::Control), QDBusConnection::sessionBus(), QDBusServiceWatcher::WatchForUnregistration); connect(mControlWatcher.get(), &QDBusServiceWatcher::serviceUnregistered, this, [this]() { qCCritical(AKONADISERVER_LOG) << "Control process died, committing suicide!"; quit(); }); // Unhide all the items that are actually hidden. // The hidden flag was probably left out after an (abrupt) // server quit. We don't attempt to resume preprocessing // for the items as we don't actually know at which stage the // operation was interrupted... DataStore::self()->unhideAllPimItems(); // We are ready, now register org.freedesktop.Akonadi service to DBus and // the fun can begin if (!QDBusConnection::sessionBus().registerService(DBus::serviceName(DBus::Server))) { qCCritical(AKONADISERVER_LOG) << "Unable to connect to dbus service: " << QDBusConnection::sessionBus().lastError().message(); quit(); return false; } return true; } AkonadiServer::~AkonadiServer() = default; bool AkonadiServer::quit() { if (mAlreadyShutdown) { return true; } mAlreadyShutdown = true; qCDebug(AKONADISERVER_LOG) << "terminating connection threads"; mConnections.clear(); qCDebug(AKONADISERVER_LOG) << "terminating service threads"; // Keep this order in sync (reversed) with the order of initialization mStorageJanitor.reset(); mSearchManager.reset(); mIntervalCheck.reset(); mPreprocessorManager.reset(); mResourceManager.reset(); mDebugInterface.reset(); mAgentSearchManager.reset(); mItemRetrieval.reset(); mCacheCleaner.reset(); mCollectionStats.reset(); mTracer.reset(); if (DbConfig::isConfigured()) { if (DataStore::hasDataStore()) { DataStore::self()->close(); } qCDebug(AKONADISERVER_LOG) << "stopping db process"; stopDatabaseProcess(); } //QSettings settings(StandardDirs::serverConfigFile(), QSettings::IniFormat); const QString connectionSettingsFile = StandardDirs::connectionConfigFile(StandardDirs::WriteOnly); if (!QDir::home().remove(connectionSettingsFile)) { qCCritical(AKONADISERVER_LOG) << "Failed to remove runtime connection config file"; } QTimer::singleShot(0, this, &AkonadiServer::doQuit); return true; } void AkonadiServer::doQuit() { QCoreApplication::exit(); } void AkonadiServer::newCmdConnection(quintptr socketDescriptor) { if (mAlreadyShutdown) { return; } auto connection = std::make_unique(socketDescriptor, *this); connect(connection.get(), &Connection::disconnected, this, &AkonadiServer::connectionDisconnected); mConnections.push_back(std::move(connection)); } void AkonadiServer::connectionDisconnected() { auto it = std::find_if(mConnections.begin(), mConnections.end(), [this](const auto &ptr) { return ptr.get() == sender(); }); Q_ASSERT(it != mConnections.end()); mConnections.erase(it); } bool AkonadiServer::setupDatabase() { if (!DbConfig::configuredDatabase()) { return false; } if (DbConfig::configuredDatabase()->useInternalServer()) { if (!startDatabaseProcess()) { return false; } } else { if (!createDatabase()) { return false; } } DbConfig::configuredDatabase()->setup(); // initialize the database DataStore *db = DataStore::self(); if (!db->database().isOpen()) { qCCritical(AKONADISERVER_LOG) << "Unable to open database" << db->database().lastError().text(); return false; } if (!db->init()) { qCCritical(AKONADISERVER_LOG) << "Unable to initialize database."; return false; } return true; } bool AkonadiServer::startDatabaseProcess() { if (!DbConfig::configuredDatabase()->useInternalServer()) { qCCritical(AKONADISERVER_LOG) << "Trying to start external database!"; } // create the database directories if they don't exists StandardDirs::saveDir("data"); StandardDirs::saveDir("data", QStringLiteral("file_db_data")); return DbConfig::configuredDatabase()->startInternalServer(); } bool AkonadiServer::createDatabase() { bool success = true; const QLatin1String initCon("initConnection"); QSqlDatabase db = QSqlDatabase::addDatabase(DbConfig::configuredDatabase()->driverName(), initCon); DbConfig::configuredDatabase()->apply(db); db.setDatabaseName(DbConfig::configuredDatabase()->databaseName()); if (!db.isValid()) { qCCritical(AKONADISERVER_LOG) << "Invalid database object during initial database connection"; return false; } if (db.open()) { db.close(); } else { qCCritical(AKONADISERVER_LOG) << "Failed to use database" << DbConfig::configuredDatabase()->databaseName(); qCCritical(AKONADISERVER_LOG) << "Database error:" << db.lastError().text(); qCDebug(AKONADISERVER_LOG) << "Trying to create database now..."; db.close(); db.setDatabaseName(QString()); if (db.open()) { { QSqlQuery query(db); if (!query.exec(QStringLiteral("CREATE DATABASE %1").arg(DbConfig::configuredDatabase()->databaseName()))) { qCCritical(AKONADISERVER_LOG) << "Failed to create database"; qCCritical(AKONADISERVER_LOG) << "Query error:" << query.lastError().text(); qCCritical(AKONADISERVER_LOG) << "Database error:" << db.lastError().text(); success = false; } } // make sure query is destroyed before we close the db db.close(); } else { qCCritical(AKONADISERVER_LOG) << "Failed to connect to database!"; qCCritical(AKONADISERVER_LOG) << "Database error:" << db.lastError().text(); success = false; } } return success; } void AkonadiServer::stopDatabaseProcess() { if (!DbConfig::configuredDatabase()->useInternalServer()) { // closing initConnection this late to work around QTBUG-63108 QSqlDatabase::removeDatabase(QStringLiteral("initConnection")); return; } DbConfig::configuredDatabase()->stopInternalServer(); } bool AkonadiServer::createServers(QSettings &settings, QSettings &connectionSettings) { mCmdServer = std::make_unique(this); connect(mCmdServer.get(), QOverload::of(&AkLocalServer::newConnection), this, &AkonadiServer::newCmdConnection); mNotificationManager = std::make_unique(); mNtfServer = std::make_unique(this); // Note: this is a queued connection, as NotificationManager lives in its // own thread connect(mNtfServer.get(), QOverload::of(&AkLocalServer::newConnection), mNotificationManager.get(), &NotificationManager::registerConnection); // TODO: share socket setup with client #ifdef Q_OS_WIN // use the installation prefix as uid QString suffix; if (Instance::hasIdentifier()) { suffix = QStringLiteral("%1-").arg(Instance::identifier()); } 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 << ":" << mCmdServer->errorString(); return false; } 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 << ":" << mNtfServer->errorString(); return false; } connectionSettings.setValue(QStringLiteral("Data/Method"), QStringLiteral("NamedPipe")); connectionSettings.setValue(QStringLiteral("Data/NamedPipe"), cmdPipe); connectionSettings.setValue(QStringLiteral("Notifications/Method"), QStringLiteral("NamedPipe")); connectionSettings.setValue(QStringLiteral("Notifications/NamedPipe"), ntfPipe); #else Q_UNUSED(settings); const QString cmdSocketName = QStringLiteral("akonadiserver-cmd.socket"); const QString ntfSocketName = QStringLiteral("akonadiserver-ntf.socket"); const QString socketDir = Utils::preferredSocketDirectory(StandardDirs::saveDir("data"), qMax(cmdSocketName.length(), ntfSocketName.length())); const QString cmdSocketFile = socketDir % QLatin1Char('/') % cmdSocketName; QFile::remove(cmdSocketFile); if (!mCmdServer->listen(cmdSocketFile)) { qCCritical(AKONADISERVER_LOG) << "Unable to listen on Unix socket" << cmdSocketFile << ":" << mCmdServer->errorString(); return false; } const QString ntfSocketFile = socketDir % QLatin1Char('/') % ntfSocketName; QFile::remove(ntfSocketFile); if (!mNtfServer->listen(ntfSocketFile)) { qCCritical(AKONADISERVER_LOG) << "Unable to listen on Unix socket" << ntfSocketFile << ":" << mNtfServer->errorString(); return false; } connectionSettings.setValue(QStringLiteral("Data/Method"), QStringLiteral("UnixPath")); connectionSettings.setValue(QStringLiteral("Data/UnixPath"), cmdSocketFile); connectionSettings.setValue(QStringLiteral("Notifications/Method"), QStringLiteral("UnixPath")); connectionSettings.setValue(QStringLiteral("Notifications/UnixPath"), ntfSocketFile); #endif return true; } CacheCleaner *AkonadiServer::cacheCleaner() { return mCacheCleaner.get(); } IntervalCheck &AkonadiServer::intervalChecker() { return *mIntervalCheck.get(); } ResourceManager &AkonadiServer::resourceManager() { return *mResourceManager.get(); } NotificationManager *AkonadiServer::notificationManager() { return mNotificationManager.get(); } CollectionStatistics &AkonadiServer::collectionStatistics() { return *mCollectionStats.get(); } PreprocessorManager &AkonadiServer::preprocessorManager() { return *mPreprocessorManager.get(); } SearchTaskManager &AkonadiServer::agentSearchManager() { return *mAgentSearchManager.get(); } SearchManager &AkonadiServer::searchManager() { return *mSearchManager.get(); } ItemRetrievalManager &AkonadiServer::itemRetrievalManager() { return *mItemRetrieval.get(); } Tracer &AkonadiServer::tracer() { return *mTracer.get(); } QString AkonadiServer::serverPath() const { return StandardDirs::saveDir("config"); } + +#include "akonadi.moc" diff --git a/src/server/collectionscheduler.h b/src/server/collectionscheduler.h index feefdd672..5c4e38e01 100644 --- a/src/server/collectionscheduler.h +++ b/src/server/collectionscheduler.h @@ -1,112 +1,112 @@ /* Copyright (c) 2014 Daniel Vrátil This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef AKONADI_SERVER_COLLECTIONSCHEDULER_H #define AKONADI_SERVER_COLLECTIONSCHEDULER_H #include #include #include #include "entities.h" #include "akthread.h" namespace Akonadi { namespace Server { class Collection; class PauseableTimer; class CollectionScheduler : public AkThread { Q_OBJECT public: explicit CollectionScheduler(const QString &threadName, QThread::Priority priority, QObject *parent = nullptr); ~CollectionScheduler() override; void collectionChanged(qint64 collectionId); void collectionRemoved(qint64 collectionId); void collectionAdded(qint64 collectionId); /** * Sets the minimum timeout interval. * * Default value is 5. * * @p intervalMinutes Minimum timeout interval in minutes. */ void setMinimumInterval(int intervalMinutes); Q_REQUIRED_RESULT int minimumInterval() const; using TimePoint = std::chrono::steady_clock::time_point; /** * @return the timestamp (in seconds since epoch) when collectionExpired * will next be called on the given collection, or 0 if we don't know about the collection. * Only used by the unittest. */ TimePoint nextScheduledTime(qint64 collectionId) const; /** * @return the next timeout */ std::chrono::milliseconds currentTimerInterval() const; protected: void init() override; void quit() override; virtual bool shouldScheduleCollection(const Collection &collection) = 0; virtual bool hasChanged(const Collection &collection, const Collection &changed) = 0; /** * @return Return cache timeout in minutes */ virtual int collectionScheduleInterval(const Collection &collection) = 0; /** * Called when it's time to do something on that collection. * Notice: this method is called in the secondary thread */ virtual void collectionExpired(const Collection &collection) = 0; void inhibit(bool inhibit = true); private Q_SLOTS: void schedulerTimeout(); void startScheduler(); - void scheduleCollection(/*sic!*/ Collection collection, bool shouldStartScheduler = true); + void scheduleCollection(/*sic!*/ Akonadi::Server::Collection collection, bool shouldStartScheduler = true); private: using ScheduleMap = QMultiMap; ScheduleMap::const_iterator constFind(qint64 collectionId) const; ScheduleMap::iterator find(qint64 collectionId); ScheduleMap::const_iterator constLowerBound(TimePoint timestamp) const; mutable QMutex mScheduleLock; ScheduleMap mSchedule; PauseableTimer *mScheduler = nullptr; int mMinInterval = 5; }; } // namespace Server } // namespace Akonadi #endif // AKONADI_SERVER_COLLECTIONSCHEDULER_H diff --git a/src/server/exception.h b/src/server/exception.h index e96c7ba15..8c23b5e4b 100644 --- a/src/server/exception.h +++ b/src/server/exception.h @@ -1,100 +1,95 @@ /* Copyright (c) 2009 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef AKONADI_EXCEPTION_H #define AKONADI_EXCEPTION_H #include #include #include namespace Akonadi { namespace Server { /** Base class for exception used internally by the Akonadi server. */ class Exception : public std::exception { public: explicit Exception(const char *what) throw() : mWhat(what) { } explicit Exception(const QByteArray &what) throw() : mWhat(what) { } explicit Exception(const QString &what) throw() : mWhat(what.toUtf8()) { } - Exception(const Exception &other) throw() - : std::exception(other) - , mWhat(other.what()) - { - } + Exception(const Exception &) = delete; + Exception &operator=(const Exception &) = delete; - virtual ~Exception() throw() - { - } + virtual ~Exception() throw() = default; const char *what() const throw() override { return mWhat.constData(); } virtual const char *type() const throw() { return "General Exception"; } protected: QByteArray mWhat; }; #define AKONADI_EXCEPTION_MAKE_INSTANCE( classname ) \ class classname : public Akonadi::Server::Exception \ { \ public: \ classname ( const char *what ) throw() \ : Akonadi::Server::Exception( what ) \ { \ } \ classname ( const QByteArray &what ) throw() \ : Akonadi::Server::Exception( what ) \ { \ } \ classname ( const QString &what ) throw() \ : Akonadi::Server::Exception( what ) \ { \ } \ const char *type() const throw() override \ { \ return "" #classname; \ } \ } } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/intervalcheck.h b/src/server/intervalcheck.h index 8e8ebcfb2..3eeeeba1a 100644 --- a/src/server/intervalcheck.h +++ b/src/server/intervalcheck.h @@ -1,71 +1,70 @@ /* Copyright (c) 2008 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef INTERVALCHECK_H #define INTERVALCHECK_H #include "collectionscheduler.h" #include #include namespace Akonadi { namespace Server { class ItemRetrievalManager; /** Interval checking thread. */ class IntervalCheck : public CollectionScheduler { Q_OBJECT public: explicit IntervalCheck(ItemRetrievalManager &itemRetrievalManager); ~IntervalCheck() override; /** * Requests the given collection to be synced. * Executed from any thread, forwards to triggerCollectionXSync() in the * retrieval thread. * A minimum time interval between two sync requests is ensured. */ void requestCollectionSync(const Collection &collection); protected: int collectionScheduleInterval(const Collection &collection) override; bool hasChanged(const Collection &collection, const Collection &changed) override; bool shouldScheduleCollection(const Collection &collection) override; -protected Q_SLOTS: void collectionExpired(const Collection &collection) override; private: QHash mLastChecks; QHash mLastCollectionTreeSyncs; ItemRetrievalManager &mItemRetrievalManager; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/resourcemanager.h b/src/server/resourcemanager.h index 7b305bd5b..33845c733 100644 --- a/src/server/resourcemanager.h +++ b/src/server/resourcemanager.h @@ -1,55 +1,56 @@ /* Copyright (c) 2006 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef AKONADI_RESOURCEMANAGER_H #define AKONADI_RESOURCEMANAGER_H #include namespace Akonadi { namespace Server { class Tracer; /** Listens to agent instance added/removed signals and creates/removes the corresponding data in the database. */ class ResourceManager : public QObject { Q_OBJECT public: explicit ResourceManager(Tracer &tracer); + QStringList resourceInstances() const; + public Q_SLOTS: void addResourceInstance(const QString &name, const QStringList &capabilities); void removeResourceInstance(const QString &name); - QStringList resourceInstances() const; private: Tracer &mTracer; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/search/searchmanager.h b/src/server/search/searchmanager.h index 4b54f7e93..1a30b504a 100644 --- a/src/server/search/searchmanager.h +++ b/src/server/search/searchmanager.h @@ -1,126 +1,126 @@ /* Copyright (c) 2010 Volker Krause Copyright (c) 2013 Daniel Vrátil This library is free software; you can redistribute it and/or modify it under the terms of the GNU 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 SEARCHMANAGER_H #define SEARCHMANAGER_H #include "akthread.h" #include #include #include class QTimer; class QPluginLoader; namespace Akonadi { class AbstractSearchPlugin; namespace Server { class AbstractSearchEngine; class Collection; class SearchTaskManager; /** * SearchManager creates and deletes persistent searches for all currently * active search engines. */ class SearchManager : public AkThread { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.freedesktop.Akonadi.SearchManager") public: /** Create a new search manager with the given @p searchEngines. */ explicit SearchManager(const QStringList &searchEngines, SearchTaskManager &agentSearchManager); ~SearchManager() override; /** * Updates the search query asynchronously. Returns immediately */ virtual void updateSearchAsync(const Collection &collection); /** * Updates the search query synchronously. */ virtual void updateSearch(const Collection &collection); /** * Returns currently available search plugins. */ virtual QVector searchPlugins() const; public Q_SLOTS: virtual void scheduleSearchUpdate(); /** * This is called via D-Bus from AgentManager to register an agent with * search interface. */ virtual void registerInstance(const QString &id); /** * This is called via D-Bus from AgentManager to unregister an agent with * search interface. */ virtual void unregisterInstance(const QString &id); private Q_SLOTS: void searchUpdateTimeout(); void searchUpdateResultsAvailable(const QSet &results); /** * Actual implementation of search updates. * * This method has to be called using QMetaObject::invokeMethod. */ - void updateSearchImpl(const Collection &collection); + void updateSearchImpl(const Akonadi::Server::Collection &collection); private: void init() override; void quit() override; // Called from main thread void loadSearchPlugins(); // Called from manager thread void initSearchPlugins(); SearchTaskManager &mAgentSearchManager; QStringList mEngineNames; QVector mPluginLoaders; QVector mEngines; QVector mPlugins; QTimer *mSearchUpdateTimer = nullptr; QMutex mLock; QSet mUpdatingCollections; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/storage/datastore.h b/src/server/storage/datastore.h index 00c60cc0f..4bfbceaa4 100644 --- a/src/server/storage/datastore.h +++ b/src/server/storage/datastore.h @@ -1,371 +1,376 @@ /*************************************************************************** * Copyright (C) 2006 by Andreas Gungl * * * * 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 DATASTORE_H #define DATASTORE_H #include #include #include #include #include class QSqlQuery; class QTimer; #include "entities.h" #include "notificationcollector.h" #include namespace Akonadi { namespace Server { class DataStore; class DataStoreFactory { public: + explicit DataStoreFactory() = default; + virtual ~DataStoreFactory() = default; virtual DataStore *createStore() = 0; + +private: + Q_DISABLE_COPY_MOVE(DataStoreFactory) }; class NotificationCollector; /** This class handles all the database access.

Database configuration

You can select between various database backends during runtime using the @c $HOME/.config/akonadi/akonadiserverrc configuration file. Example: @verbatim [%General] Driver=QMYSQL [QMYSQL_EMBEDDED] Name=akonadi Options=SERVER_DATADIR=/home/foo/.local/share/akonadi/db_data [QMYSQL] Name=akonadi Host=localhost User=foo Password=***** #Options=UNIX_SOCKET=/home/foo/.local/share/akonadi/socket-bar/mysql.socket StartServer=true ServerPath=/usr/sbin/mysqld [QSQLITE] Name=/home/foo/.local/share/akonadi/akonadi.db @endverbatim Use @c General/Driver to select the QSql driver to use for database access. The following drivers are currently supported, other might work but are untested: - QMYSQL - QMYSQL_EMBEDDED - QSQLITE The options for each driver are read from the corresponding group. The following options are supported, dependent on the driver not all of them might have an effect: - Name: Database name, for sqlite that's the file name of the database. - Host: Hostname of the database server - User: Username for the database server - Password: Password for the database server - Options: Additional options, format is driver-dependent - StartServer: Start the database locally just for Akonadi instead of using an existing one - ServerPath: Path to the server executable */ class DataStore : public QObject { Q_OBJECT public: const constexpr static bool Silent = true; static void setFactory(std::unique_ptr factory); /** Closes the database connection and destroys the DataStore object. */ ~DataStore() override; /** Opens the database connection. */ virtual void open(); /** Closes the database connection. */ void close(); /** Initializes the database. Should be called during startup by the main thread. */ virtual bool init(); /** Per thread singleton. */ static DataStore *self(); /** * Returns whether per thread DataStore has been created. */ static bool hasDataStore(); /* --- ItemFlags ----------------------------------------------------- */ virtual bool setItemsFlags(const PimItem::List &items, const QVector &flags, bool *flagsChanged = nullptr, const Collection &col = Collection(), bool silent = false); virtual bool appendItemsFlags(const PimItem::List &items, const QVector &flags, bool *flagsChanged = nullptr, bool checkIfExists = true, const Collection &col = Collection(), bool silent = false); virtual bool removeItemsFlags(const PimItem::List &items, const QVector &flags, bool *tagsChanged = nullptr, const Collection &collection = Collection(), bool silent = false); /* --- ItemTags ----------------------------------------------------- */ virtual bool setItemsTags(const PimItem::List &items, const Tag::List &tags, bool *tagsChanged = nullptr, bool silent = false); virtual bool appendItemsTags(const PimItem::List &items, const Tag::List &tags, bool *tagsChanged = nullptr, bool checkIfExists = true, const Collection &col = Collection(), bool silent = false); virtual bool removeItemsTags(const PimItem::List &items, const Tag::List &tags, bool *tagsChanged = nullptr, bool silent = false); virtual bool removeTags(const Tag::List &tags, bool silent = false); /* --- ItemParts ----------------------------------------------------- */ virtual bool removeItemParts(const PimItem &item, const QSet &parts); // removes all payload parts for this item. virtual bool invalidateItemCache(const PimItem &item); /* --- 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); /// same as the above but for database backends without working referential actions on foreign keys virtual bool cleanupCollection_slow(Collection &collection); /// moves the collection @p collection to @p newParent. virtual bool moveCollection(Collection &collection, const Collection &newParent); virtual bool appendMimeTypeForCollection(qint64 collectionId, const QStringList &mimeTypes); static QString collectionDelimiter() { return QStringLiteral("/"); } /** Determines the active cache policy for this Collection. The active cache policy is set in the corresponding Collection fields. */ virtual void activeCachePolicy(Collection &col); /// Returns all virtual collections the @p item is linked to QVector virtualCollections(const PimItem &item); QMap< Server::Entity::Id, QList< PimItem > > virtualCollections(const Akonadi::Server::PimItem::List &items); /* --- PimItem ------------------------------------------------------- */ virtual bool appendPimItem(QVector &parts, const QVector &flags, const MimeType &mimetype, const Collection &collection, const QDateTime &dateTime, const QString &remote_id, const QString &remoteRevision, const QString &gid, PimItem &pimItem); /** * Removes the pim item and all referenced data ( e.g. flags ) */ virtual bool cleanupPimItems(const PimItem::List &items, bool silent = false); /** * Unhides the specified PimItem. Emits the itemAdded() notification as * the hidden flag is assumed to have been set by appendPimItem() before * pushing the item to the preprocessor chain. The hidden item had his * notifications disabled until now (so for the clients the "unhide" operation * is actually a new item arrival). * * This function does NOT verify if the item was *really* hidden: this is * responsibility of the caller. */ virtual bool unhidePimItem(PimItem &pimItem); /** * Unhides all the items which have the "hidden" flag set. * This function doesn't emit any notification about the items * being unhidden so it's meant to be called only in rare circumstances. * The most notable call to this function is at server startup * when we attempt to restore a clean state of the database. */ virtual bool unhideAllPimItems(); /* --- Collection attributes ------------------------------------------ */ virtual bool addCollectionAttribute(const Collection &col, const QByteArray &key, const QByteArray &value, bool silent = false); /** * Removes the given collection attribute for @p col. * @throws HandlerException on database errors * @returns @c true if the attribute existed, @c false otherwise */ virtual bool removeCollectionAttribute(const Collection &col, const QByteArray &key); /* --- Helper functions ---------------------------------------------- */ /** Begins a transaction. No changes will be written to the database and no notification signal will be emitted unless you call commitTransaction(). @return @c true if successful. */ virtual bool beginTransaction(const QString &name); /** Reverts all changes within the current transaction. */ virtual bool rollbackTransaction(); /** Commits all changes within the current transaction and emits all collected notfication signals. If committing fails, the transaction will be rolled back. */ virtual bool commitTransaction(); /** Returns true if there is a transaction in progress. */ bool inTransaction() const; /** Returns the notification collector of this DataStore object. Use this to listen to change notification signals. */ NotificationCollector *notificationCollector(); /** Returns the QSqlDatabase object. Use this for generating queries yourself. Will [re-]open the database, if it is closed. */ QSqlDatabase database(); /** Sets the current session id. */ void setSessionId(const QByteArray &sessionId) { mSessionId = sessionId; } /** Returns if the database is currently open */ bool isOpened() const { return m_dbOpened; } bool doRollback(); void transactionKilledByDB(); Q_SIGNALS: /** Emitted if a transaction has been successfully committed. */ void transactionCommitted(); /** Emitted if a transaction has been aborted. */ void transactionRolledBack(); protected: /** Creates a new DataStore object and opens it. */ DataStore(AkonadiServer &akonadi); void debugLastDbError(const char *actionDescription) const; void debugLastQueryError(const QSqlQuery &query, const char *actionDescription) const; private: bool doAppendItemsFlag(const PimItem::List &items, const Flag &flag, const QSet &existing, const Collection &col, bool silent); bool doAppendItemsTag(const PimItem::List &items, const Tag &tag, const QSet &existing, const Collection &col, bool silent); /** Converts the given date/time to the database format, i.e. "YYYY-MM-DD HH:MM:SS". @param dateTime the date/time in UTC @return the date/time in database format @see dateTimeToQDateTime */ static QString dateTimeFromQDateTime(const QDateTime &dateTime); /** Converts the given date/time from database format to QDateTime. @param dateTime the date/time in database format @return the date/time as QDateTime @see dateTimeFromQDateTime */ static QDateTime dateTimeToQDateTime(const QByteArray &dateTime); private Q_SLOTS: void sendKeepAliveQuery(); protected: static std::unique_ptr sFactory; std::unique_ptr mNotificationCollector; AkonadiServer &m_akonadi; private: void cleanupAfterRollback(); QString m_connectionName; QSqlDatabase m_database; bool m_dbOpened; bool m_transactionKilledByDB = false; uint m_transactionLevel; struct TransactionQuery { QString query; QVector boundValues; bool isBatch; }; QByteArray mSessionId; QTimer *m_keepAliveTimer = nullptr; static bool s_hasForeignKeyConstraints; friend class DataStoreFactory; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/storage/dbconfig.h b/src/server/storage/dbconfig.h index 2a16b3c5c..5e107b962 100644 --- a/src/server/storage/dbconfig.h +++ b/src/server/storage/dbconfig.h @@ -1,132 +1,134 @@ /* Copyright (c) 2010 Tobias Koenig 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 DBCONFIG_H #define DBCONFIG_H #include #include namespace Akonadi { namespace Server { /** * A base class that provides an unique access layer to configuration * and initialization of different database backends. */ class DbConfig { public: virtual ~DbConfig(); /** * Returns whether database have been configured. */ static bool isConfigured(); /** * Returns the DbConfig instance for the database the user has * configured. */ static DbConfig *configuredDatabase(); /** * Returns the name of the used driver. */ virtual QString driverName() const = 0; /** * Returns the database name. */ virtual QString databaseName() const = 0; /** * This method is called whenever the Akonadi server is started * and before the initial database connection is set up. * * At this point the default settings should be determined, merged * with the given @p settings and written back. */ virtual bool init(QSettings &settings) = 0; /** * This method applies the configured settings to the QtSql @p database * instance. */ virtual void apply(QSqlDatabase &database) = 0; /** * Do session setup/initialization work on @p database. * An example would be to run some SQL commands on every new session, * typically stuff like setting encodings, transaction isolation levels, etc. */ virtual void initSession(const QSqlDatabase &database); /** * Returns whether an internal server needs to be used. */ virtual bool useInternalServer() const = 0; /** * This method is called to start an external server. */ virtual bool startInternalServer(); /** * This method is called to stop the external server. */ virtual void stopInternalServer(); /** * Payload data bigger than this value will be stored in separate files, instead of the database. Valid * * @return the size threshold in bytes, defaults to 4096. */ virtual qint64 sizeThreshold() const; /** * This method is called to setup initial database settings after a connection is established. */ virtual void setup(); protected: DbConfig(); /** * Returns the suggested default database name, if none is specified in the configuration already. * This includes instance namespaces, so usually this is not necessary to use in combination * with internal databases (in process or using our own server instance). */ static QString defaultDatabaseName(); /** * Calls QProcess::execute() and also prints the command and arguments via qCDebug() */ int execute(const QString &cmd, const QStringList &args) const; private: + Q_DISABLE_COPY(DbConfig); + qint64 mSizeThreshold; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/storage/itemretrievaljob.h b/src/server/storage/itemretrievaljob.h index cf292e728..117ab3bbc 100644 --- a/src/server/storage/itemretrievaljob.h +++ b/src/server/storage/itemretrievaljob.h @@ -1,87 +1,87 @@ /* Copyright (c) 2009 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef ITEMRETRIEVALJOB_H #define ITEMRETRIEVALJOB_H #include #include "itemretrievalrequest.h" class QDBusPendingCallWatcher; class OrgFreedesktopAkonadiResourceInterface; namespace Akonadi { namespace Server { class ItemRetrievalRequest; class AbstractItemRetrievalJob : public QObject { Q_OBJECT public: AbstractItemRetrievalJob(ItemRetrievalRequest req, QObject *parent); ~AbstractItemRetrievalJob() override = default; virtual void start() = 0; virtual void kill() = 0; const ItemRetrievalRequest &request() const { return m_result.request; } const ItemRetrievalResult &result() const { return m_result; } Q_SIGNALS: - void requestCompleted(AbstractItemRetrievalJob *job); + void requestCompleted(Akonadi::Server::AbstractItemRetrievalJob *job); protected: ItemRetrievalResult m_result; }; /// Async D-Bus retrieval, no modification of the request (thus no need for locking) class ItemRetrievalJob : public AbstractItemRetrievalJob { Q_OBJECT public: ItemRetrievalJob(ItemRetrievalRequest req, QObject *parent) : AbstractItemRetrievalJob(std::move(req), parent) {} void setInterface(OrgFreedesktopAkonadiResourceInterface *interface) { m_interface = interface; } ~ItemRetrievalJob() override; void start() override; void kill() override; private Q_SLOTS: void callFinished(QDBusPendingCallWatcher *watcher); private: bool m_active = false; OrgFreedesktopAkonadiResourceInterface *m_interface = nullptr; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/storage/itemretrievalmanager.h b/src/server/storage/itemretrievalmanager.h index 199837f23..7e3a021d0 100644 --- a/src/server/storage/itemretrievalmanager.h +++ b/src/server/storage/itemretrievalmanager.h @@ -1,107 +1,110 @@ /* Copyright (c) 2009 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef AKONADI_ITEMRETRIEVALMANAGER_H #define AKONADI_ITEMRETRIEVALMANAGER_H #include "itemretriever.h" #include "itemretrievalrequest.h" #include "akthread.h" #include #include #include #include #include #include class OrgFreedesktopAkonadiResourceInterface; namespace Akonadi { namespace Server { class Collection; class ItemRetrievalJob; class AbstractItemRetrievalJob; class AbstractItemRetrievalJobFactory { public: virtual ~AbstractItemRetrievalJobFactory() = default; virtual AbstractItemRetrievalJob *retrievalJob(ItemRetrievalRequest request, QObject *parent) = 0; + +private: + Q_DISABLE_COPY_MOVE(AbstractItemRetrievalJobFactory) }; /** Manages and processes item retrieval requests. */ class ItemRetrievalManager : public AkThread { Q_OBJECT public: explicit ItemRetrievalManager(QObject *parent = nullptr); explicit ItemRetrievalManager(std::unique_ptr factory, QObject *parent = nullptr); ~ItemRetrievalManager() override; /** * Added for convenience. ItemRetrievalManager takes ownership over the * pointer and deletes it when the request is processed. */ virtual void requestItemDelivery(ItemRetrievalRequest request); void triggerCollectionSync(const QString &resource, qint64 colId); void triggerCollectionTreeSync(const QString &resource); Q_SIGNALS: void requestFinished(const Akonadi::Server::ItemRetrievalResult &result); void requestAdded(); private: OrgFreedesktopAkonadiResourceInterface *resourceInterface(const QString &id); QVector scheduleJobsForIdleResourcesLocked(); private Q_SLOTS: void init() override; void serviceOwnerChanged(const QString &serviceName, const QString &oldOwner, const QString &newOwner); void processRequest(); - void retrievalJobFinished(AbstractItemRetrievalJob *job); + void retrievalJobFinished(Akonadi::Server::AbstractItemRetrievalJob *job); protected: std::unique_ptr mJobFactory; /// Protects mPendingRequests and every Request object posted to it QReadWriteLock mLock; /// Used to let requesting threads wait until the request has been processed QWaitCondition mWaitCondition; /// Pending requests queues, one per resource std::unordered_map> mPendingRequests; /// Currently running jobs, one per resource QHash mCurrentJobs; // resource dbus interface cache std::unordered_map> mResourceInterfaces; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/storage/itemretrievalrequest.h b/src/server/storage/itemretrievalrequest.h index 842df0ba2..d8958eaaf 100644 --- a/src/server/storage/itemretrievalrequest.h +++ b/src/server/storage/itemretrievalrequest.h @@ -1,85 +1,85 @@ /* Copyright (c) 2009 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef ITEMRETRIEVALREQUEST_H #define ITEMRETRIEVALREQUEST_H #include #include #include #include #include namespace Akonadi { namespace Server { class ItemRetrievalRequest; /// Details of a single item retrieval request class ItemRetrievalRequest { public: struct Id { explicit Id(uint32_t value): mValue(value) {}; - bool operator==(const Id &other) const { return mValue == other.mValue; } + bool operator==(Id other) const { return mValue == other.mValue; } private: uint32_t mValue; Id next() { return Id{++mValue}; } friend class ItemRetrievalRequest; friend QDebug operator<<(QDebug, Id); }; explicit ItemRetrievalRequest(); Id id; QVector ids; QString resourceId; QByteArrayList parts; // list instead of vector to simplify client-side handling private: static Id lastId; }; class ItemRetrievalResult { public: explicit ItemRetrievalResult() = default; // don't use, sadly Qt metatype system requires type to be default-constructible ItemRetrievalResult(ItemRetrievalRequest request) : request(std::move(request)) {} ItemRetrievalRequest request; std::optional errorMsg{}; }; inline QDebug operator<<(QDebug dbg, ItemRetrievalRequest::Id id) { dbg.nospace() << id.mValue; return dbg.space(); } } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/storage/notificationcollector.h b/src/server/storage/notificationcollector.h index 86c3c6211..0a367764c 100644 --- a/src/server/storage/notificationcollector.h +++ b/src/server/storage/notificationcollector.h @@ -1,269 +1,271 @@ /* Copyright (c) 2006 - 2007 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef AKONADI_NOTIFICATIONCOLLECTOR_H #define AKONADI_NOTIFICATIONCOLLECTOR_H #include "entities.h" #include #include #include #include namespace Akonadi { namespace Server { class DataStore; class Connection; class AkonadiServer; /** Part of the DataStore, collects change notifications and emits them after the current transaction has been successfully committed. Where possible, notifications are compressed. */ class NotificationCollector { public: /** Create a new notification collector for the given DataStore @p db. @param db The datastore using this notification collector. */ explicit NotificationCollector(AkonadiServer &akonadi, DataStore *db); /** Destroys this notification collector. */ virtual ~NotificationCollector() = default; /** * Sets the connection that is causing the changes. */ void setConnection(Connection *connection); /** Notify about an added item. Provide as many parameters as you have at hand currently, everything that is missing will be looked up in the database later. */ void itemAdded(const PimItem &item, bool seen, const Collection &collection = Collection(), const QByteArray &resource = QByteArray()); /** Notify about a changed item. Provide as many parameters as you have at hand currently, everything that is missing will be looked up in the database later. */ void itemChanged(const PimItem &item, const QSet &changedParts, const Collection &collection = Collection(), const QByteArray &resource = QByteArray()); /** Notify about changed items flags Provide as many parameters as you have at hand currently, everything that is missing will be looked up in the database later. */ void itemsFlagsChanged(const PimItem::List &items, const QSet &addedFlags, const QSet &removedFlags, const Collection &collection = Collection(), const QByteArray &resource = QByteArray()); /** Notify about changed items tags **/ void itemsTagsChanged(const PimItem::List &items, const QSet &addedTags, const QSet &removedTags, const Collection &collection = Collection(), const QByteArray &resource = QByteArray()); /** Notify about changed items relations **/ void itemsRelationsChanged(const PimItem::List &items, const Relation::List &addedRelations, const Relation::List &removedRelations, const Collection &collection = Collection(), const QByteArray &resource = QByteArray()); /** Notify about moved items Provide as many parameters as you have at hand currently, everything that is missing will be looked up in the database later. */ void itemsMoved(const PimItem::List &items, const Collection &collectionSrc = Collection(), const Collection &collectionDest = Collection(), const QByteArray &sourceResource = QByteArray()); /** Notify about removed items. Make sure you either provide all parameters or call this function before actually removing the item from database. */ void itemsRemoved(const PimItem::List &items, const Collection &collection = Collection(), const QByteArray &resource = QByteArray()); /** * Notify about linked items */ void itemsLinked(const PimItem::List &items, const Collection &collection); /** * Notify about unlinked items. */ void itemsUnlinked(const PimItem::List &items, const Collection &collection); /** Notify about a added collection. Provide as many parameters as you have at hand currently, everything that is missing will be looked up in the database later. */ void collectionAdded(const Collection &collection, const QByteArray &resource = QByteArray()); /** Notify about a changed collection. Provide as many parameters as you have at hand currently, everything that is missing will be looked up in the database later. */ void collectionChanged(const Collection &collection, const QList &changes, const QByteArray &resource = QByteArray()); /** Notify about a moved collection. Provide as many parameters as you have at hand currently, everything missing will be looked up on demand in the database later. */ void collectionMoved(const Collection &collection, const Collection &source, const QByteArray &resource = QByteArray(), const QByteArray &destResource = QByteArray()); /** Notify about a removed collection. Make sure you either provide all parameters or call this function before actually removing the item from database. */ void collectionRemoved(const Collection &collection, const QByteArray &resource = QByteArray()); /** * Notify about a collection subscription. */ void collectionSubscribed(const Collection &collection, const QByteArray &resource = QByteArray()); /** * Notify about a collection unsubscription */ void collectionUnsubscribed(const Collection &collection, const QByteArray &resource = QByteArray()); /** Notify about an added tag. */ void tagAdded(const Tag &tag); /** Notify about a changed tag. */ void tagChanged(const Tag &tag); /** Notify about a removed tag. */ void tagRemoved(const Tag &tag, const QByteArray &resource, const QString &remoteId); /** Notify about an added relation. */ void relationAdded(const Relation &relation); /** Notify about a removed relation. */ void relationRemoved(const Relation &relation); /** Trigger sending of collected notifications. @returns Returns true when any notifications were dispatched, false if there were no pending notifications. */ bool dispatchNotifications(); private: void itemNotification(Protocol::ItemChangeNotification::Operation op, const PimItem::List &items, const Collection &collection, const Collection &collectionDest, const QByteArray &resource, const QSet &parts = QSet(), const QSet &addedFlags = QSet(), const QSet &removedFlags = QSet(), const QSet &addedTags = QSet(), const QSet &removedTags = QSet(), const Relation::List &addedRelations = Relation::List(), const Relation::List &removedRelations = Relation::List()); void itemNotification(Protocol::ItemChangeNotification::Operation op, const PimItem &item, const Collection &collection, const Collection &collectionDest, const QByteArray &resource, const QSet &parts = QSet()); void collectionNotification(Protocol::CollectionChangeNotification::Operation op, const Collection &collection, Collection::Id source, Collection::Id destination, const QByteArray &resource, const QSet &changes = QSet(), const QByteArray &destResource = QByteArray()); void tagNotification(Protocol::TagChangeNotification::Operation op, const Tag &tag, const QByteArray &resource = QByteArray(), const QString &remoteId = QString()); void relationNotification(Protocol::RelationChangeNotification::Operation op, const Relation &relation); void dispatchNotification(const Protocol::ChangeNotificationPtr &msg); void clear(); void completeNotification(const Protocol::ChangeNotificationPtr &msg); protected: virtual void notify(Protocol::ChangeNotificationList ntfs); private: + Q_DISABLE_COPY_MOVE(NotificationCollector); + DataStore *mDb; Connection *mConnection = nullptr; AkonadiServer &mAkonadi; bool mIgnoreTransactions = false; Protocol::ChangeNotificationList mNotifications; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/tracer.h b/src/server/tracer.h index 08d259316..75e4434ee 100644 --- a/src/server/tracer.h +++ b/src/server/tracer.h @@ -1,173 +1,173 @@ /*************************************************************************** * Copyright (C) 2006 by Tobias Koenig * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef AKONADI_TRACER_H #define AKONADI_TRACER_H #include #include #include #include #include "tracerinterface.h" #include #include class QSettings; namespace Akonadi { namespace Protocol { class Command; using CommandPtr = QSharedPointer; } namespace Server { /** * The global tracer instance where all akonadi components can * send their tracing information to. * * The tracer will forward these information to the configured backends. */ class Tracer : public QObject, public TracerInterface { Q_OBJECT public: explicit Tracer(); /** * Destroys the global tracer instance. */ ~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); } + /** + * Returns the currently activated tracer type. + */ + QString currentTracer() const; + public Q_SLOTS: /** * This method is called whenever a new data (imap) connection to the akonadi server * is established. * * @param identifier The unique identifier for this connection. All input and output * messages for this connection will have the same identifier. * * @param msg A message specific string. */ void beginConnection(const QString &identifier, const QString &msg) override; /** * This method is called whenever a data (imap) connection to akonadi server is * closed. * * @param identifier The unique identifier of this connection. * @param msg A message specific string. */ void endConnection(const QString &identifier, const QString &msg) override; /** * This method is called whenever the akonadi server retrieves some data from the * outside. * * @param identifier The unique identifier of the connection on which the data * is retrieved. * @param msg A message specific string. */ 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. * * @param identifier The unique identifier of the connection on which the * data is send. * @param msg A message specific string. */ 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. * * @param signalName The name of the signal being sent. * @param msg A message specific string. */ void signal(const QString &signalName, const QString &msg) override; /** Convenience method with internal toLatin1 cast to compile with QT_NO_CAST_FROM_ASCII. */ void signal(const char *signalName, const QString &msg); /** * This method is called whenever a component wants to output a warning. */ void warning(const QString &componentName, const QString &msg) override; /** * This method is called whenever a component wants to output an error. */ void error(const QString &componentName, const QString &msg) override; /** * Convenience method for QT_NO_CAST_FROM_ASCII usage. */ void error(const char *componentName, const QString &msg); - /** - * Returns the currently activated tracer type. - */ - QString currentTracer() const; - /** * Activates the given tracer type. */ void activateTracer(const QString &type); private: mutable QMutex mMutex; std::unique_ptr mTracerBackend; std::unique_ptr mSettings; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/tracerinterface.h b/src/server/tracerinterface.h index e6547ed27..2f9a26229 100644 --- a/src/server/tracerinterface.h +++ b/src/server/tracerinterface.h @@ -1,115 +1,116 @@ /*************************************************************************** * Copyright (C) 2006 by Tobias Koenig * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef AKONADI_TRACERINTERFACE_H #define AKONADI_TRACERINTERFACE_H class QByteArray; class QString; namespace Akonadi { namespace Server { /** * This interface can be reimplemented to deliver tracing information * of the akonadi server to the outside. * * Possible implementations: * - log file * - dbus signals * - live gui */ class TracerInterface { public: enum ConnectionFormat { DebugString, Json }; - virtual ~TracerInterface() - { - } + virtual ~TracerInterface() = default; /** * This method is called whenever a new data (imap) connection to the akonadi server * is established. * * @param identifier The unique identifier for this connection. All input and output * messages for this connection will have the same identifier. * * @param msg A message specific string. */ virtual void beginConnection(const QString &identifier, const QString &msg) = 0; /** * This method is called whenever a data (imap) connection to akonadi server is * closed. * * @param identifier The unique identifier of this connection. * @param msg A message specific string. */ virtual void endConnection(const QString &identifier, const QString &msg) = 0; /** * This method is called whenever the akonadi server retrieves some data from the * outside. * * @param identifier The unique identifier of the connection on which the data * is retrieved. * @param msg A message specific string. */ virtual void connectionInput(const QString &identifier, const QByteArray &msg) = 0; /** * This method is called whenever the akonadi server sends some data out to a client. * * @param identifier The unique identifier of the connection on which the * data is send. * @param msg A message specific string. */ virtual void connectionOutput(const QString &identifier, const QByteArray &msg) = 0; /** * This method is called whenever a dbus signal is emitted on the bus. * * @param signalName The name of the signal being sent. * @param msg A message specific string. */ virtual void signal(const QString &signalName, const QString &msg) = 0; /** * This method is called whenever a component wants to output a warning. */ virtual void warning(const QString &componentName, const QString &msg) = 0; /** * 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;} + +private: + Q_DISABLE_COPY_MOVE(TracerInterface); }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/shared/akremotelog.cpp b/src/shared/akremotelog.cpp index cb7a32bbc..aa26598ff 100644 --- a/src/shared/akremotelog.cpp +++ b/src/shared/akremotelog.cpp @@ -1,213 +1,213 @@ /* 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 #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; } static RemoteLogger *self() { return sInstance; } private Q_SLOTS: void serviceRegistered(const QString &service) { mAkonadiConsoleInterface = std::make_unique(service, QStringLiteral(AKONADICONSOLE_LOGGER_PATH), QStringLiteral(AKONADICONSOLE_LOGGER_INTERFACE), QDBusConnection::sessionBus(), this); if (!mAkonadiConsoleInterface->isValid()) { mAkonadiConsoleInterface.reset(); return; } - connect(mAkonadiConsoleInterface.get(), SIGNAL(enabledChanged(bool)), + connect(mAkonadiConsoleInterface.get(), SIGNAL(enabledChanged(bool)), // clazy:exclude=old-style-connect 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); mAkonadiConsoleInterface.reset(); } 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; std::unique_ptr mAkonadiConsoleInterface; 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"