diff --git a/autotests/private/akdbustest.cpp b/autotests/private/akdbustest.cpp index 5d61c0bb6..8ed4cdb36 100644 --- a/autotests/private/akdbustest.cpp +++ b/autotests/private/akdbustest.cpp @@ -1,97 +1,96 @@ /* Copyright (c) 2011 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include using namespace Akonadi; Q_DECLARE_METATYPE(DBus::AgentType) class DBusTest : public QObject { Q_OBJECT private Q_SLOTS: void testServiceName() { akTestSetInstanceIdentifier(QString()); QCOMPARE(DBus::serviceName(DBus::Server), QLatin1String("org.freedesktop.Akonadi")); akTestSetInstanceIdentifier(QStringLiteral("foo")); QCOMPARE(DBus::serviceName(DBus::Server), QLatin1String("org.freedesktop.Akonadi.foo")); } void testParseAgentServiceName_data() { QTest::addColumn("instanceId"); QTest::addColumn("serviceName"); QTest::addColumn("agentId"); QTest::addColumn("agentType"); // generic invalid QTest::newRow("empty") << QString() << QString() << QString() << DBus::Unknown; QTest::newRow("wrong base") << QString() << "org.freedesktop.Agent.foo" << QString() << DBus::Unknown; QTest::newRow("wrong type") << QString() << "org.freedesktop.Akonadi.Randomizer.akonadi_maildir_resource_0" << QString() << DBus::Unknown; QTest::newRow("too long") << QString() << "org.freedesktop.Akonadi.Agent.akonadi_maildir_resource_0.foo.bar" << QString() << DBus::Unknown; // single instance cases QTest::newRow("agent, no multi-instance") << QString() << "org.freedesktop.Akonadi.Agent.akonadi_maildir_resource_0" << "akonadi_maildir_resource_0" << DBus::Agent; QTest::newRow("resource, no multi-instance") << QString() << "org.freedesktop.Akonadi.Resource.akonadi_maildir_resource_0" << "akonadi_maildir_resource_0" << DBus::Resource; QTest::newRow("preproc, no multi-instance") << QString() << "org.freedesktop.Akonadi.Preprocessor.akonadi_maildir_resource_0" << "akonadi_maildir_resource_0" << DBus::Preprocessor; QTest::newRow("multi-instance name in single-instance setup") << QString() << "org.freedesktop.Akonadi.Agent.akonadi_maildir_resource_0.foo" << QString() << DBus::Unknown; // multi-instance cases QTest::newRow("agent, multi-instance") << "foo" << "org.freedesktop.Akonadi.Agent.akonadi_maildir_resource_0.foo" << "akonadi_maildir_resource_0" << DBus::Agent; QTest::newRow("resource, multi-instance") << "foo" << "org.freedesktop.Akonadi.Resource.akonadi_maildir_resource_0.foo" << "akonadi_maildir_resource_0" << DBus::Resource; QTest::newRow("preproc, multi-instance") << "foo" << "org.freedesktop.Akonadi.Preprocessor.akonadi_maildir_resource_0.foo" << "akonadi_maildir_resource_0" << DBus::Preprocessor; QTest::newRow("single-instance name in multi-instance setup") << "foo" << "org.freedesktop.Akonadi.Agent.akonadi_maildir_resource_0" << QString() << DBus::Unknown; } void testParseAgentServiceName() { QFETCH(QString, instanceId); QFETCH(QString, serviceName); QFETCH(QString, agentId); QFETCH(DBus::AgentType, agentType); akTestSetInstanceIdentifier(instanceId); - DBus::AgentType parsedType; - QString parsedName = DBus::parseAgentServiceName(serviceName, parsedType); - - QCOMPARE(parsedName, agentId); - QCOMPARE(parsedType, agentType); + const auto service = DBus::parseAgentServiceName(serviceName); + QVERIFY(service.has_value()); + QCOMPARE(service->serviceName, agentId); + QCOMPARE(service->agentType, agentType); } void testAgentServiceName() { akTestSetInstanceIdentifier(QString()); QCOMPARE(DBus::agentServiceName(QLatin1String("akonadi_maildir_resource_0"), DBus::Agent), QLatin1String("org.freedesktop.Akonadi.Agent.akonadi_maildir_resource_0")); akTestSetInstanceIdentifier(QStringLiteral("foo")); QCOMPARE(DBus::agentServiceName(QLatin1String("akonadi_maildir_resource_0"), DBus::Agent), QLatin1String("org.freedesktop.Akonadi.Agent.akonadi_maildir_resource_0.foo")); } }; AKTEST_MAIN(DBusTest) #include "akdbustest.moc" diff --git a/src/3rdparty/CMakeLists.txt b/src/3rdparty/CMakeLists.txt index b90cad0e3..f96be79dc 100644 --- a/src/3rdparty/CMakeLists.txt +++ b/src/3rdparty/CMakeLists.txt @@ -1,3 +1,2 @@ -# Empty for now - -# Don't add Optional, it has install() rules that we don't want (yet) +# Don't add Optional subdir, it has install() rules that we don't want +install(FILES Optional/optional.hpp DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/AkonadiCore/) diff --git a/src/akonadicontrol/agentmanager.cpp b/src/akonadicontrol/agentmanager.cpp index d3d1ded98..b2b98eb47 100644 --- a/src/akonadicontrol/agentmanager.cpp +++ b/src/akonadicontrol/agentmanager.cpp @@ -1,867 +1,869 @@ /*************************************************************************** * Copyright (C) 2006 by Tobias Koenig * * Copyright (c) 2007 Volker Krause * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "agentmanager.h" #include "agentmanageradaptor.h" #include "agentmanagerinternaladaptor.h" #include "agentprocessinstance.h" #include "agentserverinterface.h" #include "agentthreadinstance.h" #include "preprocessor_manager.h" #include "processcontrol.h" #include "resource_manager.h" #include "serverinterface.h" #include "akonadicontrol_debug.h" #include #include #include #include #include #include #include #include #include #include using Akonadi::ProcessControl; static const bool enableAgentServerDefault = false; AgentManager::AgentManager(bool verbose, QObject *parent) : QObject(parent) , mAgentServer(nullptr) , mVerbose(verbose) { new AgentManagerAdaptor(this); new AgentManagerInternalAdaptor(this); QDBusConnection::sessionBus().registerObject(QStringLiteral("/AgentManager"), this); connect(QDBusConnection::sessionBus().interface(), &QDBusConnectionInterface::serviceOwnerChanged, this, &AgentManager::serviceOwnerChanged); if (QDBusConnection::sessionBus().interface()->isServiceRegistered(Akonadi::DBus::serviceName(Akonadi::DBus::Server))) { qFatal("akonadiserver already running!"); } const QSettings settings(Akonadi::StandardDirs::agentsConfigFile(Akonadi::StandardDirs::ReadOnly), QSettings::IniFormat); mAgentServerEnabled = settings.value(QStringLiteral("AgentServer/Enabled"), enableAgentServerDefault).toBool(); QStringList serviceArgs; if (Akonadi::Instance::hasIdentifier()) { serviceArgs << QStringLiteral("--instance") << Akonadi::Instance::identifier(); } if (verbose) { serviceArgs << QStringLiteral("--verbose"); } mStorageController = new Akonadi::ProcessControl; mStorageController->setShutdownTimeout(15 * 1000); // the server needs more time for shutdown if we are using an internal mysqld connect(mStorageController, &Akonadi::ProcessControl::unableToStart, this, &AgentManager::serverFailure); mStorageController->start(QStringLiteral("akonadiserver"), serviceArgs, Akonadi::ProcessControl::RestartOnCrash); if (mAgentServerEnabled) { mAgentServer = new Akonadi::ProcessControl; connect(mAgentServer, &Akonadi::ProcessControl::unableToStart, this, &AgentManager::agentServerFailure); mAgentServer->start(QStringLiteral("akonadi_agent_server"), serviceArgs, Akonadi::ProcessControl::RestartOnCrash); } } void AgentManager::continueStartup() { // prevent multiple calls in case the server has to be restarted static bool first = true; if (!first) { return; } first = false; readPluginInfos(); for (const AgentType &info : qAsConst(mAgents)) { Q_EMIT agentTypeAdded(info.identifier); } load(); for (const AgentType &info : qAsConst(mAgents)) { ensureAutoStart(info); } // register the real service name once everything is up an running if (!QDBusConnection::sessionBus().registerService(Akonadi::DBus::serviceName(Akonadi::DBus::Control))) { // besides a race with an older Akonadi server I have no idea how we could possibly get here... qFatal("Unable to register service as %s despite having the lock. Error was: %s", qPrintable(Akonadi::DBus::serviceName(Akonadi::DBus::Control)), qPrintable(QDBusConnection::sessionBus().lastError().message())); } qCInfo(AKONADICONTROL_LOG) << "Akonadi server is now operational."; } AgentManager::~AgentManager() { cleanup(); } void AgentManager::cleanup() { for (const AgentInstance::Ptr &instance : qAsConst(mAgentInstances)) { instance->quit(); } mAgentInstances.clear(); mStorageController->setCrashPolicy(ProcessControl::StopOnCrash); org::freedesktop::Akonadi::Server *serverIface = new org::freedesktop::Akonadi::Server(Akonadi::DBus::serviceName(Akonadi::DBus::Server), QStringLiteral("/Server"), QDBusConnection::sessionBus(), this); serverIface->quit(); if (mAgentServer) { mAgentServer->setCrashPolicy(ProcessControl::StopOnCrash); org::freedesktop::Akonadi::AgentServer *agentServerIface = new org::freedesktop::Akonadi::AgentServer(Akonadi::DBus::serviceName(Akonadi::DBus::AgentServer), QStringLiteral("/AgentServer"), QDBusConnection::sessionBus(), this); agentServerIface->quit(); } delete mStorageController; mStorageController = nullptr; delete mAgentServer; mAgentServer = nullptr; } QStringList AgentManager::agentTypes() const { return mAgents.keys(); } QString AgentManager::agentName(const QString &identifier) const { if (!checkAgentExists(identifier)) { return QString(); } return mAgents.value(identifier).name; } QString AgentManager::agentComment(const QString &identifier) const { if (!checkAgentExists(identifier)) { return QString(); } return mAgents.value(identifier).comment; } QString AgentManager::agentIcon(const QString &identifier) const { if (!checkAgentExists(identifier)) { return QString(); } const AgentType info = mAgents.value(identifier); if (!info.icon.isEmpty()) { return info.icon; } return QStringLiteral("application-x-executable"); } QStringList AgentManager::agentMimeTypes(const QString &identifier) const { if (!checkAgentExists(identifier)) { return QStringList(); } return mAgents.value(identifier).mimeTypes; } QStringList AgentManager::agentCapabilities(const QString &identifier) const { if (!checkAgentExists(identifier)) { return QStringList(); } return mAgents.value(identifier).capabilities; } QVariantMap AgentManager::agentCustomProperties(const QString &identifier) const { if (!checkAgentExists(identifier)) { return QVariantMap(); } return mAgents.value(identifier).custom; } AgentInstance::Ptr AgentManager::createAgentInstance(const AgentType &info) { switch (info.launchMethod) { case AgentType::Server: return AgentInstance::Ptr(new Akonadi::AgentThreadInstance(this)); case AgentType::Launcher: // Fall through case AgentType::Process: return AgentInstance::Ptr(new Akonadi::AgentProcessInstance(this)); default: Q_ASSERT_X(false, "AgentManger::createAgentInstance", "Unhandled AgentType::LaunchMethod case"); } return AgentInstance::Ptr(); } QString AgentManager::createAgentInstance(const QString &identifier) { if (!checkAgentExists(identifier)) { return QString(); } const AgentType agentInfo = mAgents.value(identifier); mAgents[identifier].instanceCounter++; const AgentInstance::Ptr instance = createAgentInstance(agentInfo); if (agentInfo.capabilities.contains(AgentType::CapabilityUnique)) { instance->setIdentifier(identifier); } else { instance->setIdentifier(QStringLiteral("%1_%2").arg(identifier, QString::number(agentInfo.instanceCounter))); } const QString instanceIdentifier = instance->identifier(); if (mAgentInstances.contains(instanceIdentifier)) { qCWarning(AKONADICONTROL_LOG) << "Cannot create another instance of agent" << identifier; return QString(); } // Return from this dbus call before we do the next. Otherwise dbus brakes for // this process. if (calledFromDBus()) { connection().send(message().createReply(instanceIdentifier)); } if (!instance->start(agentInfo)) { return QString(); } mAgentInstances.insert(instanceIdentifier, instance); registerAgentAtServer(instanceIdentifier, agentInfo); save(); return instanceIdentifier; } void AgentManager::removeAgentInstance(const QString &identifier) { const AgentInstance::Ptr instance = mAgentInstances.value(identifier); if (!instance) { qCWarning(AKONADICONTROL_LOG) << Q_FUNC_INFO << "Agent instance with identifier" << identifier << "does not exist"; return; } if (instance->hasAgentInterface()) { instance->cleanup(); } else { qCWarning(AKONADICONTROL_LOG) << "Agent instance" << identifier << "has no interface!"; } mAgentInstances.remove(identifier); save(); org::freedesktop::Akonadi::ResourceManager resmanager(Akonadi::DBus::serviceName(Akonadi::DBus::Server), QStringLiteral("/ResourceManager"), QDBusConnection::sessionBus(), this); resmanager.removeResourceInstance(instance->identifier()); // Kill the preprocessor instance, if any. org::freedesktop::Akonadi::PreprocessorManager preProcessorManager( Akonadi::DBus::serviceName(Akonadi::DBus::Server), QStringLiteral("/PreprocessorManager"), QDBusConnection::sessionBus(), this); preProcessorManager.unregisterInstance(instance->identifier()); if (instance->hasAgentInterface()) { qCDebug(AKONADICONTROL_LOG) << "AgentManager::removeAgentInstance: calling instance->quit()"; instance->quit(); } else { qCWarning(AKONADICONTROL_LOG) << "Agent instance" << identifier << "has no interface!"; } Q_EMIT agentInstanceRemoved(identifier); } QString AgentManager::agentInstanceType(const QString &identifier) { const AgentInstance::Ptr agent = mAgentInstances.value(identifier); if (!agent) { qCWarning(AKONADICONTROL_LOG) << "Agent instance with identifier" << identifier << "does not exist"; return QString(); } return agent->agentType(); } QStringList AgentManager::agentInstances() const { return mAgentInstances.keys(); } int AgentManager::agentInstanceStatus(const QString &identifier) const { if (!checkInstance(identifier)) { return 2; } return mAgentInstances.value(identifier)->status(); } QString AgentManager::agentInstanceStatusMessage(const QString &identifier) const { if (!checkInstance(identifier)) { return QString(); } return mAgentInstances.value(identifier)->statusMessage(); } uint AgentManager::agentInstanceProgress(const QString &identifier) const { if (!checkInstance(identifier)) { return 0; } return mAgentInstances.value(identifier)->progress(); } QString AgentManager::agentInstanceProgressMessage(const QString &identifier) const { Q_UNUSED(identifier); return QString(); } void AgentManager::agentInstanceConfigure(const QString &identifier, qlonglong windowId) { if (!checkAgentInterfaces(identifier, QStringLiteral("agentInstanceConfigure"))) { return; } mAgentInstances.value(identifier)->configure(windowId); } bool AgentManager::agentInstanceOnline(const QString &identifier) { if (!checkInstance(identifier)) { return false; } return mAgentInstances.value(identifier)->isOnline(); } void AgentManager::setAgentInstanceOnline(const QString &identifier, bool state) { if (!checkAgentInterfaces(identifier, QStringLiteral("setAgentInstanceOnline"))) { return; } mAgentInstances.value(identifier)->statusInterface()->setOnline(state); } // resource specific methods // void AgentManager::setAgentInstanceName(const QString &identifier, const QString &name) { if (!checkResourceInterface(identifier, QStringLiteral("setAgentInstanceName"))) { return; } mAgentInstances.value(identifier)->resourceInterface()->setName(name); } QString AgentManager::agentInstanceName(const QString &identifier) const { if (!checkInstance(identifier)) { return QString(); } const AgentInstance::Ptr instance = mAgentInstances.value(identifier); if (!instance->resourceName().isEmpty()) { return instance->resourceName(); } if (!checkAgentExists(instance->agentType())) { return QString(); } return mAgents.value(instance->agentType()).name; } void AgentManager::agentInstanceSynchronize(const QString &identifier) { if (!checkResourceInterface(identifier, QStringLiteral("agentInstanceSynchronize"))) { return; } mAgentInstances.value(identifier)->resourceInterface()->synchronize(); } void AgentManager::agentInstanceSynchronizeCollectionTree(const QString &identifier) { if (!checkResourceInterface(identifier, QStringLiteral("agentInstanceSynchronizeCollectionTree"))) { return; } mAgentInstances.value(identifier)->resourceInterface()->synchronizeCollectionTree(); } void AgentManager::agentInstanceSynchronizeCollection(const QString &identifier, qint64 collection) { agentInstanceSynchronizeCollection(identifier, collection, false); } void AgentManager::agentInstanceSynchronizeCollection(const QString &identifier, qint64 collection, bool recursive) { if (!checkResourceInterface(identifier, QStringLiteral("agentInstanceSynchronizeCollection"))) { return; } mAgentInstances.value(identifier)->resourceInterface()->synchronizeCollection(collection, recursive); } void AgentManager::agentInstanceSynchronizeTags(const QString &identifier) { if (!checkResourceInterface(identifier, QStringLiteral("agentInstanceSynchronizeTags"))) { return; } mAgentInstances.value(identifier)->resourceInterface()->synchronizeTags(); } void AgentManager::agentInstanceSynchronizeRelations(const QString &identifier) { if (!checkResourceInterface(identifier, QStringLiteral("agentInstanceSynchronizeRelations"))) { return; } mAgentInstances.value(identifier)->resourceInterface()->synchronizeRelations(); } void AgentManager::restartAgentInstance(const QString &identifier) { if (!checkInstance(identifier)) { return; } mAgentInstances.value(identifier)->restartWhenIdle(); } void AgentManager::updatePluginInfos() { const QHash oldInfos = mAgents; readPluginInfos(); for (const AgentType &oldInfo : oldInfos) { if (!mAgents.contains(oldInfo.identifier)) { Q_EMIT agentTypeRemoved(oldInfo.identifier); } } for (const AgentType &newInfo : qAsConst(mAgents)) { if (!oldInfos.contains(newInfo.identifier)) { Q_EMIT agentTypeAdded(newInfo.identifier); ensureAutoStart(newInfo); } } } void AgentManager::readPluginInfos() { mAgents.clear(); const QStringList pathList = pluginInfoPathList(); for (const QString &path : pathList) { const QDir directory(path, QStringLiteral("*.desktop")); readPluginInfos(directory); } } void AgentManager::readPluginInfos(const QDir &directory) { const QStringList files = directory.entryList(); qCDebug(AKONADICONTROL_LOG) << "PLUGINS: " << directory.canonicalPath(); qCDebug(AKONADICONTROL_LOG) << "PLUGINS: " << files; for (int i = 0; i < files.count(); ++i) { const QString fileName = directory.absoluteFilePath(files[i]); AgentType agentInfo; if (agentInfo.load(fileName, this)) { if (mAgents.contains(agentInfo.identifier)) { qCWarning(AKONADICONTROL_LOG) << "Duplicated agent identifier" << agentInfo.identifier << "from file" << fileName; continue; } const QString disableAutostart = akGetEnv("AKONADI_DISABLE_AGENT_AUTOSTART"); if (!disableAutostart.isEmpty()) { qCDebug(AKONADICONTROL_LOG) << "Autostarting of agents is disabled."; agentInfo.capabilities.removeOne(AgentType::CapabilityAutostart); } if (!mAgentServerEnabled && agentInfo.launchMethod == AgentType::Server) { agentInfo.launchMethod = AgentType::Launcher; } if (agentInfo.launchMethod == AgentType::Process) { const QString executable = Akonadi::StandardDirs::findExecutable(agentInfo.exec); if (executable.isEmpty()) { qCWarning(AKONADICONTROL_LOG) << "Executable" << agentInfo.exec << "for agent" << agentInfo.identifier << "could not be found!"; continue; } } qCDebug(AKONADICONTROL_LOG) << "PLUGINS inserting: " << agentInfo.identifier << agentInfo.instanceCounter << agentInfo.capabilities; mAgents.insert(agentInfo.identifier, agentInfo); } } } QStringList AgentManager::pluginInfoPathList() { return Akonadi::StandardDirs::locateAllResourceDirs(QStringLiteral("akonadi/agents")); } void AgentManager::load() { org::freedesktop::Akonadi::ResourceManager resmanager(Akonadi::DBus::serviceName(Akonadi::DBus::Server), QStringLiteral("/ResourceManager"), QDBusConnection::sessionBus(), this); const QStringList knownResources = resmanager.resourceInstances(); QSettings file(Akonadi::StandardDirs::agentsConfigFile(Akonadi::StandardDirs::ReadOnly), QSettings::IniFormat); file.beginGroup(QStringLiteral("Instances")); const QStringList entries = file.childGroups(); for (int i = 0; i < entries.count(); ++i) { const QString instanceIdentifier = entries[i]; if (mAgentInstances.contains(instanceIdentifier)) { qCWarning(AKONADICONTROL_LOG) << "Duplicated instance identifier" << instanceIdentifier << "found in agentsrc"; continue; } file.beginGroup(entries[i]); const QString agentType = file.value(QStringLiteral("AgentType")).toString(); if (!mAgents.contains(agentType)) { qCWarning(AKONADICONTROL_LOG) << "Reference to unknown agent type" << agentType << "in agentsrc"; file.endGroup(); continue; } const AgentType type = mAgents.value(agentType); // recover if the db has been deleted in the meantime or got otherwise corrupted if (!knownResources.contains(instanceIdentifier) && type.capabilities.contains(AgentType::CapabilityResource)) { qCDebug(AKONADICONTROL_LOG) << "Recovering instance" << instanceIdentifier << "after database loss"; registerAgentAtServer(instanceIdentifier, type); } const AgentInstance::Ptr instance = createAgentInstance(type); instance->setIdentifier(instanceIdentifier); if (instance->start(type)) { mAgentInstances.insert(instanceIdentifier, instance); } file.endGroup(); } file.endGroup(); } void AgentManager::save() { QSettings file(Akonadi::StandardDirs::agentsConfigFile(Akonadi::StandardDirs::WriteOnly), QSettings::IniFormat); for (const AgentType &info : qAsConst(mAgents)) { info.save(&file); } file.beginGroup(QStringLiteral("Instances")); file.remove(QString()); for (const AgentInstance::Ptr &instance : qAsConst(mAgentInstances)) { file.beginGroup(instance->identifier()); file.setValue(QStringLiteral("AgentType"), instance->agentType()); file.endGroup(); } file.endGroup(); } void AgentManager::serviceOwnerChanged(const QString &name, const QString &oldOwner, const QString &newOwner) { Q_UNUSED(oldOwner); // This is called by the D-Bus server when a service comes up, goes down or changes ownership for some reason // and this is where we "hook up" our different Agent interfaces. //qCDebug(AKONADICONTROL_LOG) << "Service " << name << " owner changed from " << oldOwner << " to " << newOwner; if ((name == Akonadi::DBus::serviceName(Akonadi::DBus::Server) || name == Akonadi::DBus::serviceName(Akonadi::DBus::AgentServer)) && !newOwner.isEmpty()) { if (QDBusConnection::sessionBus().interface()->isServiceRegistered(Akonadi::DBus::serviceName(Akonadi::DBus::Server)) && (!mAgentServer || QDBusConnection::sessionBus().interface()->isServiceRegistered(Akonadi::DBus::serviceName(Akonadi::DBus::AgentServer)))) { // server is operational, start agents continueStartup(); } } - Akonadi::DBus::AgentType agentType = Akonadi::DBus::Unknown; - const QString agentIdentifier = Akonadi::DBus::parseAgentServiceName(name, agentType); - switch (agentType) { + const auto service = Akonadi::DBus::parseAgentServiceName(name); + if (!service.has_value()) { + return; + } + switch (service->agentType) { case Akonadi::DBus::Agent: { // An agent service went up or down if (newOwner.isEmpty()) { return; // It went down: we don't care here. } - if (!mAgentInstances.contains(agentIdentifier)) { + if (!mAgentInstances.contains(service->serviceName)) { return; } - const AgentInstance::Ptr instance = mAgentInstances.value(agentIdentifier); + const AgentInstance::Ptr instance = mAgentInstances.value(service->serviceName); const bool restarting = instance->hasAgentInterface(); if (!instance->obtainAgentInterface()) { return; } if (!restarting) { - Q_EMIT agentInstanceAdded(agentIdentifier); + Q_EMIT agentInstanceAdded(service->serviceName); } break; } case Akonadi::DBus::Resource: { // A resource service went up or down if (newOwner.isEmpty()) { return; // It went down: we don't care here. } - if (!mAgentInstances.contains(agentIdentifier)) { + if (!mAgentInstances.contains(service->serviceName)) { return; } - mAgentInstances.value(agentIdentifier)->obtainResourceInterface(); + mAgentInstances.value(service->serviceName)->obtainResourceInterface(); break; } case Akonadi::DBus::Preprocessor: { // A preprocessor service went up or down // If the preprocessor is going up then the org.freedesktop.Akonadi.Agent.* interface // should be already up (as it's registered before the preprocessor one). // So if we don't know about the preprocessor as agent instance // then it's not our preprocessor. // If the preprocessor is going down then either the agent interface already // went down (and it has been already unregistered on the manager side) // or it's still registered as agent and WE have to unregister it. // The order of interface deletions depends on Qt but we handle both cases. // Check if we "know" about it. - qCDebug(AKONADICONTROL_LOG) << "Preprocessor " << agentIdentifier << " is going up or down..."; + qCDebug(AKONADICONTROL_LOG) << "Preprocessor " << service->serviceName << " is going up or down..."; - if (!mAgentInstances.contains(agentIdentifier)) { + if (!mAgentInstances.contains(service->serviceName)) { qCDebug(AKONADICONTROL_LOG) << "But it isn't registered as agent... not mine (anymore?)"; return; // not our agent (?) } org::freedesktop::Akonadi::PreprocessorManager preProcessorManager( Akonadi::DBus::serviceName(Akonadi::DBus::Server), QStringLiteral("/PreprocessorManager"), QDBusConnection::sessionBus(), this); if (!preProcessorManager.isValid()) { qCWarning(AKONADICONTROL_LOG) << "Could not connect to PreprocessorManager via D-Bus:" << preProcessorManager.lastError().message(); } else { if (newOwner.isEmpty()) { // The preprocessor went down. Unregister it on server side. - preProcessorManager.unregisterInstance(agentIdentifier); + preProcessorManager.unregisterInstance(service->serviceName); } else { // The preprocessor went up. Register it on server side. - if (!mAgentInstances.value(agentIdentifier)->obtainPreprocessorInterface()) { + if (!mAgentInstances.value(service->serviceName)->obtainPreprocessorInterface()) { // Hm.. couldn't hook up its preprocessor interface.. // Make sure we don't have it in the preprocessor chain - qCWarning(AKONADICONTROL_LOG) << "Couldn't obtain preprocessor interface for instance" << agentIdentifier; + qCWarning(AKONADICONTROL_LOG) << "Couldn't obtain preprocessor interface for instance" << service->serviceName; - preProcessorManager.unregisterInstance(agentIdentifier); + preProcessorManager.unregisterInstance(service->serviceName); return; } - qCDebug(AKONADICONTROL_LOG) << "Registering preprocessor instance" << agentIdentifier; + qCDebug(AKONADICONTROL_LOG) << "Registering preprocessor instance" << service->serviceName; // Add to the preprocessor chain - preProcessorManager.registerInstance(agentIdentifier); + preProcessorManager.registerInstance(service->serviceName); } } break; } default: break; } } bool AgentManager::checkInstance(const QString &identifier) const { if (!mAgentInstances.contains(identifier)) { qCWarning(AKONADICONTROL_LOG) << "Agent instance with identifier " << identifier << " does not exist"; return false; } return true; } bool AgentManager::checkResourceInterface(const QString &identifier, const QString &method) const { if (!checkInstance(identifier)) { return false; } if (!mAgents[mAgentInstances[identifier]->agentType()].capabilities.contains(QStringLiteral("Resource"))) { return false; } if (!mAgentInstances[identifier]->hasResourceInterface()) { qCWarning(AKONADICONTROL_LOG) << QLatin1String("AgentManager::") + method << " Agent instance " << identifier << " has no resource interface!"; return false; } return true; } bool AgentManager::checkAgentExists(const QString &identifier) const { if (!mAgents.contains(identifier)) { qCWarning(AKONADICONTROL_LOG) << "Agent instance " << identifier << " does not exist."; return false; } return true; } bool AgentManager::checkAgentInterfaces(const QString &identifier, const QString &method) const { if (!checkInstance(identifier)) { return false; } if (!mAgentInstances.value(identifier)->hasAgentInterface()) { qCWarning(AKONADICONTROL_LOG) << "Agent instance (" << method << ") " << identifier << " has no agent interface."; return false; } return true; } void AgentManager::ensureAutoStart(const AgentType &info) { if (!info.capabilities.contains(AgentType::CapabilityAutostart)) { return; // no an autostart agent } org::freedesktop::Akonadi::AgentServer agentServer(Akonadi::DBus::serviceName(Akonadi::DBus::AgentServer), QStringLiteral("/AgentServer"), QDBusConnection::sessionBus(), this); if (mAgentInstances.contains(info.identifier) || (agentServer.isValid() && agentServer.started(info.identifier))) { return; // already running } const AgentInstance::Ptr instance = createAgentInstance(info); instance->setIdentifier(info.identifier); if (instance->start(info)) { mAgentInstances.insert(instance->identifier(), instance); registerAgentAtServer(instance->identifier(), info); save(); } } void AgentManager::agentExeChanged(const QString &fileName) { if (!QFile::exists(fileName)) { return; } for (const AgentType &type : qAsConst(mAgents)) { if (fileName.endsWith(type.exec)) { for (const AgentInstance::Ptr &instance : qAsConst(mAgentInstances)) { if (instance->agentType() == type.identifier) { instance->restartWhenIdle(); } } } } } void AgentManager::registerAgentAtServer(const QString &agentIdentifier, const AgentType &type) { if (type.capabilities.contains(AgentType::CapabilityResource)) { QScopedPointer resmanager( new org::freedesktop::Akonadi::ResourceManager(Akonadi::DBus::serviceName(Akonadi::DBus::Server), QStringLiteral("/ResourceManager"), QDBusConnection::sessionBus(), this)); resmanager->addResourceInstance(agentIdentifier, type.capabilities); } } void AgentManager::addSearch(const QString &query, const QString &queryLanguage, qint64 resultCollectionId) { qCDebug(AKONADICONTROL_LOG) << "AgentManager::addSearch" << query << queryLanguage << resultCollectionId; for (const AgentInstance::Ptr &instance : qAsConst(mAgentInstances)) { const AgentType type = mAgents.value(instance->agentType()); if (type.capabilities.contains(AgentType::CapabilitySearch) && instance->searchInterface()) { instance->searchInterface()->addSearch(query, queryLanguage, resultCollectionId); } } } void AgentManager::removeSearch(quint64 resultCollectionId) { qCDebug(AKONADICONTROL_LOG) << "AgentManager::removeSearch" << resultCollectionId; for (const AgentInstance::Ptr &instance : qAsConst(mAgentInstances)) { const AgentType type = mAgents.value(instance->agentType()); if (type.capabilities.contains(AgentType::CapabilitySearch) && instance->searchInterface()) { instance->searchInterface()->removeSearch(resultCollectionId); } } } void AgentManager::agentServerFailure() { qCCritical(AKONADICONTROL_LOG) << "Failed to start AgentServer!"; // if ( requiresAgentServer ) // QCoreApplication::instance()->exit( 255 ); } void AgentManager::serverFailure() { QCoreApplication::instance()->exit(255); } diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 64061be62..74ffe7c28 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -1,351 +1,352 @@ set(akonadicore_base_SRCS agentconfigurationbase.cpp agentconfigurationfactorybase.cpp agentconfigurationmanager.cpp agentinstance.cpp agentmanager.cpp agenttype.cpp asyncselectionhandler.cpp attribute.cpp attributefactory.cpp attributestorage.cpp braveheart.cpp cachepolicy.cpp changemediator_p.cpp changenotification.cpp changenotificationdependenciesfactory.cpp changerecorder.cpp changerecorder_p.cpp changerecorderjournal.cpp connection.cpp collection.cpp collectioncolorattribute.cpp collectionfetchscope.cpp collectionpathresolver.cpp collectionquotaattribute.cpp collectionquotaattribute.cpp collectionrightsattribute.cpp collectionstatistics.cpp collectionsync.cpp conflicthandler.cpp collectionidentificationattribute.cpp control.cpp entityannotationsattribute.cpp entitycache.cpp entitydeletedattribute.cpp entitydeletedattribute.cpp entitydisplayattribute.cpp entityhiddenattribute.cpp exception.cpp favoritecollectionattribute.cpp firstrun.cpp gidextractor.cpp indexpolicyattribute.cpp item.cpp itemchangelog.cpp itemfetchscope.cpp itemmonitor.cpp itemserializer.cpp itemserializerplugin.cpp itemsync.cpp mimetypechecker.cpp monitor.cpp monitor_p.cpp newmailnotifierattribute.cpp notificationsource_p.cpp notificationsubscriber.cpp partfetcher.cpp pastehelper.cpp persistentsearchattribute.cpp pluginloader.cpp pop3resourceattribute.cpp protocolhelper.cpp remotelog.cpp relation.cpp relationsync.cpp searchquery.cpp servermanager.cpp session.cpp sessionthread.cpp specialcollectionattribute.cpp specialcollections.cpp tag.cpp tagattribute.cpp tagfetchscope.cpp tagsync.cpp trashsettings.cpp typepluginloader.cpp ) ecm_generate_headers(AkonadiCore_base_HEADERS HEADER_NAMES AbstractDifferencesReporter AgentConfigurationBase AgentConfigurationFactoryBase AgentInstance AgentManager AgentType Attribute AttributeFactory CachePolicy ChangeNotification ChangeRecorder Collection CollectionColorAttribute CollectionFetchScope CollectionQuotaAttribute CollectionStatistics CollectionUtils CollectionIdentificationAttribute Control DifferencesAlgorithmInterface EntityAnnotationsAttribute EntityDeletedAttribute EntityDisplayAttribute EntityHiddenAttribute ExceptionBase FavoriteCollectionAttribute GidExtractorInterface IndexPolicyAttribute Item ItemFetchScope ItemMonitor ItemSerializerPlugin ItemSync MimeTypeChecker NewMailNotifierAttribute NotificationSubscriber Monitor PartFetcher PersistentSearchAttribute Pop3ResourceAttribute Relation SearchQuery ServerManager Session SpecialCollections SpecialCollectionAttribute Supertrait Tag TagAttribute TagFetchScope TrashSettings CollectionPathResolver REQUIRED_HEADERS AkonadiCore_base_HEADERS ) set(akonadicore_models_SRCS models/agentfilterproxymodel.cpp models/agentinstancemodel.cpp models/agenttypemodel.cpp models/collectionfilterproxymodel.cpp models/collectionmodel.cpp models/collectionmodel_p.cpp models/entitymimetypefiltermodel.cpp models/entityorderproxymodel.cpp models/entityrightsfiltermodel.cpp models/entitytreemodel.cpp models/entitytreemodel_p.cpp models/favoritecollectionsmodel.cpp models/itemmodel.cpp models/recursivecollectionfilterproxymodel.cpp models/selectionproxymodel.cpp models/statisticsproxymodel.cpp models/subscriptionmodel.cpp models/tagmodel.cpp models/tagmodel_p.cpp models/trashfilterproxymodel.cpp ) ecm_generate_headers(AkonadiCore_models_HEADERS HEADER_NAMES AgentFilterProxyModel AgentInstanceModel AgentTypeModel CollectionFilterProxyModel EntityMimeTypeFilterModel EntityOrderProxyModel EntityRightsFilterModel EntityTreeModel FavoriteCollectionsModel ItemModel RecursiveCollectionFilterProxyModel SelectionProxyModel StatisticsProxyModel TagModel TrashFilterProxyModel REQUIRED_HEADERS AkonadiCore_models_HEADERS RELATIVE models ) set(akonadicore_jobs_SRCS jobs/agentinstancecreatejob.cpp jobs/collectionattributessynchronizationjob.cpp jobs/collectioncopyjob.cpp jobs/collectioncreatejob.cpp jobs/collectiondeletejob.cpp jobs/collectionfetchjob.cpp jobs/collectionmodifyjob.cpp jobs/collectionmovejob.cpp jobs/collectionstatisticsjob.cpp jobs/invalidatecachejob.cpp jobs/itemcopyjob.cpp jobs/itemcreatejob.cpp jobs/itemdeletejob.cpp jobs/itemfetchjob.cpp jobs/itemmodifyjob.cpp jobs/itemmovejob.cpp jobs/itemsearchjob.cpp jobs/job.cpp jobs/kjobprivatebase.cpp jobs/linkjob.cpp jobs/recursiveitemfetchjob.cpp jobs/resourceselectjob.cpp jobs/resourcesynchronizationjob.cpp jobs/relationfetchjob.cpp jobs/relationcreatejob.cpp jobs/relationdeletejob.cpp jobs/searchcreatejob.cpp jobs/searchresultjob.cpp jobs/specialcollectionsdiscoveryjob.cpp jobs/specialcollectionshelperjobs.cpp jobs/specialcollectionsrequestjob.cpp jobs/subscriptionjob.cpp jobs/tagcreatejob.cpp jobs/tagdeletejob.cpp jobs/tagfetchjob.cpp jobs/tagmodifyjob.cpp jobs/transactionjobs.cpp jobs/transactionsequence.cpp jobs/trashjob.cpp jobs/trashrestorejob.cpp jobs/unlinkjob.cpp ) ecm_generate_headers(AkonadiCore_jobs_HEADERS HEADER_NAMES AgentInstanceCreateJob CollectionAttributesSynchronizationJob CollectionCopyJob CollectionCreateJob CollectionDeleteJob CollectionFetchJob CollectionModifyJob CollectionMoveJob CollectionStatisticsJob ItemCopyJob ItemCreateJob ItemDeleteJob ItemFetchJob ItemModifyJob ItemMoveJob ItemSearchJob Job LinkJob RecursiveItemFetchJob ResourceSynchronizationJob RelationFetchJob RelationCreateJob RelationDeleteJob SearchCreateJob SpecialCollectionsDiscoveryJob SpecialCollectionsRequestJob TagCreateJob TagDeleteJob TagFetchJob TagModifyJob TransactionJobs TransactionSequence TrashJob TrashRestoreJob UnlinkJob REQUIRED_HEADERS AkonadiCore_jobs_HEADERS RELATIVE jobs ) set(akonadicore_dbus_xml ${Akonadi_SOURCE_DIR}/src/interfaces/org.freedesktop.Akonadi.NotificationManager.xml) qt5_add_dbus_interface(akonadicore_dbus_SRCS ${akonadicore_dbus_xml} notificationmanagerinterface) set(akonadicore_dbus_xml ${Akonadi_SOURCE_DIR}/src/interfaces/org.freedesktop.Akonadi.NotificationSource.xml) set_source_files_properties(${akonadicore_dbus_xml} PROPERTIES INCLUDE "${Akonadi_SOURCE_DIR}/src/private/protocol_p.h" ) qt5_add_dbus_interface(akonadicore_dbus_SRCS ${akonadicore_dbus_xml} notificationsourceinterface) qt5_add_dbus_interfaces(akonadicore_dbus_SRCS ${Akonadi_SOURCE_DIR}/src/interfaces/org.freedesktop.Akonadi.AgentManager.xml) qt5_add_dbus_interfaces(akonadicore_dbus_SRCS ${Akonadi_SOURCE_DIR}/src/interfaces/org.freedesktop.Akonadi.Tracer.xml) qt5_add_dbus_interfaces(akonadicore_dbus_SRCS ${Akonadi_SOURCE_DIR}/src/interfaces/org.freedesktop.Akonadi.Agent.Control.xml) set(akonadicore_SRCS ${akonadicore_base_SRCS} ${akonadicore_jobs_SRCS} ${akonadicore_models_SRCS} ${akonadicore_dbus_SRCS} ) ecm_qt_declare_logging_category(akonadicore_SRCS HEADER akonadicore_debug.h IDENTIFIER AKONADICORE_LOG CATEGORY_NAME org.kde.pim.akonadicore) add_library(KF5AkonadiCore ${akonadicore_SRCS}) generate_export_header(KF5AkonadiCore BASE_NAME akonadicore) add_library(KF5::AkonadiCore ALIAS KF5AkonadiCore) target_include_directories(KF5AkonadiCore INTERFACE "$") target_include_directories(KF5AkonadiCore PUBLIC "$") target_include_directories(KF5AkonadiCore PUBLIC "$") target_include_directories(KF5AkonadiCore PUBLIC "$") kde_target_enable_exceptions(KF5AkonadiCore PUBLIC) target_link_libraries(KF5AkonadiCore PUBLIC KF5::CoreAddons # for KJob KF5::ItemModels Qt5::Gui # for QColor PRIVATE Qt5::Network Qt5::Widgets KF5::AkonadiPrivate KF5::DBusAddons KF5::I18n KF5::IconThemes KF5::ConfigCore KF5AkonadiPrivate akonadi_shared ) set_target_properties(KF5AkonadiCore PROPERTIES VERSION ${AKONADI_VERSION_STRING} SOVERSION ${AKONADI_SOVERSION} EXPORT_NAME AkonadiCore ) ecm_generate_pri_file(BASE_NAME AkonadiCore LIB_NAME KF5AkonadiCore DEPS "ItemModels CoreAddons" FILENAME_VAR PRI_FILENAME ) install(TARGETS KF5AkonadiCore EXPORT KF5AkonadiTargets ${KF5_INSTALL_TARGETS_DEFAULT_ARGS} ) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/akonadicore_export.h ${AkonadiCore_base_HEADERS} ${AkonadiCore_models_HEADERS} ${AkonadiCore_jobs_HEADERS} ${AkonadiCore_HEADERS} qtest_akonadi.h itempayloadinternals_p.h + ${Akonadi_BINARY_DIR}/config-akonadi.h DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/AkonadiCore COMPONENT Devel ) install(FILES ${PRI_FILENAME} DESTINATION ${ECM_MKSPECS_INSTALL_DIR} ) install( FILES kcfg2dbus.xsl DESTINATION ${KDE_INSTALL_DATADIR_KF5}/akonadi ) diff --git a/src/private/CMakeLists.txt b/src/private/CMakeLists.txt index 3f6bd72ae..13ae4303a 100644 --- a/src/private/CMakeLists.txt +++ b/src/private/CMakeLists.txt @@ -1,106 +1,107 @@ add_subdirectory(protocolgen) if(NOT XMLLINT_EXECUTABLE) message(STATUS "xmllint not found, skipping protocol.xml validation") else() add_test(AkonadiPrivate-protocol-xmllint ${XMLLINT_EXECUTABLE} --noout ${CMAKE_CURRENT_SOURCE_DIR}/protocol.xml) endif() add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/protocol_gen.cpp ${CMAKE_CURRENT_BINARY_DIR}/protocol_gen.h COMMAND protocolgen ${CMAKE_CURRENT_SOURCE_DIR}/protocol.xml DEPENDS protocolgen ${CMAKE_CURRENT_SOURCE_DIR}/protocol.xml COMMENT "Generating Protocol implementation" ) add_custom_target(generate_protocol DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/protocol_gen.cpp) set(akonadiprivate_SRCS imapparser.cpp imapset.cpp instance.cpp datastream_p.cpp externalpartstorage.cpp protocol.cpp scope.cpp tristate.cpp standarddirs.cpp dbus.cpp ) set(akonadiprivate_LIBS PUBLIC Qt5::Core Qt5::DBus ) if (WIN32) set(akonadiprivate_LIBS ${akonadiprivate_LIBS} PRIVATE Qt5::Network ) endif() ecm_qt_declare_logging_category(akonadiprivate_SRCS HEADER akonadiprivate_debug.h IDENTIFIER AKONADIPRIVATE_LOG CATEGORY_NAME org.kde.pim.akonadiprivate) if (WIN32) # MSVC does not like when the same object files are reused for shared and # static linking, so in this case we build all sources twice to make it happy set(akonadiprivate_buildsources ${akonadiprivate_SRCS}) else() add_library(akonadiprivate_obj OBJECT ${akonadiprivate_SRCS}) target_include_directories(akonadiprivate_obj PUBLIC "$") + target_include_directories(akonadiprivate_obj PRIVATE "$") target_include_directories(akonadiprivate_obj PUBLIC "$") set_target_properties(akonadiprivate_obj PROPERTIES POSITION_INDEPENDENT_CODE 1) add_dependencies(akonadiprivate_obj generate_protocol) set(akonadiprivate_buildsources $) endif() add_library(KF5AkonadiPrivate SHARED ${akonadiprivate_buildsources}) add_library(KF5::AkonadiPrivate ALIAS KF5AkonadiPrivate) if (WIN32) add_dependencies(KF5AkonadiPrivate generate_protocol) endif() target_link_libraries(KF5AkonadiPrivate ${akonadiprivate_LIBS}) generate_export_header(KF5AkonadiPrivate BASE_NAME akonadiprivate) set_target_properties(KF5AkonadiPrivate PROPERTIES VERSION ${AKONADI_VERSION_STRING} SOVERSION ${AKONADI_SOVERSION} EXPORT_NAME AkonadiPrivate ) install(TARGETS KF5AkonadiPrivate EXPORT KF5AkonadiTargets ${KF5_INSTALL_TARGETS_DEFAULT_ARGS} ) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/akonadiprivate_export.h standarddirs_p.h dbus_p.h imapparser_p.h imapset_p.h instance_p.h externalpartstorage_p.h protocol_p.h ${CMAKE_CURRENT_BINARY_DIR}/protocol_gen.h protocol_exception_p.h capabilities_p.h scope_p.h tristate_p.h DESTINATION ${KF5_INCLUDE_INSTALL_DIR}/akonadi/private ) ### Private static library used by unit-tests #### add_library(akonadiprivate_static STATIC ${akonadiprivate_buildsources}) if (WIN32) add_dependencies(akonadiprivate_static generate_protocol) endif() set_target_properties(akonadiprivate_static PROPERTIES COMPILE_FLAGS -DAKONADIPRIVATE_STATIC_DEFINE ) target_link_libraries(akonadiprivate_static ${akonadiprivate_LIBS}) diff --git a/src/private/dbus.cpp b/src/private/dbus.cpp index 9b8a672e3..ac46d5827 100644 --- a/src/private/dbus.cpp +++ b/src/private/dbus.cpp @@ -1,112 +1,110 @@ /* Copyright (c) 2011 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "dbus_p.h" #include "instance_p.h" #include #include using namespace Akonadi; #define AKONADI_DBUS_SERVER_SERVICE "org.freedesktop.Akonadi" #define AKONADI_DBUS_CONTROL_SERVICE "org.freedesktop.Akonadi.Control" #define AKONADI_DBUS_CONTROL_SERVICE_LOCK "org.freedesktop.Akonadi.Control.lock" #define AKONADI_DBUS_AGENTSERVER_SERVICE "org.freedesktop.Akonadi.AgentServer" #define AKONADI_DBUS_STORAGEJANITOR_SERVICE "org.freedesktop.Akonadi.Janitor" #define AKONADI_DBUS_SERVER_SERVICE_UPGRADING "org.freedesktop.Akonadi.upgrading" static QString makeServiceName(const char *base) { if (!Instance::hasIdentifier()) { return QLatin1String(base); } return QLatin1String(base) % QLatin1Literal(".") % Instance::identifier(); } QString DBus::serviceName(DBus::ServiceType serviceType) { switch (serviceType) { case Server: return makeServiceName(AKONADI_DBUS_SERVER_SERVICE); case Control: return makeServiceName(AKONADI_DBUS_CONTROL_SERVICE); case ControlLock: return makeServiceName(AKONADI_DBUS_CONTROL_SERVICE_LOCK); case AgentServer: return makeServiceName(AKONADI_DBUS_AGENTSERVER_SERVICE); case StorageJanitor: return makeServiceName(AKONADI_DBUS_STORAGEJANITOR_SERVICE); case UpgradeIndicator: return makeServiceName(AKONADI_DBUS_SERVER_SERVICE_UPGRADING); } Q_ASSERT(!"WTF?"); return QString(); } -QString DBus::parseAgentServiceName(const QString &serviceName, DBus::AgentType &agentType) +akOptional DBus::parseAgentServiceName(const QString &serviceName) { - agentType = Unknown; if (!serviceName.startsWith(QLatin1String("org.freedesktop.Akonadi."))) { - return QString(); + return nullopt; } const QStringList parts = serviceName.mid(24).split(QLatin1Char('.')); if ((parts.size() == 2 && !Akonadi::Instance::hasIdentifier()) || (parts.size() == 3 && Akonadi::Instance::hasIdentifier() && Akonadi::Instance::identifier() == parts.at(2))) { // switch on parts.at( 0 ) const QString &partFirst = parts.constFirst(); if (partFirst == QLatin1String("Agent")) { - agentType = Agent; + return AgentService{parts.at(1), DBus::Agent}; } else if (partFirst == QLatin1String("Resource")) { - agentType = Resource; + return AgentService{parts.at(1), DBus::Resource}; } else if (partFirst == QLatin1String("Preprocessor")) { - agentType = Preprocessor; + return AgentService{parts.at(1), DBus::Preprocessor}; } else { - return QString(); + return nullopt; } - return parts.at(1); } - return QString(); + return nullopt; } QString DBus::agentServiceName(const QString &agentIdentifier, DBus::AgentType agentType) { Q_ASSERT(!agentIdentifier.isEmpty()); Q_ASSERT(agentType != Unknown); QString serviceName = QStringLiteral("org.freedesktop.Akonadi."); switch (agentType) { case Agent: serviceName += QLatin1String("Agent."); break; case Resource: serviceName += QLatin1String("Resource."); break; case Preprocessor: serviceName += QLatin1String("Preprocessor."); break; default: Q_ASSERT(!"WTF?"); } serviceName += agentIdentifier; if (Akonadi::Instance::hasIdentifier()) { serviceName += QLatin1Char('.') % Akonadi::Instance::identifier(); } return serviceName; } diff --git a/src/private/dbus_p.h b/src/private/dbus_p.h index 38b0c29eb..851ecb0c3 100644 --- a/src/private/dbus_p.h +++ b/src/private/dbus_p.h @@ -1,83 +1,89 @@ /* Copyright (c) 2011 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef AKONADI_DBUS_H #define AKONADI_DBUS_H #include "akonadiprivate_export.h" -class QString; +#include + +#include "akoptional.h" /** * Helper methods for obtaining D-Bus identifiers. * This should be used instead of hardcoded identifiers or constants to support multi-instance namespacing * @since 1.7 */ #define AKONADI_DBUS_AGENTMANAGER_PATH "/AgentManager" #define AKONADI_DBUS_AGENTSERVER_PATH "/AgentServer" #define AKONADI_DBUS_STORAGEJANITOR_PATH "/Janitor" namespace Akonadi { namespace DBus { /** D-Bus service types used by the Akonadi server processes. */ enum ServiceType { Server, Control, ControlLock, AgentServer, StorageJanitor, UpgradeIndicator }; /** * Returns the service name for the given @p serviceType. */ AKONADIPRIVATE_EXPORT QString serviceName(ServiceType serviceType); /** Known D-Bus service name types for agents. */ enum AgentType { Unknown, Agent, Resource, Preprocessor }; +struct AgentService { + QString serviceName{}; + DBus::AgentType agentType{DBus::Unknown}; +}; + /** * Parses a D-Bus service name and checks if it belongs to an agent of this instance. * @param serviceName The service name to parse. - * @param agentType Output parameter containing the agent type. * @return The identifier of the agent, empty string if that's not an agent (or an agent of a different Akonadi instance) */ -AKONADIPRIVATE_EXPORT QString parseAgentServiceName(const QString &serviceName, DBus::AgentType &agentType); +AKONADIPRIVATE_EXPORT akOptional parseAgentServiceName(const QString &serviceName); /** * Returns the D-Bus service name of the agent @p agentIdentifier for type @p agentType. */ -AKONADIPRIVATE_EXPORT QString agentServiceName(const QString &agentIdentifier, DBus::AgentType agentType); +AKONADIPRIVATE_EXPORT QString agentServiceName(const QString &agentIdentifier, DBus::AgentType agentType); } } #endif diff --git a/src/server/storage/itemretrievalmanager.cpp b/src/server/storage/itemretrievalmanager.cpp index 2ca5dcdd6..001348f00 100644 --- a/src/server/storage/itemretrievalmanager.cpp +++ b/src/server/storage/itemretrievalmanager.cpp @@ -1,259 +1,258 @@ /* Copyright (c) 2009 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "itemretrievalmanager.h" #include "itemretrievalrequest.h" #include "itemretrievaljob.h" #include "dbusconnectionpool.h" #include "akonadiserver_debug.h" #include "resourceinterface.h" #include #include #include #include #include #include using namespace Akonadi; using namespace Akonadi::Server; ItemRetrievalManager *ItemRetrievalManager::sInstance = nullptr; class ItemRetrievalJobFactory : public AbstractItemRetrievalJobFactory { AbstractItemRetrievalJob *retrievalJob(ItemRetrievalRequest *request, QObject *parent) override { return new ItemRetrievalJob(request, parent); } }; ItemRetrievalManager::ItemRetrievalManager(QObject *parent) : ItemRetrievalManager(std::make_unique(), parent) { } ItemRetrievalManager::ItemRetrievalManager(std::unique_ptr factory, QObject *parent) : AkThread(QStringLiteral("ItemRetrievalManager"), QThread::HighPriority, parent) , mJobFactory(std::move(factory)) { qDBusRegisterMetaType(); Q_ASSERT(sInstance == nullptr); sInstance = this; mLock = new QReadWriteLock(); mWaitCondition = new QWaitCondition(); } ItemRetrievalManager::~ItemRetrievalManager() { quitThread(); delete mWaitCondition; delete mLock; sInstance = nullptr; } void ItemRetrievalManager::init() { AkThread::init(); QDBusConnection conn = DBusConnectionPool::threadConnection(); connect(conn.interface(), &QDBusConnectionInterface::serviceOwnerChanged, this, &ItemRetrievalManager::serviceOwnerChanged); connect(this, &ItemRetrievalManager::requestAdded, this, &ItemRetrievalManager::processRequest, Qt::QueuedConnection); } ItemRetrievalManager *ItemRetrievalManager::instance() { Q_ASSERT(sInstance); return sInstance; } // called within the retrieval thread void ItemRetrievalManager::serviceOwnerChanged(const QString &serviceName, const QString &oldOwner, const QString &newOwner) { Q_UNUSED(newOwner); if (oldOwner.isEmpty()) { return; } - DBus::AgentType type = DBus::Unknown; - const QString resourceId = DBus::parseAgentServiceName(serviceName, type); - if (resourceId.isEmpty() || type != DBus::Resource) { + const auto service = DBus::parseAgentServiceName(serviceName); + if (!service.has_value() || service->agentType != DBus::Resource) { return; } qCDebug(AKONADISERVER_LOG) << "ItemRetrievalManager lost connection to resource" << serviceName << ", discarding cached interface"; - mResourceInterfaces.remove(resourceId); + mResourceInterfaces.remove(service->serviceName); } // called within the retrieval thread org::freedesktop::Akonadi::Resource *ItemRetrievalManager::resourceInterface(const QString &id) { if (id.isEmpty()) { return nullptr; } org::freedesktop::Akonadi::Resource *iface = mResourceInterfaces.value(id); if (iface && iface->isValid()) { return iface; } delete iface; iface = new org::freedesktop::Akonadi::Resource(DBus::agentServiceName(id, DBus::Resource), QStringLiteral("/"), DBusConnectionPool::threadConnection(), this); if (!iface || !iface->isValid()) { qCCritical(AKONADISERVER_LOG, "Cannot connect to agent instance with identifier '%s', error message: '%s'", qUtf8Printable(id), qUtf8Printable(iface ? iface->lastError().message() : QString())); delete iface; return nullptr; } // DBus calls can take some time to reply -- e.g. if a huge local mbox has to be parsed first. iface->setTimeout(5 * 60 * 1000); // 5 minutes, rather than 25 seconds mResourceInterfaces.insert(id, iface); return iface; } // called from any thread void ItemRetrievalManager::requestItemDelivery(ItemRetrievalRequest *req) { mLock->lockForWrite(); qCDebug(AKONADISERVER_LOG) << "ItemRetrievalManager posting retrieval request for items" << req->ids << "to" <resourceId << ". There are" << mPendingRequests.size() << "request queues and" << mPendingRequests[req->resourceId].size() << "items mine"; mPendingRequests[req->resourceId].append(req); mLock->unlock(); Q_EMIT requestAdded(); #if 0 mLock->lockForRead(); Q_FOREVER { //qCDebug(AKONADISERVER_LOG) << "checking if request for item" << req->id << "has been processed..."; if (req->processed) { QScopedPointer reqDeleter(req); Q_ASSERT(!mPendingRequests[req->resourceId].contains(req)); const QString errorMsg = req->errorMsg; mLock->unlock(); if (errorMsg.isEmpty()) { qCDebug(AKONADISERVER_LOG) << "request for items" << req->ids << "succeeded"; return; } else { qCDebug(AKONADISERVER_LOG) << "request for items" << req->ids << "failed:" << errorMsg; throw ItemRetrieverException(errorMsg); } } else { qCDebug(AKONADISERVER_LOG) << "request for items" << req->ids << "still pending - waiting"; mWaitCondition->wait(mLock); qCDebug(AKONADISERVER_LOG) << "continuing"; } } throw ItemRetrieverException("WTF?"); #endif } // called within the retrieval thread void ItemRetrievalManager::processRequest() { QVector > newJobs; mLock->lockForWrite(); // look for idle resources for (auto it = mPendingRequests.begin(); it != mPendingRequests.end();) { if (it.value().isEmpty()) { it = mPendingRequests.erase(it); continue; } if (!mCurrentJobs.contains(it.key()) || mCurrentJobs.value(it.key()) == nullptr) { // TODO: check if there is another one for the same uid with more parts requested ItemRetrievalRequest *req = it.value().takeFirst(); Q_ASSERT(req->resourceId == it.key()); AbstractItemRetrievalJob *job = mJobFactory->retrievalJob(req, this); connect(job, &AbstractItemRetrievalJob::requestCompleted, this, &ItemRetrievalManager::retrievalJobFinished); mCurrentJobs.insert(req->resourceId, job); // delay job execution until after we unlocked the mutex, since the job can emit the finished signal immediately in some cases newJobs.append(qMakePair(job, req->resourceId)); qCDebug(AKONADISERVER_LOG) << "ItemRetrievalJob" << job << "started for request" << req; } ++it; } bool nothingGoingOn = mPendingRequests.isEmpty() && mCurrentJobs.isEmpty() && newJobs.isEmpty(); mLock->unlock(); if (nothingGoingOn) { // someone asked as to process requests although everything is done already, he might still be waiting return; } for (auto it = newJobs.constBegin(), end = newJobs.constEnd(); it != end; ++it) { if (ItemRetrievalJob *j = qobject_cast((*it).first)) { j->setInterface(resourceInterface((*it).second)); } (*it).first->start(); } } void ItemRetrievalManager::retrievalJobFinished(ItemRetrievalRequest *request, const QString &errorMsg) { if (errorMsg.isEmpty()) { qCInfo(AKONADISERVER_LOG) << "ItemRetrievalJob for request" << request << "finished"; } else { qCWarning(AKONADISERVER_LOG) << "ItemRetrievalJob for request" << request << "finished with error:" << errorMsg; } mLock->lockForWrite(); request->errorMsg = errorMsg; request->processed = true; Q_ASSERT(mCurrentJobs.contains(request->resourceId)); mCurrentJobs.remove(request->resourceId); // TODO check if (*it)->parts is a subset of currentRequest->parts for (QList::Iterator it = mPendingRequests[request->resourceId].begin(); it != mPendingRequests[request->resourceId].end();) { if ((*it)->ids == request->ids) { qCDebug(AKONADISERVER_LOG) << "someone else requested item" << request->ids << "as well, marking as processed"; (*it)->errorMsg = errorMsg; (*it)->processed = true; Q_EMIT requestFinished(*it); it = mPendingRequests[request->resourceId].erase(it); } else { ++it; } } mLock->unlock(); Q_EMIT requestFinished(request); Q_EMIT requestAdded(); // trigger processRequest() again, in case there is more in the queues } void ItemRetrievalManager::triggerCollectionSync(const QString &resource, qint64 colId) { org::freedesktop::Akonadi::Resource *interface = resourceInterface(resource); if (interface) { interface->synchronizeCollection(colId); } } void ItemRetrievalManager::triggerCollectionTreeSync(const QString &resource) { org::freedesktop::Akonadi::Resource *interface = resourceInterface(resource); if (interface) { interface->synchronizeCollectionTree(); } } diff --git a/src/shared/CMakeLists.txt b/src/shared/CMakeLists.txt index d4c43bbef..1b72d0e27 100644 --- a/src/shared/CMakeLists.txt +++ b/src/shared/CMakeLists.txt @@ -1,28 +1,29 @@ set(akonadi_shared_srcs akapplication.cpp akdebug.cpp akremotelog.cpp ) add_library(akonadi_shared STATIC ${akonadi_shared_srcs}) target_include_directories(akonadi_shared INTERFACE $) target_include_directories(akonadi_shared INTERFACE $) target_link_libraries(akonadi_shared Qt5::Core KF5AkonadiPrivate KF5::Crash ) ecm_generate_headers(shared_HEADERS HEADER_NAMES + AkOptional VectorHelper REQUIRED_HEADERS shared_HEADERS ) # shared is not generally a public library, so install only the useful # public stuff to core install(FILES ${shared_HEADERS} DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/AkonadiCore COMPONENT Devel )