diff --git a/CMakeLists.txt b/CMakeLists.txt index b0674e867..fd7e1c78a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,342 +1,360 @@ cmake_minimum_required(VERSION 3.5) set(PIM_VERSION "5.12.40") project(Akonadi VERSION ${PIM_VERSION}) if (MSVC) # On Windows our target compiler is MSVC 14.14, which supports C++17, so # we can enable it. MSVC does not ship experimental/optional and similar # and disallows use of C++17 classes in C++14 mode. set(CMAKE_CXX_STANDARD 17) else() # On Linux, MacOS and BSD we need to support older compilers that only # offer C++14. set(CMAKE_CXX_STANDARD 14) set(WITH_3RDPARTY_OPTIONAL TRUE) endif() # ECM setup set(KF5_MIN_VERSION "5.61.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(AkonadiMacros) set(QT_REQUIRED_VERSION "5.11.0") set(AKONADI_VERSION ${PIM_VERSION}) 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(KF5Completion ${KF5_MIN_VERSION} CONFIG 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(KF5DBusAddons ${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 ) set(USE_NEW_DESIGNERPLUGIN_MACRO false) if (ECM_VERSION VERSION_GREATER "5.61.0") option(BUILD_DESIGNERPLUGIN "Build plugin for Qt Designer" ON) add_feature_info(DESIGNERPLUGIN ${BUILD_DESIGNERPLUGIN} "Build plugin for Qt Designer") set(USE_NEW_DESIGNERPLUGIN_MACRO true) else() find_package(KF5DesignerPlugin ${KF5_MIN_VERSION} CONFIG) set_package_properties(KF5DesignerPlugin PROPERTIES DESCRIPTION "KF5 designer plugin" TYPE OPTIONAL) endif() 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() configure_file(akonaditests_export.h.in "${CMAKE_CURRENT_BINARY_DIR}/akonaditests_export.h") configure_file(akonadiwidgetstests_export.h.in "${CMAKE_CURRENT_BINARY_DIR}/akonadiwidgetstests_export.h") # Make sure the KF5Akonadi_DATA_DIR is absolute before passing it to KF5AkonadiConfig.cmake.in # otherwise build fails either on OSX CI, or for normal users 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 ) 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_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(akonadi-prefix.h.cmake ${Akonadi_BINARY_DIR}/akonadi-prefix.h) 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=0x060000) 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() ############### 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::) install(FILES akonadi.categories akonadi.renamecategories 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/config-akonadi.h.cmake b/config-akonadi.h.cmake index 42413c087..9063ec7eb 100644 --- a/config-akonadi.h.cmake +++ b/config-akonadi.h.cmake @@ -1,7 +1,9 @@ #cmakedefine HAVE_UNISTD_H 1 #cmakedefine HAVE_MALLOC_TRIM 1 #define AKONADI_DATABASE_BACKEND "@AKONADI_DATABASE_BACKEND@" #cmakedefine WITH_3RDPARTY_OPTIONAL 1 + +#cmakedefine WITH_ACCOUNTS 1 diff --git a/src/akonadicontrol/CMakeLists.txt b/src/akonadicontrol/CMakeLists.txt index cac9e3dab..df7c99be5 100644 --- a/src/akonadicontrol/CMakeLists.txt +++ b/src/akonadicontrol/CMakeLists.txt @@ -1,58 +1,69 @@ include_directories(BEFORE ${CMAKE_CURRENT_BINARY_DIR}) ########### next target ############### set(control_SRCS agenttype.cpp agentinstance.cpp agentprocessinstance.cpp agentthreadinstance.cpp agentmanager.cpp controlmanager.cpp main.cpp processcontrol.cpp ) +if (WITH_ACCOUNTS) + list(APPEND control_SRCS accountsintegration.cpp) +endif() + ecm_qt_declare_logging_category(control_SRCS HEADER akonadicontrol_debug.h IDENTIFIER AKONADICONTROL_LOG CATEGORY_NAME org.kde.pim.akonadicontrol) qt5_add_dbus_adaptor(control_SRCS ${Akonadi_SOURCE_DIR}/src/interfaces/org.freedesktop.Akonadi.AgentManager.xml agentmanager.h AgentManager) qt5_add_dbus_adaptor(control_SRCS ${Akonadi_SOURCE_DIR}/src/interfaces/org.freedesktop.Akonadi.ControlManager.xml controlmanager.h ControlManager) qt5_add_dbus_adaptor(control_SRCS ${Akonadi_SOURCE_DIR}/src/interfaces/org.freedesktop.Akonadi.AgentManagerInternal.xml agentmanager.h AgentManager) qt5_add_dbus_interfaces(control_SRCS ${Akonadi_SOURCE_DIR}/src/interfaces/org.freedesktop.Akonadi.Agent.Control.xml ${Akonadi_SOURCE_DIR}/src/interfaces/org.freedesktop.Akonadi.Agent.Status.xml ${Akonadi_SOURCE_DIR}/src/interfaces/org.freedesktop.Akonadi.Agent.Search.xml ${Akonadi_SOURCE_DIR}/src/interfaces/org.freedesktop.Akonadi.AgentServer.xml ${Akonadi_SOURCE_DIR}/src/interfaces/org.freedesktop.Akonadi.Resource.xml ${Akonadi_SOURCE_DIR}/src/interfaces/org.freedesktop.Akonadi.Preprocessor.xml ${Akonadi_SOURCE_DIR}/src/interfaces/org.freedesktop.Akonadi.Server.xml + ${Akonadi_SOURCE_DIR}/src/interfaces/org.kde.Akonadi.Accounts.xml ) qt5_add_dbus_interface(control_SRCS ${Akonadi_SOURCE_DIR}/src/interfaces/org.freedesktop.Akonadi.ResourceManager.xml resource_manager) qt5_add_dbus_interface(control_SRCS ${Akonadi_SOURCE_DIR}/src/interfaces/org.freedesktop.Akonadi.PreprocessorManager.xml preprocessor_manager) add_executable(akonadi_control ${control_SRCS}) set_target_properties(akonadi_control PROPERTIES MACOSX_BUNDLE FALSE) set_target_properties(akonadi_control PROPERTIES OUTPUT_NAME akonadi_control) if (WIN32) set_target_properties(akonadi_control PROPERTIES WIN32_EXECUTABLE TRUE) target_link_libraries(akonadi_control ${QT_QTMAIN_LIBRARY}) endif() target_link_libraries(akonadi_control akonadi_shared KF5AkonadiPrivate KF5::CoreAddons KF5::ConfigCore Qt5::Core Qt5::DBus Qt5::Gui ) +if (WITH_ACCOUNTS) + target_include_directories(akonadi_control PRIVATE ${ACCOUNTSQT_INCLUDE_DIRS}) + # We need Qt5::Xml because the Accounts framework leaks QDocument includes into public interface + target_link_libraries(akonadi_control ${ACCOUNTSQT_LIBRARIES} Qt5::Xml) +endif() + install(TARGETS akonadi_control ${KF5_INSTALL_TARGETS_DEFAULT_ARGS}) configure_file(org.freedesktop.Akonadi.Control.service.cmake ${CMAKE_CURRENT_BINARY_DIR}/org.freedesktop.Akonadi.Control.service) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/org.freedesktop.Akonadi.Control.service DESTINATION ${KDE_INSTALL_DBUSSERVICEDIR}) diff --git a/src/akonadicontrol/accountsintegration.cpp b/src/akonadicontrol/accountsintegration.cpp new file mode 100644 index 000000000..4fbe1c2bc --- /dev/null +++ b/src/akonadicontrol/accountsintegration.cpp @@ -0,0 +1,177 @@ +/*************************************************************************** + * Copyright (c) 2019 Daniel Vrátil * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Library General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "accountsintegration.h" +#include "agentmanager.h" +#include "akonadicontrol_debug.h" +#include "accountsinterface.h" + +#include + +#include +#include + +#include +#include + +using namespace Akonadi; +using namespace std::chrono_literals; + +namespace { + +static const auto akonadiAgentType = QStringLiteral("akonadi/agentType"); + +} + +AccountsIntegration::AccountsIntegration(AgentManager &agentManager) + : QObject() + , mAgentManager(agentManager) +{ + connect(&mAccountsManager, &Accounts::Manager::accountCreated, + this, &AccountsIntegration::onAccountAdded); + connect(&mAccountsManager, &Accounts::Manager::accountRemoved, + this, &AccountsIntegration::onAccountRemoved); + + const auto accounts = mAccountsManager.accountList(); + for (const auto account : accounts) { + connect(mAccountsManager.account(account), &Accounts::Account::enabledChanged, + this, &AccountsIntegration::onAccountServiceEnabled); + } +} + +akOptional AccountsIntegration::agentForAccount(const QString &agentType, Accounts::AccountId accountId) const +{ + const auto instances = mAgentManager.agentInstances(); + for (const auto &identifier : instances) { + if (mAgentManager.agentInstanceType(identifier) == agentType) { + const auto serviceName = Akonadi::DBus::agentServiceName(identifier, Akonadi::DBus::Resource); + org::kde::Akonadi::Accounts accountsIface(serviceName, QStringLiteral("/Accounts"), QDBusConnection::sessionBus()); + if (!accountsIface.isValid()) { + continue; + } + + if (accountsIface.getAccountId() == accountId) { + return identifier; + } + } + } + return nullopt; +} + +void AccountsIntegration::configureAgentInstance(const QString &identifier, Accounts::AccountId accountId, int attempt) +{ + const auto serviceName = Akonadi::DBus::agentServiceName(identifier, Akonadi::DBus::Resource); + org::kde::Akonadi::Accounts accountsIface(serviceName, QStringLiteral("/Accounts"), QDBusConnection::sessionBus()); + if (!accountsIface.isValid()) { + if (attempt >= 3) { + qCWarning(AKONADICONTROL_LOG) << "The resource" << identifier << "does not provide the Accounts DBus interface. Will remove the agent"; + mAgentManager.removeAgentInstance(identifier); + } else { + QTimer::singleShot(2s, this, [this, identifier, accountId, attempt]() { + configureAgentInstance(identifier, accountId, attempt + 1); + }); + } + return; + } + + accountsIface.setAccountId(accountId); + qCDebug(AKONADICONTROL_LOG) << "Configured resource" << identifier << "for account" << accountId; +} + +void AccountsIntegration::createAgent(const QString &agentType, Accounts::AccountId accountId) +{ + const auto identifier = mAgentManager.createAgentInstance(agentType); + qCDebug(AKONADICONTROL_LOG) << "Created resource" << identifier << "for account" << accountId; + configureAgentInstance(identifier, accountId); +} + +void AccountsIntegration::removeAgentInstance(const QString &identifier) +{ + mAgentManager.removeAgentInstance(identifier); +} + +void AccountsIntegration::onAccountAdded(Accounts::AccountId accId) +{ + qCDebug(AKONADICONTROL_LOG) << "Online account ID" << accId << "added."; + auto account = mAccountsManager.account(accId); + if (!account || !account->isEnabled()) { + return; + } + + const auto enabledServices = account->enabledServices(); + for (const auto &service : enabledServices) { + account->selectService(service); + const auto agentType = account->valueAsString(akonadiAgentType); + if (agentType.isEmpty()) { + continue; // doesn't support Akonadi :-( + } + const auto agent = agentForAccount(agentType, accId); + if (!agent.has_value()) { + createAgent(agentType, account->id()); + } + // Always go through all services, there may be more! + } + account->selectService(); + + connect(account, &Accounts::Account::enabledChanged, + this, &AccountsIntegration::onAccountServiceEnabled); +} + +void AccountsIntegration::onAccountRemoved(Accounts::AccountId accId) +{ + qCDebug(AKONADICONTROL_LOG) << "Online account ID" << accId << "removed."; + + const auto instances = mAgentManager.agentInstances(); + for (const auto &instance : instances) { + const auto serviceName = DBus::agentServiceName(instance, DBus::Resource); + org::kde::Akonadi::Accounts accountIface(serviceName, QStringLiteral("/Accounts"), QDBusConnection::sessionBus()); + if (!accountIface.isValid()) { + continue; + } + + if (accountIface.getAccountId() == accId) { + removeAgentInstance(instance); + } + } +} + +void AccountsIntegration::onAccountServiceEnabled(const QString &serviceType, bool enabled) +{ + if (serviceType.isEmpty()) { + return; + } + + auto account = qobject_cast(sender()); + qCDebug(AKONADICONTROL_LOG) << "Online account ID" << account->id() << "service" << serviceType << "has been" << (enabled ? "enabled" : "disabled"); + + const auto service = mAccountsManager.service(serviceType); + account->selectService(service); + const auto agentType = account->valueAsString(akonadiAgentType); + account->selectService(); + if (agentType.isEmpty()) { + return; // this service does not support Akonadi (yet -;) + } + + const auto identifier = agentForAccount(agentType, account->id()); + if (enabled && !identifier.has_value()) { + createAgent(agentType, account->id()); + } else if (!enabled && identifier.has_value()) { + removeAgentInstance(identifier.value()); + } +} diff --git a/src/akonadicontrol/accountsintegration.h b/src/akonadicontrol/accountsintegration.h new file mode 100644 index 000000000..7884b34e0 --- /dev/null +++ b/src/akonadicontrol/accountsintegration.h @@ -0,0 +1,65 @@ +/*************************************************************************** + * Copyright (c) 2019 Daniel Vrátil * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Library General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef ACCOUNTSINTEGRATION_H_ +#define ACCOUNTSINTEGRATION_H_ + +#include +#include + +#include + +#include + +namespace Accounts +{ +class Account; +class Service; +} + +class AgentManager; + +class AccountsIntegration: public QObject +{ + Q_OBJECT +public: + explicit AccountsIntegration(AgentManager &agentManager); + +private Q_SLOTS: + void onAccountAdded(Accounts::AccountId); + void onAccountRemoved(Accounts::AccountId); + void onAccountServiceEnabled(const QString &service, bool enabled); + +private: + void configureAgentInstance(const QString &identifier, Accounts::AccountId accountId, int attempt = 0); + Akonadi::akOptional agentForAccount(const QString &agentType, Accounts::AccountId accountId) const; + void createAgent(const QString &agentType, Accounts::AccountId accountId); + void removeAgentInstance(const QString &identifier); + void loadSupportedServices(); + + AgentManager &mAgentManager; + Accounts::Manager mAccountsManager; + + + QMap mSupportedServices; +}; + + + +#endif diff --git a/src/akonadicontrol/main.cpp b/src/akonadicontrol/main.cpp index 261ca5dec..55d0f2b3c 100644 --- a/src/akonadicontrol/main.cpp +++ b/src/akonadicontrol/main.cpp @@ -1,89 +1,97 @@ /*************************************************************************** * Copyright (C) 2006 by Tobias Koenig * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "agentmanager.h" #include "controlmanager.h" #include "processcontrol.h" #include "akonadicontrol_debug.h" #include "akonadi_version.h" +#include "config-akonadi.h" + +#ifdef WITH_ACCOUNTS +#include "accountsintegration.h" +#endif #include #include #include #include #include #include #include #include #ifdef HAVE_UNISTD_H # include #endif static AgentManager *sAgentManager = nullptr; void crashHandler(int) { if (sAgentManager) { sAgentManager->cleanup(); } exit(255); } int main(int argc, char **argv) { AkUniqueGuiApplication app(argc, argv, Akonadi::DBus::serviceName(Akonadi::DBus::ControlLock), AKONADICONTROL_LOG()); app.setDescription(QStringLiteral("Akonadi Control Process\nDo not run this manually, use 'akonadictl' instead to start/stop Akonadi.")); KAboutData aboutData(QStringLiteral("akonadi_control"), QStringLiteral("Akonadi Control"), QStringLiteral(AKONADI_VERSION_STRING), QStringLiteral("Akonadi Control"), KAboutLicense::LGPL_V2); KAboutData::setApplicationData(aboutData); app.parseCommandLine(); // older Akonadi server versions don't use the lock service yet, so check if one is already running before we try to start another one // TODO: Remove this legacy check? if (QDBusConnection::sessionBus().interface()->isServiceRegistered(Akonadi::DBus::serviceName(Akonadi::DBus::Control))) { qCWarning(AKONADICONTROL_LOG) << "Another Akonadi control process is already running."; return -1; } ControlManager controlManager; AgentManager agentManager(app.commandLineArguments().isSet(QStringLiteral("verbose"))); +#ifdef WITH_ACCOUNTS + AccountsIntegration accountsIntegration(agentManager); +#endif KCrash::setEmergencySaveFunction(crashHandler); QGuiApplication::setFallbackSessionManagementEnabled(false); // akonadi_control is started on-demand, no need to auto restart by session. auto disableSessionManagement = [](QSessionManager & sm) { sm.setRestartHint(QSessionManager::RestartNever); }; QObject::connect(qApp, &QGuiApplication::commitDataRequest, disableSessionManagement); QObject::connect(qApp, &QGuiApplication::saveStateRequest, disableSessionManagement); return app.exec(); } diff --git a/src/interfaces/org.kde.Akonadi.Accounts.xml b/src/interfaces/org.kde.Akonadi.Accounts.xml new file mode 100644 index 000000000..b8ae7a999 --- /dev/null +++ b/src/interfaces/org.kde.Akonadi.Accounts.xml @@ -0,0 +1,11 @@ + + + + + + + + + + +