diff --git a/src/akonadicontrol/agentmanager.cpp b/src/akonadicontrol/agentmanager.cpp index 23b4a1f7e..95de81a75 100644 --- a/src/akonadicontrol/agentmanager.cpp +++ b/src/akonadicontrol/agentmanager.cpp @@ -1,896 +1,923 @@ /*************************************************************************** * 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; using namespace std::chrono_literals; static const bool enableAgentServerDefault = false; class StorageProcessControl : public Akonadi::ProcessControl { Q_OBJECT public: StorageProcessControl(const QStringList &args) : Akonadi::ProcessControl() { setShutdownTimeout(15s); connect(this, &Akonadi::ProcessControl::unableToStart, this, []() { QCoreApplication::instance()->exit(255); }); start(QStringLiteral("akonadiserver"), args, RestartOnCrash); } ~StorageProcessControl() override { setCrashPolicy(ProcessControl::StopOnCrash); org::freedesktop::Akonadi::Server serverIface(Akonadi::DBus::serviceName(Akonadi::DBus::Server), QStringLiteral("/Server"), QDBusConnection::sessionBus()); serverIface.quit(); } }; class AgentServerProcessControl : public Akonadi::ProcessControl { Q_OBJECT public: AgentServerProcessControl(const QStringList &args) : Akonadi::ProcessControl() { connect(this, &Akonadi::ProcessControl::unableToStart, this, []() { qCCritical(AKONADICONTROL_LOG) << "Failed to start AgentServer!"; }); start(QStringLiteral("akonadi_agent_server"), args, RestartOnCrash); } ~AgentServerProcessControl() override { setCrashPolicy(ProcessControl::StopOnCrash); org::freedesktop::Akonadi::AgentServer agentServerIface(Akonadi::DBus::serviceName(Akonadi::DBus::AgentServer), QStringLiteral("/AgentServer"), QDBusConnection::sessionBus(), this); agentServerIface.quit(); } }; 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); + auto bus = QDBusConnection::sessionBus(); + bus.registerObject(QStringLiteral("/AgentManager"), this); + + mWatcher.setConnection(bus); + mWatcher.setWatchMode(QDBusServiceWatcher::WatchForRegistration | QDBusServiceWatcher::WatchForUnregistration); + mWatcher.addWatchedService(QStringLiteral("org.freedesktop.Akonadi*")); + connect(&mWatcher, &QDBusServiceWatcher::serviceRegistered, + this, [this](const QString &name) { + qCDebug(AKONADICONTROL_LOG) << "Service" << name<< "registered"; + if (checkServerIsOperational(name)) { + continueStartup(); // Server is operational, start agents + return; + } + + const auto service = Akonadi::DBus::parseAgentServiceName(name); + if (service.has_value()) { + switch (service->agentType) { + case Akonadi::DBus::Agent: + registerAgentService(*service); + break; + case Akonadi::DBus::Resource: + registerResourceService(*service); + break; + case Akonadi::DBus::Preprocessor: + registerPreprocessorService(*service); + break; + case Akonadi::DBus::Unknown: + break; + } + } + }); + connect(&mWatcher, &QDBusServiceWatcher::serviceUnregistered, + this, [this](const QString &name) { + qCDebug(AKONADICONTROL_LOG) << "Service" << name << "unregistered from bus"; + const auto service = Akonadi::DBus::parseAgentServiceName(name); + if (service.has_value()) { + switch (service->agentType) { + case Akonadi::DBus::Agent: + case Akonadi::DBus::Resource: + case Akonadi::DBus::Unknown: + // We don't care those got lost + break; + case Akonadi::DBus::Preprocessor: + unregisterPreprocessorService(*service); + break; + } + } + }); - if (QDBusConnection::sessionBus().interface()->isServiceRegistered(Akonadi::DBus::serviceName(Akonadi::DBus::Server))) { + if (bus.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 = std::unique_ptr(new StorageProcessControl(serviceArgs)); if (mAgentServerEnabled) { mAgentServer = std::unique_ptr(new AgentServerProcessControl(serviceArgs)); } } 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.reset(); mStorageController.reset(); } 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 QSharedPointer::create(*this); case AgentType::Launcher: // Fall through case AgentType::Process: return QSharedPointer::create(*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[identifier]; mAgents[identifier].instanceCounter++; const auto 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 auto 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) +void AgentManager::registerAgentService(const Akonadi::DBus::AgentService &service) { - 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(); - } + if (!mAgentInstances.contains(service.identifier)) { + return; } - const auto service = Akonadi::DBus::parseAgentServiceName(name); - if (!service.has_value()) { + const AgentInstance::Ptr instance = mAgentInstances.value(service.identifier); + const bool restarting = instance->hasAgentInterface(); + if (!instance->obtainAgentInterface()) { 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(service->identifier)) { - return; - } - - const AgentInstance::Ptr instance = mAgentInstances.value(service->identifier); - const bool restarting = instance->hasAgentInterface(); - if (!instance->obtainAgentInterface()) { - return; - } - - Q_ASSERT(mAgents.contains(instance->agentType())); - const bool isResource = mAgents.value(instance->agentType()).capabilities.contains(AgentType::CapabilityResource); - if (!restarting && (!isResource || instance->hasResourceInterface())) { - Q_EMIT agentInstanceAdded(service->identifier); - } + Q_ASSERT(mAgents.contains(instance->agentType())); + const bool isResource = mAgents.value(instance->agentType()).capabilities.contains(AgentType::CapabilityResource); - break; + if (!restarting && (!isResource || instance->hasResourceInterface())) { + Q_EMIT agentInstanceAdded(service.identifier); } - 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(service->identifier)) { - return; - } - - const AgentInstance::Ptr instance = mAgentInstances.value(service->identifier); - const bool restarting = instance->hasResourceInterface(); - if (!instance->obtainResourceInterface()) { - return; - } +} - if (!restarting && instance->hasAgentInterface()) { - Q_EMIT agentInstanceAdded(service->identifier); - } +void AgentManager::registerResourceService(const Akonadi::DBus::AgentService &service) +{ + if (!mAgentInstances.contains(service.identifier)) { + return; + } - break; + const AgentInstance::Ptr instance = mAgentInstances.value(service.identifier); + const bool restarting = instance->hasResourceInterface(); + if (!instance->obtainResourceInterface()) { + return; } - 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 (!restarting && instance->hasAgentInterface()) { + Q_EMIT agentInstanceAdded(service.identifier); + } +} - // 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. +void AgentManager::registerPreprocessorService(const Akonadi::DBus::AgentService &service) +{ + // 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. - // Check if we "know" about it. - qCDebug(AKONADICONTROL_LOG) << "Preprocessor " << service->identifier << " is going up or down..."; + qCDebug(AKONADICONTROL_LOG) << "Preprocessor " << service.identifier << " is going up or down..."; - if (!mAgentInstances.contains(service->identifier)) { - qCDebug(AKONADICONTROL_LOG) << "But it isn't registered as agent... not mine (anymore?)"; - return; // not our agent (?) - } + // Check if we "know" about it. + if (!mAgentInstances.contains(service.identifier)) { + 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); + 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. + if (!preProcessorManager.isValid()) { + qCWarning(AKONADICONTROL_LOG) << "Could not connect to PreprocessorManager via D-Bus:" << preProcessorManager.lastError().message(); + return; + } - preProcessorManager.unregisterInstance(service->identifier); + if (!mAgentInstances.value(service.identifier)->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" << service.identifier; + preProcessorManager.unregisterInstance(service.identifier); + return; + } - } else { + qCDebug(AKONADICONTROL_LOG) << "Registering preprocessor instance" << service.identifier; - // The preprocessor went up. Register it on server side. + // Add to the preprocessor chain + preProcessorManager.registerInstance(service.identifier); +} - if (!mAgentInstances.value(service->identifier)->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" << service->identifier; +void AgentManager::unregisterPreprocessorService(const Akonadi::DBus::AgentService &service) +{ + // 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. - preProcessorManager.unregisterInstance(service->identifier); - return; - } + org::freedesktop::Akonadi::PreprocessorManager preProcessorManager( + Akonadi::DBus::serviceName(Akonadi::DBus::Server), + QStringLiteral("/PreprocessorManager"), + QDBusConnection::sessionBus(), + this); - qCDebug(AKONADICONTROL_LOG) << "Registering preprocessor instance" << service->identifier; + if (!preProcessorManager.isValid()) { + qCWarning(AKONADICONTROL_LOG) << "Could not connect to PreprocessorManager via D-Bus:" << preProcessorManager.lastError().message(); + return; + } + // The preprocessor went down. Unregister it on server side. + preProcessorManager.unregisterInstance(service.identifier); +} - // Add to the preprocessor chain - preProcessorManager.registerInstance(service->identifier); - } +bool AgentManager::checkServerIsOperational(const QString &service) const +{ + const auto dbusInterface = QDBusConnection::sessionBus().interface(); + const auto serverService = Akonadi::DBus::serviceName(Akonadi::DBus::Server); + const auto agentServerService = Akonadi::DBus::serviceName(Akonadi::DBus::AgentServer); + if (service == serverService || service == agentServerService) { + if (dbusInterface->isServiceRegistered(serverService) && (!mAgentServer || dbusInterface->isServiceRegistered(agentServerService))) { + return true; } - - break; - } - default: - break; } + + return false; } 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(QLatin1String("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); } } } #include "agentmanager.moc" diff --git a/src/akonadicontrol/agentmanager.h b/src/akonadicontrol/agentmanager.h index 68a9b13d5..fcc8f3b82 100644 --- a/src/akonadicontrol/agentmanager.h +++ b/src/akonadicontrol/agentmanager.h @@ -1,391 +1,398 @@ /*************************************************************************** * 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. * ***************************************************************************/ #ifndef AGENTMANAGER_H #define AGENTMANAGER_H #include #include +#include #include "agenttype.h" #include "agentinstance.h" +#include class QDir; namespace Akonadi { class ProcessControl; } /** * The agent manager has knowledge about all available agents (it scans * for .desktop files in the agent directory) and the available configured * instances. */ class AgentManager : public QObject, protected QDBusContext { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.freedesktop.Akonadi.AgentManager") public: /** * Creates a new agent manager. * * @param parent The parent object. */ explicit AgentManager(bool verbose, QObject *parent = nullptr); /** * Destroys the agent manager. */ ~AgentManager(); /** * Called by the crash handler and dtor to terminate * the child processes. */ void cleanup(); public Q_SLOTS: /** * Returns the list of identifiers of all available * agent types. */ QStringList agentTypes() const; /** * Returns the i18n'ed name of the agent type for * the given @p identifier. */ QString agentName(const QString &identifier) const; /** * Returns the i18n'ed comment of the agent type for * the given @p identifier.. */ QString agentComment(const QString &identifier) const; /** * Returns the icon name of the agent type for the * given @p identifier. */ QString agentIcon(const QString &identifier) const; /** * Returns a list of supported mimetypes of the agent type * for the given @p identifier. */ QStringList agentMimeTypes(const QString &identifier) const; /** * Returns a list of supported capabilities of the agent type * for the given @p identifier. */ QStringList agentCapabilities(const QString &identifier) const; /** * Returns a list of Custom added propeties of the agent type * for the given @p identifier * @since 1.11 */ QVariantMap agentCustomProperties(const QString &identifier) const; /** * Creates a new agent of the given agent type @p identifier. * * @return The identifier of the new agent if created successfully, * an empty string otherwise. * The identifier consists of two parts, the type of the * agent and an unique instance number, and looks like * the following: 'file_1' or 'imap_267'. */ QString createAgentInstance(const QString &identifier); /** * Removes the agent with the given @p identifier. */ void removeAgentInstance(const QString &identifier); /** * Returns the type of the agent instance with the given @p identifier. */ QString agentInstanceType(const QString &identifier); /** * Returns the list of identifiers of configured instances. */ QStringList agentInstances() const; /** * Returns the current status code of the agent with the given @p identifier. */ int agentInstanceStatus(const QString &identifier) const; /** * Returns the i18n'ed description of the current status of the agent with * the given @p identifier. */ QString agentInstanceStatusMessage(const QString &identifier) const; /** * Returns the current progress of the agent with the given @p identifier * in percentage. */ uint agentInstanceProgress(const QString &identifier) const; /** * Returns the i18n'ed description of the current progress of the agent with * the given @p identifier. */ QString agentInstanceProgressMessage(const QString &identifier) const; /** * Sets the @p name of the agent instance with the given @p identifier. */ void setAgentInstanceName(const QString &identifier, const QString &name); /** * Returns the name of the agent instance with the given @p identifier. */ QString agentInstanceName(const QString &identifier) const; /** * Triggers the agent instance with the given @p identifier to show * its configuration dialog. * @param windowId Parent window id for the configuration dialog. */ void agentInstanceConfigure(const QString &identifier, qlonglong windowId); /** * Triggers the agent instance with the given @p identifier to start * synchronization. */ void agentInstanceSynchronize(const QString &identifier); /** Trigger a synchronization of the collection tree by the given resource agent. @param identifier The resource agent identifier. */ void agentInstanceSynchronizeCollectionTree(const QString &identifier); /** Trigger a synchronization of the given collection by its owning resource agent. */ void agentInstanceSynchronizeCollection(const QString &identifier, qint64 collection); /** Trigger a synchronization of the given collection by its owning resource agent. @param recursive set it true to have sub-collection synchronized as well */ void agentInstanceSynchronizeCollection(const QString &identifier, qint64 collection, bool recursive); /** * Trigger a synchronization of tags by the given resource agent. * @param identifier The resource agent identifier. */ void agentInstanceSynchronizeTags(const QString &identifier); /** * Trigger a synchronization of relations by the given resource agent. * @param identifier The resource agent identifier. */ void agentInstanceSynchronizeRelations(const QString &identifier); /** Returns if the agent instance @p identifier is in online mode. */ bool agentInstanceOnline(const QString &identifier); /** Sets agent instance @p identifier to online or offline mode. */ void setAgentInstanceOnline(const QString &identifier, bool state); /** Restarts the agent instance @p identifier. This is supposed to be used as a development aid and not something to use during normal operations. */ void restartAgentInstance(const QString &identifier); /** * Add a persistent search to remote search agents. */ void addSearch(const QString &query, const QString &queryLanguage, qint64 resultCollectionId); /** * Removes a persistent search for the given result collection. */ void removeSearch(quint64 resultCollectionId); Q_SIGNALS: /** * This signal is emitted whenever a new agent type was installed on the system. * * @param agentType The identifier of the new agent type. */ void agentTypeAdded(const QString &agentType); /** * This signal is emitted whenever an agent type was removed from the system. * * @param agentType The identifier of the removed agent type. */ void agentTypeRemoved(const QString &agentType); /** * This signal is emitted whenever a new agent instance was created. * * @param agentIdentifier The identifier of the new agent instance. */ void agentInstanceAdded(const QString &agentIdentifier); /** * This signal is emitted whenever an agent instance was removed. * * @param agentIdentifier The identifier of the removed agent instance. */ void agentInstanceRemoved(const QString &agentIdentifier); /** * This signal is emitted whenever the status of an agent instance has * changed. * * @param agentIdentifier The identifier of the agent that has changed. * @param status The new status code. * @param message The i18n'ed description of the new status. */ void agentInstanceStatusChanged(const QString &agentIdentifier, int status, const QString &message); /** * This signal is emitted whenever the status of an agent instance has * changed. * * @param agentIdentifier The identifier of the agent that has changed. * @param status The object that describes the status change. */ void agentInstanceAdvancedStatusChanged(const QString &agentIdentifier, const QVariantMap &status); /** * This signal is emitted whenever the progress of an agent instance has * changed. * * @param agentIdentifier The identifier of the agent that has changed. * @param progress The new progress in percentage. * @param message The i18n'ed description of the new progress. */ void agentInstanceProgressChanged(const QString &agentIdentifier, uint progress, const QString &message); /** * This signal is emitted whenever an agent instance raised a warning. * * @param agentIdentifier The identifier of the agent instance. * @param message The i18n'ed warning message. */ void agentInstanceWarning(const QString &agentIdentifier, const QString &message); /** * This signal is emitted whenever an agent instance raised an error. * * @param agentIdentifier The identifier of the agent instance. * @param message The i18n'ed error message. */ void agentInstanceError(const QString &agentIdentifier, const QString &message); /** * This signal is emitted whenever the name of the agent instance has changed. * * @param agentIdentifier The identifier of the agent that has changed. * @param name The new name of the agent instance. */ void agentInstanceNameChanged(const QString &agentIdentifier, const QString &name); /** * Emitted when the online state of an agent changed. */ void agentInstanceOnlineChanged(const QString &agentIdentifier, bool state); private Q_SLOTS: void updatePluginInfos(); - void serviceOwnerChanged(const QString &name, const QString &oldOwner, const QString &newOwner); void agentExeChanged(const QString &fileName); private: /** * Returns the list of directory paths where the .desktop files * for the plugins are located. */ static QStringList pluginInfoPathList(); /** * Loads the internal state from config file. */ void load(); /** * Saves internal state to the config file. */ void save(); /** * Reads the plugin information from directory. */ void readPluginInfos(); /** * Reads the plugin information from directory. * * @param directory the directory to get plugin information from */ void readPluginInfos(const QDir &directory); AgentInstance::Ptr createAgentInstance(const AgentType &type); + bool checkServerIsOperational(const QString &serviceName) const; bool checkAgentInterfaces(const QString &identifier, const QString &method) const; bool checkInstance(const QString &identifier) const; bool checkResourceInterface(const QString &identifier, const QString &method) const; bool checkAgentExists(const QString &identifier) const; void ensureAutoStart(const AgentType &info); void continueStartup(); void registerAgentAtServer(const QString &agentIdentifier, const AgentType &type); + void registerAgentService(const Akonadi::DBus::AgentService &service); + void registerResourceService(const Akonadi::DBus::AgentService &service); + void registerPreprocessorService(const Akonadi::DBus::AgentService &service); + void unregisterPreprocessorService(const Akonadi::DBus::AgentService &service); private: /** * The map which stores the .desktop file * entries for every agent type. * * Key is the agent type (e.g. 'file' or 'imap'). */ QHash mAgents; /** * The map which stores the active instances. * * Key is the instance identifier. */ QHash mAgentInstances; std::unique_ptr mAgentServer; std::unique_ptr mStorageController; + QDBusServiceWatcher mWatcher; bool mAgentServerEnabled = false; bool mVerbose = false; friend class AgentInstance; }; #endif