diff --git a/src/akonadictl/akonadistarter.cpp b/src/akonadictl/akonadistarter.cpp index 26a84f55a..d1a830c48 100644 --- a/src/akonadictl/akonadistarter.cpp +++ b/src/akonadictl/akonadistarter.cpp @@ -1,91 +1,81 @@ /* Copyright (c) 2008 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "akonadistarter.h" #include "akonadictl_debug.h" #include #include #include #include #include #include #include #include #include AkonadiStarter::AkonadiStarter(QObject *parent) : QObject(parent) + , mWatcher(Akonadi::DBus::serviceName(Akonadi::DBus::ControlLock), QDBusConnection::sessionBus(), + QDBusServiceWatcher::WatchForRegistration) { - QDBusServiceWatcher *watcher = new QDBusServiceWatcher(Akonadi::DBus::serviceName(Akonadi::DBus::ControlLock), - QDBusConnection::sessionBus(), - QDBusServiceWatcher::WatchForOwnerChange, this); - - connect(watcher, &QDBusServiceWatcher::serviceOwnerChanged, - this, &AkonadiStarter::serviceOwnerChanged); + connect(&mWatcher, &QDBusServiceWatcher::serviceRegistered, + this, [this]() { + mRegistered = true; + QCoreApplication::instance()->quit(); + }); } bool AkonadiStarter::start(bool verbose) { - qCDebug(AKONADICTL_LOG) << "Starting Akonadi Server..."; + qCInfo(AKONADICTL_LOG) << "Starting Akonadi Server..."; QStringList serverArgs; if (Akonadi::Instance::hasIdentifier()) { serverArgs << QStringLiteral("--instance") << Akonadi::Instance::identifier(); } if (verbose) { serverArgs << QStringLiteral("--verbose"); } const bool ok = QProcess::startDetached(QStringLiteral("akonadi_control"), serverArgs); if (!ok) { std::cerr << "Error: unable to execute binary akonadi_control" << std::endl; return false; } // safety timeout QTimer::singleShot(5000, QCoreApplication::instance(), &QCoreApplication::quit); // wait for the server to register with D-Bus QCoreApplication::instance()->exec(); if (!mRegistered) { std::cerr << "Error: akonadi_control was started but didn't register at D-Bus session bus." << std::endl << "Make sure your system is set up correctly!" << std::endl; return false; } - qCDebug(AKONADICTL_LOG) << " done."; + qCInfo(AKONADICTL_LOG) << " done."; return true; } -void AkonadiStarter::serviceOwnerChanged(const QString &name, const QString &oldOwner, const QString &newOwner) -{ - Q_UNUSED(name); - Q_UNUSED(oldOwner); - if (newOwner.isEmpty()) { - return; - } - - mRegistered = true; - QCoreApplication::instance()->quit(); -} diff --git a/src/akonadictl/akonadistarter.h b/src/akonadictl/akonadistarter.h index db011a9c0..dc7d0931d 100644 --- a/src/akonadictl/akonadistarter.h +++ b/src/akonadictl/akonadistarter.h @@ -1,39 +1,38 @@ /* Copyright (c) 2008 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef AKONADISTARTER_H #define AKONADISTARTER_H #include +#include class AkonadiStarter : public QObject { Q_OBJECT public: explicit AkonadiStarter(QObject *parent = nullptr); Q_REQUIRED_RESULT bool start(bool verbose); -private Q_SLOTS: - void serviceOwnerChanged(const QString &name, const QString &oldOwner, const QString &newOwner); - private: + QDBusServiceWatcher mWatcher; bool mRegistered = false; }; #endif diff --git a/src/core/agentmanager.cpp b/src/core/agentmanager.cpp index d7930b8b3..4613810d0 100644 --- a/src/core/agentmanager.cpp +++ b/src/core/agentmanager.cpp @@ -1,438 +1,435 @@ /* Copyright (c) 2006-2008 Tobias Koenig This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "agentmanager.h" #include "agentmanager_p.h" #include "agenttype_p.h" #include "agentinstance_p.h" #include "KDBusConnectionPool" #include "servermanager.h" #include "collection.h" #include "shared/akranges.h" #include #include +#include using namespace Akonadi; using namespace AkRanges; // @cond PRIVATE AgentInstance AgentManagerPrivate::createInstance(const AgentType &type) { const QString &identifier = mManager->createAgentInstance(type.identifier()); if (identifier.isEmpty()) { return AgentInstance(); } return fillAgentInstanceLight(identifier); } void AgentManagerPrivate::agentTypeAdded(const QString &identifier) { // Ignore agent types we already know about, for example because we called // readAgentTypes before. if (mTypes.contains(identifier)) { return; } if (mTypes.isEmpty()) { // The Akonadi ServerManager assumes that the server is up and running as soon // as it knows about at least one agent type. // If we Q_EMIT the typeAdded() signal here, it therefore thinks the server is // running. However, the AgentManager does not know about all agent types yet, // as the server might still have pending agentTypeAdded() signals, even though // it internally knows all agent types already. // This can cause situations where the client gets told by the ServerManager that // the server is running, yet the client will encounter an error because the // AgentManager doesn't know all types yet. // // Therefore, we read all agent types from the server here so they are known. readAgentTypes(); } const AgentType type = fillAgentType(identifier); if (type.isValid()) { mTypes.insert(identifier, type); Q_EMIT mParent->typeAdded(type); } } void AgentManagerPrivate::agentTypeRemoved(const QString &identifier) { if (!mTypes.contains(identifier)) { return; } const AgentType type = mTypes.take(identifier); Q_EMIT mParent->typeRemoved(type); } void AgentManagerPrivate::agentInstanceAdded(const QString &identifier) { const AgentInstance instance = fillAgentInstance(identifier); if (instance.isValid()) { // It is possible that this function is called when the instance is already // in our list we filled initially in the constructor. // This happens when the constructor is called during Akonadi startup, when // the agent processes are not fully loaded and have no D-Bus interface yet. // The server-side agent manager then emits the instance added signal when // the D-Bus interface for the agent comes up. // In this case, we simply notify that the instance status has changed. const bool newAgentInstance = !mInstances.contains(identifier); if (newAgentInstance) { mInstances.insert(identifier, instance); Q_EMIT mParent->instanceAdded(instance); } else { mInstances.remove(identifier); mInstances.insert(identifier, instance); Q_EMIT mParent->instanceStatusChanged(instance); } } } void AgentManagerPrivate::agentInstanceRemoved(const QString &identifier) { if (!mInstances.contains(identifier)) { return; } const AgentInstance instance = mInstances.take(identifier); Q_EMIT mParent->instanceRemoved(instance); } void AgentManagerPrivate::agentInstanceStatusChanged(const QString &identifier, int status, const QString &msg) { if (!mInstances.contains(identifier)) { return; } AgentInstance &instance = mInstances[identifier]; instance.d->mStatus = status; instance.d->mStatusMessage = msg; Q_EMIT mParent->instanceStatusChanged(instance); } void AgentManagerPrivate::agentInstanceProgressChanged(const QString &identifier, uint progress, const QString &msg) { if (!mInstances.contains(identifier)) { return; } AgentInstance &instance = mInstances[identifier]; instance.d->mProgress = progress; if (!msg.isEmpty()) { instance.d->mStatusMessage = msg; } Q_EMIT mParent->instanceProgressChanged(instance); } void AgentManagerPrivate::agentInstanceWarning(const QString &identifier, const QString &msg) { if (!mInstances.contains(identifier)) { return; } AgentInstance &instance = mInstances[identifier]; Q_EMIT mParent->instanceWarning(instance, msg); } void AgentManagerPrivate::agentInstanceError(const QString &identifier, const QString &msg) { if (!mInstances.contains(identifier)) { return; } AgentInstance &instance = mInstances[identifier]; Q_EMIT mParent->instanceError(instance, msg); } void AgentManagerPrivate::agentInstanceOnlineChanged(const QString &identifier, bool state) { if (!mInstances.contains(identifier)) { return; } AgentInstance &instance = mInstances[identifier]; instance.d->mIsOnline = state; Q_EMIT mParent->instanceOnline(instance, state); } void AgentManagerPrivate::agentInstanceNameChanged(const QString &identifier, const QString &name) { if (!mInstances.contains(identifier)) { return; } AgentInstance &instance = mInstances[identifier]; instance.d->mName = name; Q_EMIT mParent->instanceNameChanged(instance); } void AgentManagerPrivate::readAgentTypes() { const QDBusReply types = mManager->agentTypes(); if (types.isValid()) { const QStringList lst = types.value(); for (const QString &type : lst) { const AgentType agentType = fillAgentType(type); if (agentType.isValid()) { mTypes.insert(type, agentType); Q_EMIT mParent->typeAdded(agentType); } } } } void AgentManagerPrivate::readAgentInstances() { const QDBusReply instances = mManager->agentInstances(); if (instances.isValid()) { const QStringList lst = instances.value(); for (const QString &instance : lst) { const AgentInstance agentInstance = fillAgentInstance(instance); if (agentInstance.isValid()) { mInstances.insert(instance, agentInstance); Q_EMIT mParent->instanceAdded(agentInstance); } } } } AgentType AgentManagerPrivate::fillAgentType(const QString &identifier) const { AgentType type; type.d->mIdentifier = identifier; type.d->mName = mManager->agentName(identifier); type.d->mDescription = mManager->agentComment(identifier); type.d->mIconName = mManager->agentIcon(identifier); type.d->mMimeTypes = mManager->agentMimeTypes(identifier); type.d->mCapabilities = mManager->agentCapabilities(identifier); type.d->mCustomProperties = mManager->agentCustomProperties(identifier); return type; } void AgentManagerPrivate::setName(const AgentInstance &instance, const QString &name) { mManager->setAgentInstanceName(instance.identifier(), name); } void AgentManagerPrivate::setOnline(const AgentInstance &instance, bool state) { mManager->setAgentInstanceOnline(instance.identifier(), state); } void AgentManagerPrivate::configure(const AgentInstance &instance, QWidget *parent) { qlonglong winId = 0; if (parent) { winId = static_cast(parent->window()->winId()); } mManager->agentInstanceConfigure(instance.identifier(), winId); } void AgentManagerPrivate::synchronize(const AgentInstance &instance) { mManager->agentInstanceSynchronize(instance.identifier()); } void AgentManagerPrivate::synchronizeCollectionTree(const AgentInstance &instance) { mManager->agentInstanceSynchronizeCollectionTree(instance.identifier()); } void AgentManagerPrivate::synchronizeTags(const AgentInstance &instance) { mManager->agentInstanceSynchronizeTags(instance.identifier()); } void AgentManagerPrivate::synchronizeRelations(const AgentInstance &instance) { mManager->agentInstanceSynchronizeRelations(instance.identifier()); } AgentInstance AgentManagerPrivate::fillAgentInstance(const QString &identifier) const { AgentInstance instance; const QString agentTypeIdentifier = mManager->agentInstanceType(identifier); if (!mTypes.contains(agentTypeIdentifier)) { return instance; } instance.d->mType = mTypes.value(agentTypeIdentifier); instance.d->mIdentifier = identifier; instance.d->mName = mManager->agentInstanceName(identifier); instance.d->mStatus = mManager->agentInstanceStatus(identifier); instance.d->mStatusMessage = mManager->agentInstanceStatusMessage(identifier); instance.d->mProgress = mManager->agentInstanceProgress(identifier); instance.d->mIsOnline = mManager->agentInstanceOnline(identifier); return instance; } AgentInstance AgentManagerPrivate::fillAgentInstanceLight(const QString &identifier) const { AgentInstance instance; const QString agentTypeIdentifier = mManager->agentInstanceType(identifier); Q_ASSERT_X(mTypes.contains(agentTypeIdentifier), "fillAgentInstanceLight", "Requests non-existing agent type"); instance.d->mType = mTypes.value(agentTypeIdentifier); instance.d->mIdentifier = identifier; return instance; } -void AgentManagerPrivate::serviceOwnerChanged(const QString &, const QString &oldOwner, const QString &) -{ - if (oldOwner.isEmpty()) { - if (mTypes.isEmpty()) { // just to be safe - readAgentTypes(); - } - if (mInstances.isEmpty()) { - readAgentInstances(); - } - } -} - void AgentManagerPrivate::createDBusInterface() { mTypes.clear(); mInstances.clear(); delete mManager; mManager = new org::freedesktop::Akonadi::AgentManager(ServerManager::serviceName(ServerManager::Control), QStringLiteral("/AgentManager"), KDBusConnectionPool::threadConnection(), mParent); QObject::connect(mManager, SIGNAL(agentTypeAdded(QString)), mParent, SLOT(agentTypeAdded(QString))); QObject::connect(mManager, SIGNAL(agentTypeRemoved(QString)), mParent, SLOT(agentTypeRemoved(QString))); QObject::connect(mManager, SIGNAL(agentInstanceAdded(QString)), mParent, SLOT(agentInstanceAdded(QString))); QObject::connect(mManager, SIGNAL(agentInstanceRemoved(QString)), mParent, SLOT(agentInstanceRemoved(QString))); QObject::connect(mManager, SIGNAL(agentInstanceStatusChanged(QString,int,QString)), mParent, SLOT(agentInstanceStatusChanged(QString,int,QString))); QObject::connect(mManager, SIGNAL(agentInstanceProgressChanged(QString,uint,QString)), mParent, SLOT(agentInstanceProgressChanged(QString,uint,QString))); QObject::connect(mManager, SIGNAL(agentInstanceNameChanged(QString,QString)), mParent, SLOT(agentInstanceNameChanged(QString,QString))); QObject::connect(mManager, SIGNAL(agentInstanceWarning(QString,QString)), mParent, SLOT(agentInstanceWarning(QString,QString))); QObject::connect(mManager, SIGNAL(agentInstanceError(QString,QString)), mParent, SLOT(agentInstanceError(QString,QString))); QObject::connect(mManager, SIGNAL(agentInstanceOnlineChanged(QString,bool)), mParent, SLOT(agentInstanceOnlineChanged(QString,bool))); if (mManager->isValid()) { readAgentTypes(); readAgentInstances(); } } AgentManager *AgentManagerPrivate::mSelf = nullptr; AgentManager::AgentManager() : QObject(nullptr) , d(new AgentManagerPrivate(this)) { // needed for queued connections on our signals qRegisterMetaType(); qRegisterMetaType(); d->createDBusInterface(); - QDBusServiceWatcher *watcher = new QDBusServiceWatcher(ServerManager::serviceName(ServerManager::Control), - KDBusConnectionPool::threadConnection(), - QDBusServiceWatcher::WatchForOwnerChange, this); - connect(watcher, &QDBusServiceWatcher::serviceOwnerChanged, - this, [this](const QString &arg1, const QString &arg2 , const QString &arg3) { d->serviceOwnerChanged(arg1, arg2, arg3); }); + d->mServiceWatcher = std::make_unique( + ServerManager::serviceName(ServerManager::Control), KDBusConnectionPool::threadConnection(), + QDBusServiceWatcher::WatchForRegistration); + connect(d->mServiceWatcher.get(), &QDBusServiceWatcher::serviceRegistered, + this, [this]() { + if (d->mTypes.isEmpty()) { // just to be safe + d->readAgentTypes(); + } + if (d->mInstances.isEmpty()) { + d->readAgentInstances(); + } + }); } // @endcond AgentManager::~AgentManager() { delete d; } AgentManager *AgentManager::self() { if (!AgentManagerPrivate::mSelf) { AgentManagerPrivate::mSelf = new AgentManager(); } return AgentManagerPrivate::mSelf; } AgentType::List AgentManager::types() const { - // Maybe the Control process is up and ready but we haven't been to the event loop yet so serviceOwnerChanged wasn't called yet. + // Maybe the Control process is up and ready but we haven't been to the event loop yet so + // QDBusServiceWatcher hasn't notified us yet. // In that case make sure to do it here, to avoid going into Broken state. if (d->mTypes.isEmpty()) { d->readAgentTypes(); } return d->mTypes | Views::values | Actions::toQVector; } AgentType AgentManager::type(const QString &identifier) const { return d->mTypes.value(identifier); } AgentInstance::List AgentManager::instances() const { return d->mInstances | Views::values | Actions::toQVector; } AgentInstance AgentManager::instance(const QString &identifier) const { return d->mInstances.value(identifier); } void AgentManager::removeInstance(const AgentInstance &instance) { d->mManager->removeAgentInstance(instance.identifier()); } void AgentManager::synchronizeCollection(const Collection &collection) { synchronizeCollection(collection, false); } void AgentManager::synchronizeCollection(const Collection &collection, bool recursive) { const QString resId = collection.resource(); Q_ASSERT(!resId.isEmpty()); d->mManager->agentInstanceSynchronizeCollection(resId, collection.id(), recursive); } #include "moc_agentmanager.cpp" diff --git a/src/core/agentmanager_p.h b/src/core/agentmanager_p.h index 0f1aab9d7..4772acd86 100644 --- a/src/core/agentmanager_p.h +++ b/src/core/agentmanager_p.h @@ -1,106 +1,111 @@ /* Copyright (c) 2006-2008 Tobias Koenig This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef AKONADI_AGENTMANAGER_P_H #define AKONADI_AGENTMANAGER_P_H #include "agentmanagerinterface.h" #include "agenttype.h" #include "agentinstance.h" #include +#include + +class QDBusServiceWatcher; + namespace Akonadi { class AgentManager; /** * @internal */ class AgentManagerPrivate { friend class AgentManager; public: explicit AgentManagerPrivate(AgentManager *parent) : mParent(parent) { } /* * Used by AgentInstanceCreateJob */ AgentInstance createInstance(const AgentType &type); void agentTypeAdded(const QString &identifier); void agentTypeRemoved(const QString &identifier); void agentInstanceAdded(const QString &identifier); void agentInstanceRemoved(const QString &identifier); void agentInstanceStatusChanged(const QString &identifier, int status, const QString &msg); void agentInstanceProgressChanged(const QString &identifier, uint progress, const QString &msg); void agentInstanceNameChanged(const QString &identifier, const QString &name); void agentInstanceWarning(const QString &identifier, const QString &msg); void agentInstanceError(const QString &identifier, const QString &msg); void agentInstanceOnlineChanged(const QString &identifier, bool state); /** * Reads the information about all known agent types from the serverside * agent manager and updates mTypes, like agentTypeAdded() does. * * This will not remove agents from the internal map that are no longer on * the server. */ void readAgentTypes(); /** * Reads the information about all known agent instances from the server. If AgentManager * is created before the Akonadi.Control interface is registered, the agent * instances aren't immediately found then. */ void readAgentInstances(); void setName(const AgentInstance &instance, const QString &name); void setOnline(const AgentInstance &instance, bool state); void configure(const AgentInstance &instance, QWidget *parent); void synchronize(const AgentInstance &instance); void synchronizeCollectionTree(const AgentInstance &instance); void synchronizeTags(const AgentInstance &instance); void synchronizeRelations(const AgentInstance &instance); - void serviceOwnerChanged(const QString &name, const QString &oldOwner, const QString &newOwner); void createDBusInterface(); AgentType fillAgentType(const QString &identifier) const; AgentInstance fillAgentInstance(const QString &identifier) const; AgentInstance fillAgentInstanceLight(const QString &identifier) const; static AgentManager *mSelf; AgentManager *mParent = nullptr; org::freedesktop::Akonadi::AgentManager *mManager = nullptr; QHash mTypes; QHash mInstances; + + std::unique_ptr mServiceWatcher; }; } #endif diff --git a/src/core/jobs/specialcollectionshelperjobs.cpp b/src/core/jobs/specialcollectionshelperjobs.cpp index 7e6f4070c..e5b59f48f 100644 --- a/src/core/jobs/specialcollectionshelperjobs.cpp +++ b/src/core/jobs/specialcollectionshelperjobs.cpp @@ -1,666 +1,656 @@ /* Copyright (c) 2009 Constantin Berzan 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 "specialcollectionshelperjobs_p.h" #include "KDBusConnectionPool" #include "specialcollectionattribute.h" #include "specialcollections.h" #include "servermanager.h" #include "agentinstance.h" #include "agentinstancecreatejob.h" #include "agentmanager.h" #include "collectionfetchjob.h" #include "collectionfetchscope.h" #include "collectionmodifyjob.h" #include "entitydisplayattribute.h" #include "resourcesynchronizationjob.h" #include "akonadicore_debug.h" #include #include #include #include #include #include #include #include +#include #define LOCK_WAIT_TIMEOUT_SECONDS 30 using namespace Akonadi; // convenient methods to get/set the default resource id static void setDefaultResourceId(KCoreConfigSkeleton *settings, const QString &value) { KConfigSkeletonItem *item = settings->findItem(QStringLiteral("DefaultResourceId")); Q_ASSERT(item); item->setProperty(value); } static QString defaultResourceId(KCoreConfigSkeleton *settings) { const KConfigSkeletonItem *item = settings->findItem(QStringLiteral("DefaultResourceId")); Q_ASSERT(item); return item->property().toString(); } static QString dbusServiceName() { QString service = QStringLiteral("org.kde.pim.SpecialCollections"); if (ServerManager::hasInstanceIdentifier()) { return service + ServerManager::instanceIdentifier(); } return service; } static QVariant::Type argumentType(const QMetaObject *mo, const QString &method) { QMetaMethod m; for (int i = 0; i < mo->methodCount(); ++i) { const QString signature = QString::fromLatin1(mo->method(i).methodSignature()); if (signature.startsWith(method)) { m = mo->method(i); } } if (m.methodSignature().isEmpty()) { return QVariant::Invalid; } const QList argTypes = m.parameterTypes(); if (argTypes.count() != 1) { return QVariant::Invalid; } return QVariant::nameToType(argTypes.first().constData()); } // ===================== ResourceScanJob ============================ /** @internal */ class Q_DECL_HIDDEN Akonadi::ResourceScanJob::Private { public: Private(KCoreConfigSkeleton *settings, ResourceScanJob *qq); void fetchResult(KJob *job); // slot ResourceScanJob *const q; // Input: QString mResourceId; KCoreConfigSkeleton *mSettings = nullptr; // Output: Collection mRootCollection; Collection::List mSpecialCollections; }; ResourceScanJob::Private::Private(KCoreConfigSkeleton *settings, ResourceScanJob *qq) : q(qq) , mSettings(settings) { } void ResourceScanJob::Private::fetchResult(KJob *job) { if (job->error()) { qCWarning(AKONADICORE_LOG) << job->errorText(); return; } CollectionFetchJob *fetchJob = qobject_cast(job); Q_ASSERT(fetchJob); Q_ASSERT(!mRootCollection.isValid()); Q_ASSERT(mSpecialCollections.isEmpty()); const Akonadi::Collection::List lstCols = fetchJob->collections(); for (const Collection &collection : lstCols) { if (collection.parentCollection() == Collection::root()) { if (mRootCollection.isValid()) { qCWarning(AKONADICORE_LOG) << "Resource has more than one root collection. I don't know what to do."; } else { mRootCollection = collection; } } if (collection.hasAttribute()) { mSpecialCollections.append(collection); } } qCDebug(AKONADICORE_LOG) << "Fetched root collection" << mRootCollection.id() << "and" << mSpecialCollections.count() << "local folders" << "(total" << fetchJob->collections().count() << "collections)."; if (!mRootCollection.isValid()) { q->setError(Unknown); q->setErrorText(i18n("Could not fetch root collection of resource %1.", mResourceId)); q->emitResult(); return; } // We are done! q->emitResult(); } ResourceScanJob::ResourceScanJob(const QString &resourceId, KCoreConfigSkeleton *settings, QObject *parent) : Job(parent) , d(new Private(settings, this)) { setResourceId(resourceId); } ResourceScanJob::~ResourceScanJob() { delete d; } QString ResourceScanJob::resourceId() const { return d->mResourceId; } void ResourceScanJob::setResourceId(const QString &resourceId) { d->mResourceId = resourceId; } Akonadi::Collection ResourceScanJob::rootResourceCollection() const { return d->mRootCollection; } Akonadi::Collection::List ResourceScanJob::specialCollections() const { return d->mSpecialCollections; } void ResourceScanJob::doStart() { if (d->mResourceId.isEmpty()) { if (!qobject_cast(this)) { qCCritical(AKONADICORE_LOG) << "No resource ID given."; setError(Job::Unknown); setErrorText(i18n("No resource ID given.")); } emitResult(); return; } CollectionFetchJob *fetchJob = new CollectionFetchJob(Collection::root(), CollectionFetchJob::Recursive, this); fetchJob->fetchScope().setResource(d->mResourceId); fetchJob->fetchScope().setIncludeStatistics(true); fetchJob->fetchScope().setListFilter(CollectionFetchScope::Display); connect(fetchJob, &CollectionFetchJob::result, this, [this](KJob *job) { d->fetchResult(job); }); } // ===================== DefaultResourceJob ============================ /** @internal */ class Akonadi::DefaultResourceJobPrivate { public: DefaultResourceJobPrivate(KCoreConfigSkeleton *settings, DefaultResourceJob *qq); void tryFetchResource(); void resourceCreateResult(KJob *job); // slot void resourceSyncResult(KJob *job); // slot void collectionFetchResult(KJob *job); // slot void collectionModifyResult(KJob *job); // slot DefaultResourceJob *const q; KCoreConfigSkeleton *mSettings = nullptr; QVariantMap mDefaultResourceOptions; QList mKnownTypes; QMap mNameForTypeMap; QMap mIconForTypeMap; QString mDefaultResourceType; int mPendingModifyJobs = 0; bool mResourceWasPreexisting = true; }; DefaultResourceJobPrivate::DefaultResourceJobPrivate(KCoreConfigSkeleton *settings, DefaultResourceJob *qq) : q(qq) , mSettings(settings) , mPendingModifyJobs(0) , mResourceWasPreexisting(true /* for safety, so as not to accidentally delete data */) { } void DefaultResourceJobPrivate::tryFetchResource() { // Get the resourceId from config. Another instance might have changed it in the meantime. mSettings->load(); const QString resourceId = defaultResourceId(mSettings); qCDebug(AKONADICORE_LOG) << "Read defaultResourceId" << resourceId << "from config."; const AgentInstance resource = AgentManager::self()->instance(resourceId); if (resource.isValid()) { // The resource exists; scan it. mResourceWasPreexisting = true; qCDebug(AKONADICORE_LOG) << "Found resource" << resourceId; q->setResourceId(resourceId); CollectionFetchJob *fetchJob = new CollectionFetchJob(Collection::root(), CollectionFetchJob::Recursive, q); fetchJob->fetchScope().setResource(resourceId); fetchJob->fetchScope().setIncludeStatistics(true); q->connect(fetchJob, &CollectionFetchJob::result, q, [this](KJob *job) { collectionFetchResult(job); }); } else { // Try harder: maybe the default resource has been removed and another one added // without updating the config file, in this case search for a resource // of the same type and the default name const AgentInstance::List resources = AgentManager::self()->instances(); for (const AgentInstance &resource : resources) { if (resource.type().identifier() == mDefaultResourceType) { if (resource.name() == mDefaultResourceOptions.value(QStringLiteral("Name")).toString()) { // found a matching one... setDefaultResourceId(mSettings, resource.identifier()); mSettings->save(); mResourceWasPreexisting = true; qCDebug(AKONADICORE_LOG) << "Found resource" << resource.identifier(); q->setResourceId(resource.identifier()); q->ResourceScanJob::doStart(); return; } } } // Create the resource. mResourceWasPreexisting = false; qCDebug(AKONADICORE_LOG) << "Creating maildir resource."; const AgentType type = AgentManager::self()->type(mDefaultResourceType); AgentInstanceCreateJob *job = new AgentInstanceCreateJob(type, q); QObject::connect(job, &AgentInstanceCreateJob::result, q, [this](KJob *job) { resourceCreateResult(job); }); job->start(); // non-Akonadi::Job } } void DefaultResourceJobPrivate::resourceCreateResult(KJob *job) { if (job->error()) { qCWarning(AKONADICORE_LOG) << job->errorText(); //fail( i18n( "Failed to create the default resource (%1).", job->errorString() ) ); q->setError(job->error()); q->setErrorText(job->errorText()); q->emitResult(); return; } AgentInstance agent; // Get the resource instance. { AgentInstanceCreateJob *createJob = qobject_cast(job); Q_ASSERT(createJob); agent = createJob->instance(); setDefaultResourceId(mSettings, agent.identifier()); qCDebug(AKONADICORE_LOG) << "Created maildir resource with id" << defaultResourceId(mSettings); } const QString defaultId = defaultResourceId(mSettings); // Configure the resource. { agent.setName(mDefaultResourceOptions.value(QStringLiteral("Name")).toString()); const auto service = ServerManager::agentServiceName(ServerManager::Resource, defaultId); QDBusInterface conf(service, QStringLiteral("/Settings"), QString()); if (!conf.isValid()) { q->setError(-1); q->setErrorText(i18n("Invalid resource identifier '%1'", defaultId)); q->emitResult(); return; } QMap::const_iterator it = mDefaultResourceOptions.cbegin(); const QMap::const_iterator itEnd = mDefaultResourceOptions.cend(); for (;it != itEnd; ++it) { if (it.key() == QLatin1String("Name")) { continue; } const QString methodName = QStringLiteral("set%1").arg(it.key()); const QVariant::Type argType = argumentType(conf.metaObject(), methodName); if (argType == QVariant::Invalid) { q->setError(Job::Unknown); q->setErrorText(i18n("Failed to configure default resource via D-Bus.")); q->emitResult(); return; } QDBusReply reply = conf.call(methodName, it.value()); if (!reply.isValid()) { q->setError(Job::Unknown); q->setErrorText(i18n("Failed to configure default resource via D-Bus.")); q->emitResult(); return; } } conf.call(QStringLiteral("writeConfig")); agent.reconfigure(); } // Sync the resource. { ResourceSynchronizationJob *syncJob = new ResourceSynchronizationJob(agent, q); QObject::connect(syncJob, &ResourceSynchronizationJob::result, q, [this](KJob *job) { resourceSyncResult(job); }); syncJob->start(); // non-Akonadi } } void DefaultResourceJobPrivate::resourceSyncResult(KJob *job) { if (job->error()) { qCWarning(AKONADICORE_LOG) << job->errorText(); //fail( i18n( "ResourceSynchronizationJob failed (%1).", job->errorString() ) ); return; } // Fetch the collections of the resource. qCDebug(AKONADICORE_LOG) << "Fetching maildir collections."; CollectionFetchJob *fetchJob = new CollectionFetchJob(Collection::root(), CollectionFetchJob::Recursive, q); fetchJob->fetchScope().setResource(defaultResourceId(mSettings)); QObject::connect(fetchJob, &CollectionFetchJob::result, q, [this](KJob *job) { collectionFetchResult(job); }); } void DefaultResourceJobPrivate::collectionFetchResult(KJob *job) { if (job->error()) { qCWarning(AKONADICORE_LOG) << job->errorText(); //fail( i18n( "Failed to fetch the root maildir collection (%1).", job->errorString() ) ); return; } CollectionFetchJob *fetchJob = qobject_cast(job); Q_ASSERT(fetchJob); const Collection::List collections = fetchJob->collections(); qCDebug(AKONADICORE_LOG) << "Fetched" << collections.count() << "collections."; // Find the root maildir collection. Collection::List toRecover; Collection resourceCollection; for (const Collection &collection : collections) { if (collection.parentCollection() == Collection::root()) { resourceCollection = collection; toRecover.append(collection); break; } } if (!resourceCollection.isValid()) { q->setError(Job::Unknown); q->setErrorText(i18n("Failed to fetch the resource collection.")); q->emitResult(); return; } // Find all children of the resource collection. for (const Collection &collection : qAsConst(collections)) { if (collection.parentCollection() == resourceCollection) { toRecover.append(collection); } } QHash typeForName; for (const QByteArray &type : qAsConst(mKnownTypes)) { const QString displayName = mNameForTypeMap.value(type); typeForName[displayName] = type; } // These collections have been created by the maildir resource, when it // found the folders on disk. So give them the necessary attributes now. Q_ASSERT(mPendingModifyJobs == 0); for (Collection collection : qAsConst(toRecover)) { // krazy:exclude=foreach if (collection.hasAttribute()) { continue; } // Find the type for the collection. const QString name = collection.displayName(); const QByteArray type = typeForName.value(name); if (!type.isEmpty()) { qCDebug(AKONADICORE_LOG) << "Recovering collection" << name; setCollectionAttributes(collection, type, mNameForTypeMap, mIconForTypeMap); CollectionModifyJob *modifyJob = new CollectionModifyJob(collection, q); QObject::connect(modifyJob, &CollectionModifyJob::result, q, [this](KJob *job) {collectionModifyResult(job); }); mPendingModifyJobs++; } else { qCDebug(AKONADICORE_LOG) << "Searching for names: " << typeForName.keys(); qCDebug(AKONADICORE_LOG) << "Unknown collection name" << name << "-- not recovering."; } } if (mPendingModifyJobs == 0) { // Scan the resource. q->setResourceId(defaultResourceId(mSettings)); q->ResourceScanJob::doStart(); } } void DefaultResourceJobPrivate::collectionModifyResult(KJob *job) { if (job->error()) { qCWarning(AKONADICORE_LOG) << job->errorText(); //fail( i18n( "Failed to modify the root maildir collection (%1).", job->errorString() ) ); return; } Q_ASSERT(mPendingModifyJobs > 0); mPendingModifyJobs--; qCDebug(AKONADICORE_LOG) << "pendingModifyJobs now" << mPendingModifyJobs; if (mPendingModifyJobs == 0) { // Write the updated config. qCDebug(AKONADICORE_LOG) << "Writing defaultResourceId" << defaultResourceId(mSettings) << "to config."; mSettings->save(); // Scan the resource. q->setResourceId(defaultResourceId(mSettings)); q->ResourceScanJob::doStart(); } } DefaultResourceJob::DefaultResourceJob(KCoreConfigSkeleton *settings, QObject *parent) : ResourceScanJob(QString(), settings, parent) , d(new DefaultResourceJobPrivate(settings, this)) { } DefaultResourceJob::~DefaultResourceJob() { delete d; } void DefaultResourceJob::setDefaultResourceType(const QString &type) { d->mDefaultResourceType = type; } void DefaultResourceJob::setDefaultResourceOptions(const QVariantMap &options) { d->mDefaultResourceOptions = options; } void DefaultResourceJob::setTypes(const QList &types) { d->mKnownTypes = types; } void DefaultResourceJob::setNameForTypeMap(const QMap &map) { d->mNameForTypeMap = map; } void DefaultResourceJob::setIconForTypeMap(const QMap &map) { d->mIconForTypeMap = map; } void DefaultResourceJob::doStart() { d->tryFetchResource(); } void DefaultResourceJob::slotResult(KJob *job) { if (job->error()) { qCWarning(AKONADICORE_LOG) << job->errorText(); // Do some cleanup. if (!d->mResourceWasPreexisting) { // We only removed the resource instance if we have created it. // Otherwise we might lose the user's data. const AgentInstance resource = AgentManager::self()->instance(defaultResourceId(d->mSettings)); qCDebug(AKONADICORE_LOG) << "Removing resource" << resource.identifier(); AgentManager::self()->removeInstance(resource); } } Job::slotResult(job); } // ===================== GetLockJob ============================ class Q_DECL_HIDDEN Akonadi::GetLockJob::Private { public: Private(GetLockJob *qq); void doStart(); // slot - void serviceOwnerChanged(const QString &name, const QString &oldOwner, - const QString &newOwner); // slot void timeout(); // slot GetLockJob *const q; QTimer *mSafetyTimer = nullptr; }; GetLockJob::Private::Private(GetLockJob *qq) : q(qq) , mSafetyTimer(nullptr) { } void GetLockJob::Private::doStart() { // Just doing registerService() and checking its return value is not sufficient, // since we may *already* own the name, and then registerService() returns true. QDBusConnection bus = KDBusConnectionPool::threadConnection(); const bool alreadyLocked = bus.interface()->isServiceRegistered(dbusServiceName()); const bool gotIt = bus.registerService(dbusServiceName()); if (gotIt && !alreadyLocked) { //qCDebug(AKONADICORE_LOG) << "Got lock immediately."; q->emitResult(); } else { - QDBusServiceWatcher *watcher = new QDBusServiceWatcher(dbusServiceName(), KDBusConnectionPool::threadConnection(), - QDBusServiceWatcher::WatchForOwnerChange, q); - //qCDebug(AKONADICORE_LOG) << "Waiting for lock."; - connect(watcher, SIGNAL(serviceOwnerChanged(QString,QString,QString)), q, SLOT(serviceOwnerChanged(QString,QString,QString))); + auto watcher = new QDBusServiceWatcher(dbusServiceName(), KDBusConnectionPool::threadConnection(), + QDBusServiceWatcher::WatchForUnregistration, q); + connect(watcher, &QDBusServiceWatcher::serviceUnregistered, + q, [this]() { + if (KDBusConnectionPool::threadConnection().registerService(dbusServiceName())) { + mSafetyTimer->stop(); + q->emitResult(); + } + }); mSafetyTimer = new QTimer(q); mSafetyTimer->setSingleShot(true); mSafetyTimer->setInterval(LOCK_WAIT_TIMEOUT_SECONDS * 1000); mSafetyTimer->start(); connect(mSafetyTimer, &QTimer::timeout, q, [this]() { timeout(); }); } } -void GetLockJob::Private::serviceOwnerChanged(const QString &name, const QString &oldOwner, const QString &newOwner) -{ - Q_UNUSED(name); - Q_UNUSED(oldOwner); - - if (newOwner.isEmpty()) { - const bool gotIt = KDBusConnectionPool::threadConnection().registerService(dbusServiceName()); - if (gotIt) { - mSafetyTimer->stop(); - q->emitResult(); - } - } -} - void GetLockJob::Private::timeout() { qCWarning(AKONADICORE_LOG) << "Timeout trying to get lock. Check who has acquired the name" << dbusServiceName() << "on DBus, using qdbus or qdbusviewer."; q->setError(Job::Unknown); q->setErrorText(i18n("Timeout trying to get lock.")); q->emitResult(); } GetLockJob::GetLockJob(QObject *parent) : KJob(parent) , d(new Private(this)) { } GetLockJob::~GetLockJob() { delete d; } void GetLockJob::start() { QTimer::singleShot(0, this, [this]() { d->doStart(); }); } void Akonadi::setCollectionAttributes(Akonadi::Collection &collection, const QByteArray &type, const QMap &nameForType, const QMap &iconForType) { { EntityDisplayAttribute *attr = new EntityDisplayAttribute; attr->setIconName(iconForType.value(type)); attr->setDisplayName(nameForType.value(type)); collection.addAttribute(attr); } { SpecialCollectionAttribute *attr = new SpecialCollectionAttribute; attr->setCollectionType(type); collection.addAttribute(attr); } } bool Akonadi::releaseLock() { return KDBusConnectionPool::threadConnection().unregisterService(dbusServiceName()); } #include "moc_specialcollectionshelperjobs_p.cpp" diff --git a/src/core/jobs/specialcollectionshelperjobs_p.h b/src/core/jobs/specialcollectionshelperjobs_p.h index b13c48d6e..37ea2d42c 100644 --- a/src/core/jobs/specialcollectionshelperjobs_p.h +++ b/src/core/jobs/specialcollectionshelperjobs_p.h @@ -1,237 +1,236 @@ /* Copyright (c) 2009 Constantin Berzan 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_SPECIALCOLLECTIONSHELPERJOBS_P_H #define AKONADI_SPECIALCOLLECTIONSHELPERJOBS_P_H #include "akonaditests_export.h" #include "collection.h" #include "specialcollections.h" #include "transactionsequence.h" #include namespace Akonadi { // ===================== ResourceScanJob ============================ /** @internal Helper job for SpecialCollectionsRequestJob. A Job that fetches all the collections of a resource, and returns only those that have a SpecialCollectionAttribute. @author Constantin Berzan @since 4.4 */ class AKONADI_TESTS_EXPORT ResourceScanJob : public Job { Q_OBJECT public: /** Creates a new ResourceScanJob. */ explicit ResourceScanJob(const QString &resourceId, KCoreConfigSkeleton *settings, QObject *parent = nullptr); /** Destroys this ResourceScanJob. */ ~ResourceScanJob() override; /** Returns the resource ID of the resource being scanned. */ Q_REQUIRED_RESULT QString resourceId() const; /** Sets the resource ID of the resource to scan. */ void setResourceId(const QString &resourceId); /** Returns the root collection of the resource being scanned. This function relies on there being a single top-level collection owned by this resource. */ Q_REQUIRED_RESULT Akonadi::Collection rootResourceCollection() const; /** Returns all the collections of this resource which have a SpecialCollectionAttribute. These might include the root resource collection. */ Q_REQUIRED_RESULT Akonadi::Collection::List specialCollections() const; protected: /* reimpl */ void doStart() override; private: class Private; friend class Private; Private *const d; }; // ===================== DefaultResourceJob ============================ class DefaultResourceJobPrivate; /** @internal Helper job for SpecialCollectionsRequestJob. A custom ResourceScanJob for the default local folders resource. This is a maildir resource stored in ~/.local/share/local-mail. This job does two things that a regular ResourceScanJob does not do: 1) It creates and syncs the resource if it is missing. The resource ID is stored in a config file named specialcollectionsrc. 2) If the resource had to be recreated, but the folders existed on disk before that, it recovers the folders based on name. For instance, it will give a folder named outbox a SpecialCollectionAttribute of type Outbox. @author Constantin Berzan @since 4.4 */ class AKONADI_TESTS_EXPORT DefaultResourceJob : public ResourceScanJob { Q_OBJECT public: /** * Creates a new DefaultResourceJob. */ explicit DefaultResourceJob(KCoreConfigSkeleton *settings, QObject *parent = nullptr); /** * Destroys the DefaultResourceJob. */ ~DefaultResourceJob(); /** * Sets the @p type of the resource that shall be created if the requested * special collection does not exist yet. */ void setDefaultResourceType(const QString &type); /** * Sets the configuration @p options that shall be applied to the new resource * that is created if the requested special collection does not exist yet. */ void setDefaultResourceOptions(const QVariantMap &options); /** * Sets the list of well known special collection @p types. */ void setTypes(const QList &types); /** * Sets the @p map of special collection types to display names. */ void setNameForTypeMap(const QMap &map); /** * Sets the @p map of special collection types to icon names. */ void setIconForTypeMap(const QMap &map); protected: /* reimpl */ void doStart() override; /* reimpl */ void slotResult(KJob *job) override; private: friend class DefaultResourceJobPrivate; DefaultResourceJobPrivate *const d; }; // ===================== GetLockJob ============================ /** @internal Helper job for SpecialCollectionsRequestJob. If SpecialCollectionsRequestJob needs to create a collection, it sets a lock so that other instances do not interfere. This lock is an org.kde.pim.SpecialCollections name registered on D-Bus. This job is used to get that lock. This job will give the lock immediately if possible, or wait up to three seconds for the lock to be released. If the lock is not released during that time, this job fails. (This is based on the assumption that SpecialCollectionsRequestJob operations should not take long.) Use the releaseLock() function to release the lock. @author Constantin Berzan @since 4.4 */ class AKONADI_TESTS_EXPORT GetLockJob : public KJob { Q_OBJECT public: /** Creates a new GetLockJob. */ explicit GetLockJob(QObject *parent = nullptr); /** Destroys the GetLockJob. */ ~GetLockJob(); /* reimpl */ void start() override; private: class Private; friend class Private; Private *const d; Q_PRIVATE_SLOT(d, void doStart()) - Q_PRIVATE_SLOT(d, void serviceOwnerChanged(QString, QString, QString)) }; // ===================== helper functions ============================ /** * Sets on @p col the required attributes of SpecialCollection type @p type * These are a SpecialCollectionAttribute and an EntityDisplayAttribute. * @param col collection * @param type collection type * @param nameForType collection name for type * @param iconForType collection icon for type */ void setCollectionAttributes(Akonadi::Collection &col, const QByteArray &type, const QMap &nameForType, const QMap &iconForType); /** Releases the SpecialCollectionsRequestJob lock that was obtained through GetLockJob. @return Whether the lock was released successfully. */ bool AKONADI_TESTS_EXPORT releaseLock(); } // namespace Akonadi #endif // AKONADI_SPECIALCOLLECTIONSHELPERJOBS_P_H diff --git a/src/core/servermanager.cpp b/src/core/servermanager.cpp index fb0de21b6..e68cd7a38 100644 --- a/src/core/servermanager.cpp +++ b/src/core/servermanager.cpp @@ -1,405 +1,407 @@ /* Copyright (c) 2008 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "servermanager.h" #include "servermanager_p.h" #include "agenttype.h" #include "agentmanager.h" #include "KDBusConnectionPool" #include "session_p.h" #include "firstrun_p.h" #include "akonadicore_debug.h" #include #include #include "private/protocol_p.h" #include "private/standarddirs_p.h" #include "private/dbus_p.h" #include "private/instance_p.h" #include #include #include #include #include #include #include #include +#include +#include using namespace Akonadi; class Akonadi::ServerManagerPrivate { public: ServerManagerPrivate() : instance(new ServerManager(this)) , mState(ServerManager::NotRunning) , mSafetyTimer(new QTimer) { mState = instance->state(); mSafetyTimer->setSingleShot(true); mSafetyTimer->setInterval(30000); QObject::connect(mSafetyTimer.data(), &QTimer::timeout, instance, [this]() { timeout();}); if (mState == ServerManager::Running && Internal::clientType() == Internal::User && !ServerManager::hasInstanceIdentifier()) { mFirstRunner = new Firstrun(instance); } } ~ServerManagerPrivate() { delete instance; } - void serviceOwnerChanged(const QString &name, const QString &oldOwner, const QString &newOwner) - { - if (name == ServerManager::serviceName(ServerManager::ControlLock) && !oldOwner.isEmpty() && newOwner.isEmpty()) { - // Control.Lock has disappeared during startup, which means that akonadi_control - // has terminated, most probably because it was not able to start akonadiserver - // process. Don't wait 30 seconds for sefetyTimeout, but go into Broken state - // immediately. - if (mState == ServerManager::Starting) { - setState(ServerManager::Broken); - return; - } - } - - serverProtocolVersion = -1; - checkStatusChanged(); - } - void checkStatusChanged() { setState(instance->state()); } void setState(ServerManager::State state) { - if (mState != state) { mState = state; Q_EMIT instance->stateChanged(state); if (state == ServerManager::Running) { Q_EMIT instance->started(); if (!mFirstRunner && Internal::clientType() == Internal::User && !ServerManager::hasInstanceIdentifier()) { mFirstRunner = new Firstrun(instance); } } else if (state == ServerManager::NotRunning || state == ServerManager::Broken) { Q_EMIT instance->stopped(); } if (state == ServerManager::Starting || state == ServerManager::Stopping) { QMetaObject::invokeMethod(mSafetyTimer.data(), QOverload<>::of(&QTimer::start), Qt::QueuedConnection); // in case we are in a different thread } else { QMetaObject::invokeMethod(mSafetyTimer.data(), &QTimer::stop, Qt::QueuedConnection); // in case we are in a different thread } } } void timeout() { if (mState == ServerManager::Starting || mState == ServerManager::Stopping) { setState(ServerManager::Broken); } } ServerManager *instance = nullptr; static int serverProtocolVersion; static uint generation; ServerManager::State mState; QScopedPointer mSafetyTimer; Firstrun *mFirstRunner = nullptr; static Internal::ClientType clientType; QString mBrokenReason; + std::unique_ptr serviceWatcher; }; int ServerManagerPrivate::serverProtocolVersion = -1; uint ServerManagerPrivate::generation = 0; Internal::ClientType ServerManagerPrivate::clientType = Internal::User; Q_GLOBAL_STATIC(ServerManagerPrivate, sInstance) ServerManager::ServerManager(ServerManagerPrivate *dd) : d(dd) { Kdelibs4ConfigMigrator migrate(QStringLiteral("servermanager")); migrate.setConfigFiles(QStringList() << QStringLiteral("akonadi-firstrunrc")); migrate.migrate(); qRegisterMetaType(); - QDBusServiceWatcher *watcher = new QDBusServiceWatcher(ServerManager::serviceName(ServerManager::Server), - KDBusConnectionPool::threadConnection(), - QDBusServiceWatcher::WatchForOwnerChange, this); - watcher->addWatchedService(ServerManager::serviceName(ServerManager::Control)); - watcher->addWatchedService(ServerManager::serviceName(ServerManager::ControlLock)); - watcher->addWatchedService(ServerManager::serviceName(ServerManager::UpgradeIndicator)); + d->serviceWatcher = std::make_unique( + ServerManager::serviceName(ServerManager::Server), KDBusConnectionPool::threadConnection(), + QDBusServiceWatcher::WatchForRegistration | QDBusServiceWatcher::WatchForUnregistration); + d->serviceWatcher->addWatchedService(ServerManager::serviceName(ServerManager::Control)); + d->serviceWatcher->addWatchedService(ServerManager::serviceName(ServerManager::ControlLock)); + d->serviceWatcher->addWatchedService(ServerManager::serviceName(ServerManager::UpgradeIndicator)); // this (and also the two connects below) are queued so that they trigger after AgentManager is done loading // the current agent types and instances // this ensures the invariant of AgentManager reporting a consistent state if ServerManager::state() == Running // that's the case with direct connections as well, but only after you enter the event loop once - connect(watcher, SIGNAL(serviceOwnerChanged(QString,QString,QString)), - this, SLOT(serviceOwnerChanged(QString,QString,QString)), Qt::QueuedConnection); + connect(d->serviceWatcher.get(), &QDBusServiceWatcher::serviceRegistered, + this, [this]() { + d->serverProtocolVersion = -1; + d->checkStatusChanged(); + }, Qt::QueuedConnection); + connect(d->serviceWatcher.get(), &QDBusServiceWatcher::serviceUnregistered, + this, [this](const QString &name) { + if (name == ServerManager::serviceName(ServerManager::ControlLock) && d->mState == ServerManager::Starting) { + // Control.Lock has disappeared during startup, which means that akonadi_control + // has terminated, most probably because it was not able to start akonadiserver + // process. Don't wait 30 seconds for sefetyTimeout, but go into Broken state + // immediately. + d->setState(ServerManager::Broken); + return; + } + + d->serverProtocolVersion = -1; + d->checkStatusChanged(); + }, Qt::QueuedConnection); // AgentManager is dangerous to use for agents themselves if (Internal::clientType() != Internal::User) { return; } connect(AgentManager::self(), SIGNAL(typeAdded(Akonadi::AgentType)), SLOT(checkStatusChanged()), Qt::QueuedConnection); connect(AgentManager::self(), SIGNAL(typeRemoved(Akonadi::AgentType)), SLOT(checkStatusChanged()), Qt::QueuedConnection); } ServerManager *Akonadi::ServerManager::self() { return sInstance->instance; } bool ServerManager::start() { const bool controlRegistered = KDBusConnectionPool::threadConnection().interface()->isServiceRegistered(ServerManager::serviceName(ServerManager::Control)); const bool serverRegistered = KDBusConnectionPool::threadConnection().interface()->isServiceRegistered(ServerManager::serviceName(ServerManager::Server)); if (controlRegistered && serverRegistered) { return true; } const bool controlLockRegistered = KDBusConnectionPool::threadConnection().interface()->isServiceRegistered(ServerManager::serviceName(ServerManager::ControlLock)); if (controlLockRegistered || controlRegistered) { qCDebug(AKONADICORE_LOG) << "Akonadi server is already starting up"; sInstance->setState(Starting); return true; } qCDebug(AKONADICORE_LOG) << "executing akonadi_control"; QStringList args; if (hasInstanceIdentifier()) { args << QStringLiteral("--instance") << instanceIdentifier(); } const bool ok = QProcess::startDetached(QStringLiteral("akonadi_control"), args); if (!ok) { qCWarning(AKONADICORE_LOG) << "Unable to execute akonadi_control, falling back to D-Bus auto-launch"; QDBusReply reply = KDBusConnectionPool::threadConnection().interface()->startService(ServerManager::serviceName(ServerManager::Control)); if (!reply.isValid()) { qCDebug(AKONADICORE_LOG) << "Akonadi server could not be started via D-Bus either: " << reply.error().message(); return false; } } sInstance->setState(Starting); return true; } bool ServerManager::stop() { QDBusInterface iface(ServerManager::serviceName(ServerManager::Control), QStringLiteral("/ControlManager"), QStringLiteral("org.freedesktop.Akonadi.ControlManager")); if (!iface.isValid()) { return false; } iface.call(QDBus::NoBlock, QStringLiteral("shutdown")); sInstance->setState(Stopping); return true; } void ServerManager::showSelfTestDialog(QWidget *parent) { Q_UNUSED(parent); QProcess::startDetached(QStringLiteral("akonadiselftest")); } bool ServerManager::isRunning() { return state() == Running; } ServerManager::State ServerManager::state() { ServerManager::State previousState = NotRunning; if (sInstance.exists()) { // be careful, this is called from the ServerManager::Private ctor, so using sInstance unprotected can cause infinite recursion previousState = sInstance->mState; sInstance->mBrokenReason.clear(); } const bool serverUpgrading = KDBusConnectionPool::threadConnection().interface()->isServiceRegistered(ServerManager::serviceName(ServerManager::UpgradeIndicator)); if (serverUpgrading) { return Upgrading; } const bool controlRegistered = KDBusConnectionPool::threadConnection().interface()->isServiceRegistered(ServerManager::serviceName(ServerManager::Control)); const bool serverRegistered = KDBusConnectionPool::threadConnection().interface()->isServiceRegistered(ServerManager::serviceName(ServerManager::Server)); if (controlRegistered && serverRegistered) { // check if the server protocol is recent enough if (sInstance.exists()) { if (Internal::serverProtocolVersion() >= 0 && Internal::serverProtocolVersion() != Protocol::version()) { sInstance->mBrokenReason = i18n("The Akonadi server protocol version differs from the protocol version used by this application.\n" "If you recently updated your system please log out and back in to make sure all applications use the " "correct protocol version."); return Broken; } } // AgentManager is dangerous to use for agents themselves if (Internal::clientType() == Internal::User) { // besides the running server processes we also need at least one resource to be operational const AgentType::List agentTypes = AgentManager::self()->types(); for (const AgentType &type : agentTypes) { if (type.capabilities().contains(QLatin1String("Resource"))) { return Running; } } if (sInstance.exists()) { sInstance->mBrokenReason = i18n("There are no Akonadi Agents available. Please verify your KDE PIM installation."); } return Broken; } else { return Running; } } const bool controlLockRegistered = KDBusConnectionPool::threadConnection().interface()->isServiceRegistered(ServerManager::serviceName(ServerManager::ControlLock)); if (controlLockRegistered || controlRegistered) { qCDebug(AKONADICORE_LOG) << "Akonadi server is only partially running. Server:" << serverRegistered << "ControlLock:" << controlLockRegistered << "Control:" << controlRegistered; if (previousState == Running) { return NotRunning; // we don't know if it's starting or stopping, probably triggered by someone else } return previousState; } if (serverRegistered) { qCWarning(AKONADICORE_LOG) << "Akonadi server running without control process!"; return Broken; } if (previousState == Starting) { // valid case where nothing is running (yet) return previousState; } return NotRunning; } QString ServerManager::brokenReason() { if (sInstance.exists()) { return sInstance->mBrokenReason; } return QString(); } QString ServerManager::instanceIdentifier() { return Instance::identifier(); } bool ServerManager::hasInstanceIdentifier() { return Instance::hasIdentifier(); } QString ServerManager::serviceName(ServerManager::ServiceType serviceType) { switch (serviceType) { case Server: return DBus::serviceName(DBus::Server); case Control: return DBus::serviceName(DBus::Control); case ControlLock: return DBus::serviceName(DBus::ControlLock); case UpgradeIndicator: return DBus::serviceName(DBus::UpgradeIndicator); } Q_ASSERT(!"WTF?"); return QString(); } QString ServerManager::agentServiceName(ServiceAgentType agentType, const QString &identifier) { switch (agentType) { case Agent: return DBus::agentServiceName(identifier, DBus::Agent); case Resource: return DBus::agentServiceName(identifier, DBus::Resource); case Preprocessor: return DBus::agentServiceName(identifier, DBus::Preprocessor); } Q_ASSERT(!"WTF?"); return QString(); } QString ServerManager::serverConfigFilePath(OpenMode openMode) { return StandardDirs::serverConfigFile( openMode == Akonadi::ServerManager::ReadOnly ? StandardDirs::ReadOnly : StandardDirs::ReadWrite); } QString ServerManager::agentConfigFilePath(const QString &identifier) { return StandardDirs::agentConfigFile(identifier); } QString ServerManager::addNamespace(const QString &string) { if (Instance::hasIdentifier()) { return string % QLatin1Char('_') % Instance::identifier(); } return string; } uint ServerManager::generation() { return Internal::generation(); } int Internal::serverProtocolVersion() { return ServerManagerPrivate::serverProtocolVersion; } void Internal::setServerProtocolVersion(int version) { ServerManagerPrivate::serverProtocolVersion = version; if (sInstance.exists()) { sInstance->checkStatusChanged(); } } uint Internal::generation() { return ServerManagerPrivate::generation; } void Internal::setGeneration(uint generation) { ServerManagerPrivate::generation = generation; } Internal::ClientType Internal::clientType() { return ServerManagerPrivate::clientType; } void Internal::setClientType(ClientType type) { ServerManagerPrivate::clientType = type; } #include "moc_servermanager.cpp" diff --git a/src/core/servermanager.h b/src/core/servermanager.h index c00dee4bf..b4088ce2f 100644 --- a/src/core/servermanager.h +++ b/src/core/servermanager.h @@ -1,244 +1,243 @@ /* Copyright (c) 2008 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef AKONADI_SERVERMANAGER_H #define AKONADI_SERVERMANAGER_H #include "akonadicore_export.h" #include #include namespace Akonadi { class ServerManagerPrivate; /** * @short Provides methods to control the Akonadi server process. * * Asynchronous, low-level control of the Akonadi server. * Akonadi::Control provides a synchronous interface to some of the methods in here. * * @author Volker Krause * @see Akonadi::Control * @since 4.2 */ class AKONADICORE_EXPORT ServerManager : public QObject { Q_OBJECT public: /** * Enum for the various states the server can be in. * @since 4.5 */ enum State { NotRunning, ///< Server is not running, could be no one started it yet or it failed to start. Starting, ///< Server was started but is not yet running. Running, ///< Server is running and operational. Stopping, ///< Server is shutting down. Broken, ///< Server is not operational and an error has been detected. Upgrading ///< Server is performing a database upgrade as part of a new startup. }; /** * Starts the server. This method returns immediately and does not wait * until the server is actually up and running. * @return @c true if the start was possible (which not necessarily means * the server is really running though) and @c false if an immediate error occurred. * @see Akonadi::Control::start() */ static bool start(); /** * Stops the server. This methods returns immediately after the shutdown * command has been send and does not wait until the server is actually * shut down. * @return @c true if the shutdown command was sent successfully, @c false * otherwise */ static bool stop(); /** * Shows the Akonadi self test dialog, which tests Akonadi for various problems * and reports these to the user if. * @param parent the parent widget for the dialog */ static void showSelfTestDialog(QWidget *parent); /** * Checks if the server is available currently. For more detailed status information * see state(). * @see state() */ Q_REQUIRED_RESULT static bool isRunning(); /** * Returns the state of the server. * @since 4.5 */ Q_REQUIRED_RESULT static State state(); /** * Returns the reason why the Server is broken, if known. * * If state() is @p Broken, then you can use this method to obtain a more * detailed description of the problem and present it to users. Note that * the message can be empty if the reason is not known. * * @since 5.6 */ Q_REQUIRED_RESULT static QString brokenReason(); /** * Returns the identifier of the Akonadi instance we are connected to. This is usually * an empty string (representing the default instance), unless you have explicitly set * the AKONADI_INSTANCE environment variable to connect to a different one. * @since 4.10 */ Q_REQUIRED_RESULT static QString instanceIdentifier(); /** * Returns @c true if we are connected to a non-default Akonadi server instance. * @since 4.10 */ Q_REQUIRED_RESULT static bool hasInstanceIdentifier(); /** * Types of known D-Bus services. * @since 4.10 */ enum ServiceType { Server, Control, ControlLock, UpgradeIndicator }; /** * Returns the namespaced D-Bus service name for @p serviceType. * Use this rather the raw service name strings in order to support usage of a non-default * instance of the Akonadi server. * @param serviceType the service type for which to return the D-Bus name * @since 4.10 */ static QString serviceName(ServiceType serviceType); /** * Known agent types. * @since 4.10 */ enum ServiceAgentType { Agent, Resource, Preprocessor }; /** * Returns the namespaced D-Bus service name for an agent of type @p agentType with agent * identifier @p identifier. * @param agentType the agent type to use for D-Bus base name * @param identifier the agent identifier to include in the D-Bus name * @since 4.10 */ Q_REQUIRED_RESULT static QString agentServiceName(ServiceAgentType agentType, const QString &identifier); /** * Adds the multi-instance namespace to @p string if required (with '_' as separator). * Use whenever a multi-instance safe name is required (configfiles, identifiers, ...). * @param string the string to adapt * @since 4.10 */ Q_REQUIRED_RESULT static QString addNamespace(const QString &string); /** * Returns the singleton instance of this class, for connecting to its * signals */ static ServerManager *self(); enum OpenMode { ReadOnly, ReadWrite }; /** * Returns absolute path to akonadiserverrc file with Akonadi server * configuration. */ Q_REQUIRED_RESULT static QString serverConfigFilePath(OpenMode openMode); /** * Returns absolute path to configuration file of an agent identified by * given @p identifier. */ Q_REQUIRED_RESULT static QString agentConfigFilePath(const QString &identifier); /** * Returns current Akonadi database generation identifier * * Generation is guaranteed to never change unless as long as the database * backend is not removed and re-created. In such case it is guaranteed that * the new generation number will be higher than the previous one. * * Generation can be used by applications to detect when Akonadi database * has been recreated and thus some of the configuration (for example * collection IDs stored in a config file) must be invalidated. * * @note Note that the generation number is only available if the server * is running. If this function is called before the server starts it will * return 0. * * @since 5.4 */ Q_REQUIRED_RESULT static uint generation(); Q_SIGNALS: /** * Emitted whenever the server becomes fully operational. */ void started(); /** * Emitted whenever the server becomes unavailable. */ void stopped(); /** * Emitted whenever the server state changes. * @param state the new server state * @since 4.5 */ void stateChanged(Akonadi::ServerManager::State state); private: //@cond PRIVATE friend class ServerManagerPrivate; ServerManager(ServerManagerPrivate *dd); ServerManagerPrivate *const d; - Q_PRIVATE_SLOT(d, void serviceOwnerChanged(const QString &, const QString &, const QString &)) Q_PRIVATE_SLOT(d, void checkStatusChanged()) Q_PRIVATE_SLOT(d, void timeout()) //@endcond }; } Q_DECLARE_METATYPE(Akonadi::ServerManager::State) #endif diff --git a/src/server/akonadi.cpp b/src/server/akonadi.cpp index 4f71a6a53..6d11d0a27 100644 --- a/src/server/akonadi.cpp +++ b/src/server/akonadi.cpp @@ -1,425 +1,417 @@ /*************************************************************************** * Copyright (C) 2006 by Till Adam * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "akonadi.h" #include "handler.h" #include "connection.h" #include "serveradaptor.h" #include "akonadiserver_debug.h" #include "cachecleaner.h" #include "intervalcheck.h" #include "storagejanitor.h" #include "storage/dbconfig.h" #include "storage/datastore.h" #include "notificationmanager.h" #include "resourcemanager.h" #include "tracer.h" #include "utils.h" #include "debuginterface.h" #include "storage/itemretrievalmanager.h" #include "storage/collectionstatistics.h" #include "preprocessormanager.h" #include "search/searchmanager.h" #include "search/searchtaskmanager.h" #include "aklocalserver.h" #include #include #include #include #include #include #include #include #include #include #include using namespace Akonadi; using namespace Akonadi::Server; AkonadiServer *AkonadiServer::s_instance = nullptr; AkonadiServer::AkonadiServer(QObject *parent) : QObject(parent) { // Register bunch of useful types qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType("quintptr"); } bool AkonadiServer::init() { qCInfo(AKONADISERVER_LOG) << "Starting up the Akonadi Server..."; const QString serverConfigFile = StandardDirs::serverConfigFile(StandardDirs::ReadWrite); QSettings settings(serverConfigFile, QSettings::IniFormat); // Restrict permission to 600, as the file might contain database password in plaintext QFile::setPermissions(serverConfigFile, QFile::ReadOwner | QFile::WriteOwner); if (!DbConfig::configuredDatabase()) { quit(); return false; } if (DbConfig::configuredDatabase()->useInternalServer()) { if (!startDatabaseProcess()) { quit(); return false; } } else { if (!createDatabase()) { quit(); return false; } } DbConfig::configuredDatabase()->setup(); s_instance = this; const QString connectionSettingsFile = StandardDirs::connectionConfigFile(StandardDirs::WriteOnly); QSettings connectionSettings(connectionSettingsFile, QSettings::IniFormat); mCmdServer = new AkLocalServer(this); connect(mCmdServer, QOverload::of(&AkLocalServer::newConnection), this, &AkonadiServer::newCmdConnection); mNotificationManager = new NotificationManager(); mNtfServer = new AkLocalServer(this); // Note: this is a queued connection, as NotificationManager lives in its // own thread connect(mNtfServer, QOverload::of(&AkLocalServer::newConnection), mNotificationManager, &NotificationManager::registerConnection); // TODO: share socket setup with client #ifdef Q_OS_WIN // use the installation prefix as uid QString suffix; if (Instance::hasIdentifier()) { suffix = QStringLiteral("%1-").arg(Instance::identifier()); } suffix += QString::fromUtf8(QUrl::toPercentEncoding(qApp->applicationDirPath())); const QString defaultCmdPipe = QStringLiteral("Akonadi-Cmd-") % suffix; const QString cmdPipe = settings.value(QStringLiteral("Connection/NamedPipe"), defaultCmdPipe).toString(); if (!mCmdServer->listen(cmdPipe)) { qCCritical(AKONADISERVER_LOG) << "Unable to listen on Named Pipe" << cmdPipe << ":" << mCmdServer->errorString(); quit(); return false; } const QString defaultNtfPipe = QStringLiteral("Akonadi-Ntf-") % suffix; const QString ntfPipe = settings.value(QStringLiteral("Connection/NtfNamedPipe"), defaultNtfPipe).toString(); if (!mNtfServer->listen(ntfPipe)) { qCCritical(AKONADISERVER_LOG) << "Unable to listen on Named Pipe" << ntfPipe << ":" << mNtfServer->errorString(); quit(); return false; } connectionSettings.setValue(QStringLiteral("Data/Method"), QStringLiteral("NamedPipe")); connectionSettings.setValue(QStringLiteral("Data/NamedPipe"), cmdPipe); connectionSettings.setValue(QStringLiteral("Notifications/Method"), QStringLiteral("NamedPipe")); connectionSettings.setValue(QStringLiteral("Notifications/NamedPipe"), ntfPipe); #else const QString cmdSocketName = QStringLiteral("akonadiserver-cmd.socket"); const QString ntfSocketName = QStringLiteral("akonadiserver-ntf.socket"); const QString socketDir = Utils::preferredSocketDirectory(StandardDirs::saveDir("data"), qMax(cmdSocketName.length(), ntfSocketName.length())); const QString cmdSocketFile = socketDir % QLatin1Char('/') % cmdSocketName; QFile::remove(cmdSocketFile); if (!mCmdServer->listen(cmdSocketFile)) { qCCritical(AKONADISERVER_LOG) << "Unable to listen on Unix socket" << cmdSocketFile << ":" << mCmdServer->errorString(); quit(); return false; } const QString ntfSocketFile = socketDir % QLatin1Char('/') % ntfSocketName; QFile::remove(ntfSocketFile); if (!mNtfServer->listen(ntfSocketFile)) { qCCritical(AKONADISERVER_LOG) << "Unable to listen on Unix socket" << ntfSocketFile << ":" << mNtfServer->errorString(); quit(); return false; } connectionSettings.setValue(QStringLiteral("Data/Method"), QStringLiteral("UnixPath")); connectionSettings.setValue(QStringLiteral("Data/UnixPath"), cmdSocketFile); connectionSettings.setValue(QStringLiteral("Notifications/Method"), QStringLiteral("UnixPath")); connectionSettings.setValue(QStringLiteral("Notifications/UnixPath"), ntfSocketFile); #endif // initialize the database DataStore *db = DataStore::self(); if (!db->database().isOpen()) { qCCritical(AKONADISERVER_LOG) << "Unable to open database" << db->database().lastError().text(); quit(); return false; } if (!db->init()) { qCCritical(AKONADISERVER_LOG) << "Unable to initialize database."; quit(); return false; } Tracer::self(); new DebugInterface(this); ResourceManager::self(); CollectionStatistics::self(); // Initialize the preprocessor manager PreprocessorManager::init(); // Forcibly disable it if configuration says so if (settings.value(QStringLiteral("General/DisablePreprocessing"), false).toBool()) { PreprocessorManager::instance()->setEnabled(false); } if (settings.value(QStringLiteral("Cache/EnableCleaner"), true).toBool()) { mCacheCleaner = new CacheCleaner(); } mIntervalCheck = new IntervalCheck(); mStorageJanitor = new StorageJanitor(); mItemRetrieval = new ItemRetrievalManager(); mAgentSearchManager = new SearchTaskManager(); const QStringList searchManagers = settings.value(QStringLiteral("Search/Manager"), QStringList() << QStringLiteral("Agent")).toStringList(); mSearchManager = new SearchManager(searchManagers); new ServerAdaptor(this); QDBusConnection::sessionBus().registerObject(QStringLiteral("/Server"), this); const QByteArray dbusAddress = qgetenv("DBUS_SESSION_BUS_ADDRESS"); if (!dbusAddress.isEmpty()) { connectionSettings.setValue(QStringLiteral("DBUS/Address"), QLatin1String(dbusAddress)); } - QDBusServiceWatcher *watcher = new QDBusServiceWatcher(DBus::serviceName(DBus::Control), - QDBusConnection::sessionBus(), - QDBusServiceWatcher::WatchForOwnerChange, this); - - connect(watcher, &QDBusServiceWatcher::serviceOwnerChanged, - this, &AkonadiServer::serviceOwnerChanged); + mControlWatcher = std::make_unique( + DBus::serviceName(DBus::Control), QDBusConnection::sessionBus(), + QDBusServiceWatcher::WatchForUnregistration); + connect(mControlWatcher.get(), &QDBusServiceWatcher::serviceUnregistered, + this, [this]() { + qCCritical(AKONADISERVER_LOG) << "Control process died, committing suicide!"; + quit(); + }); // Unhide all the items that are actually hidden. // The hidden flag was probably left out after an (abrupt) // server quit. We don't attempt to resume preprocessing // for the items as we don't actually know at which stage the // operation was interrupted... db->unhideAllPimItems(); // We are ready, now register org.freedesktop.Akonadi service to DBus and // the fun can begin if (!QDBusConnection::sessionBus().registerService(DBus::serviceName(DBus::Server))) { qCCritical(AKONADISERVER_LOG) << "Unable to connect to dbus service: " << QDBusConnection::sessionBus().lastError().message(); quit(); return false; } return true; } AkonadiServer::~AkonadiServer() { } template static void quitThread(T &thread) { if (thread) { thread->quit(); thread->wait(); delete thread; thread = nullptr; } } bool AkonadiServer::quit() { if (mAlreadyShutdown) { return true; } mAlreadyShutdown = true; qCDebug(AKONADISERVER_LOG) << "terminating connection threads"; qDeleteAll(mConnections); mConnections.clear(); qCDebug(AKONADISERVER_LOG) << "terminating service threads"; delete mCacheCleaner; delete mIntervalCheck; delete mStorageJanitor; delete mItemRetrieval; delete mAgentSearchManager; delete mSearchManager; delete mNotificationManager; // Terminate the preprocessor manager before the database but after all connections are gone PreprocessorManager::done(); CollectionStatistics::destroy(); if (DbConfig::isConfigured()) { if (DataStore::hasDataStore()) { DataStore::self()->close(); } qCDebug(AKONADISERVER_LOG) << "stopping db process"; stopDatabaseProcess(); } //QSettings settings(StandardDirs::serverConfigFile(), QSettings::IniFormat); const QString connectionSettingsFile = StandardDirs::connectionConfigFile(StandardDirs::WriteOnly); if (!QDir::home().remove(connectionSettingsFile)) { qCCritical(AKONADISERVER_LOG) << "Failed to remove runtime connection config file"; } QTimer::singleShot(0, this, &AkonadiServer::doQuit); return true; } void AkonadiServer::doQuit() { QCoreApplication::exit(); } void AkonadiServer::newCmdConnection(quintptr socketDescriptor) { if (mAlreadyShutdown) { return; } Connection *connection = new Connection(socketDescriptor); connect(connection, &Connection::disconnected, this, &AkonadiServer::connectionDisconnected); mConnections.append(connection); } void AkonadiServer::connectionDisconnected() { auto conn = qobject_cast(sender()); mConnections.removeOne(conn); delete conn; } AkonadiServer *AkonadiServer::instance() { if (!s_instance) { s_instance = new AkonadiServer(); } return s_instance; } bool AkonadiServer::startDatabaseProcess() { if (!DbConfig::configuredDatabase()->useInternalServer()) { qCCritical(AKONADISERVER_LOG) << "Trying to start external database!"; } // create the database directories if they don't exists StandardDirs::saveDir("data"); StandardDirs::saveDir("data", QStringLiteral("file_db_data")); return DbConfig::configuredDatabase()->startInternalServer(); } bool AkonadiServer::createDatabase() { bool success = true; const QLatin1String initCon("initConnection"); QSqlDatabase db = QSqlDatabase::addDatabase(DbConfig::configuredDatabase()->driverName(), initCon); DbConfig::configuredDatabase()->apply(db); db.setDatabaseName(DbConfig::configuredDatabase()->databaseName()); if (!db.isValid()) { qCCritical(AKONADISERVER_LOG) << "Invalid database object during initial database connection"; return false; } if (db.open()) { db.close(); } else { qCCritical(AKONADISERVER_LOG) << "Failed to use database" << DbConfig::configuredDatabase()->databaseName(); qCCritical(AKONADISERVER_LOG) << "Database error:" << db.lastError().text(); qCDebug(AKONADISERVER_LOG) << "Trying to create database now..."; db.close(); db.setDatabaseName(QString()); if (db.open()) { { QSqlQuery query(db); if (!query.exec(QStringLiteral("CREATE DATABASE %1").arg(DbConfig::configuredDatabase()->databaseName()))) { qCCritical(AKONADISERVER_LOG) << "Failed to create database"; qCCritical(AKONADISERVER_LOG) << "Query error:" << query.lastError().text(); qCCritical(AKONADISERVER_LOG) << "Database error:" << db.lastError().text(); success = false; } } // make sure query is destroyed before we close the db db.close(); } else { qCCritical(AKONADISERVER_LOG) << "Failed to connect to database!"; qCCritical(AKONADISERVER_LOG) << "Database error:" << db.lastError().text(); success = false; } } return success; } void AkonadiServer::stopDatabaseProcess() { if (!DbConfig::configuredDatabase()->useInternalServer()) { // closing initConnection this late to work around QTBUG-63108 QSqlDatabase::removeDatabase(QStringLiteral("initConnection")); return; } DbConfig::configuredDatabase()->stopInternalServer(); } -void AkonadiServer::serviceOwnerChanged(const QString &name, const QString &oldOwner, const QString &newOwner) -{ - Q_UNUSED(name); - Q_UNUSED(oldOwner); - if (newOwner.isEmpty()) { - qCCritical(AKONADISERVER_LOG) << "Control process died, committing suicide!"; - quit(); - } -} - CacheCleaner *AkonadiServer::cacheCleaner() { return mCacheCleaner; } IntervalCheck *AkonadiServer::intervalChecker() { return mIntervalCheck; } NotificationManager *AkonadiServer::notificationManager() { return mNotificationManager; } QString AkonadiServer::serverPath() const { return StandardDirs::saveDir("config"); } diff --git a/src/server/akonadi.h b/src/server/akonadi.h index 7815b4e51..b97d4feb6 100644 --- a/src/server/akonadi.h +++ b/src/server/akonadi.h @@ -1,115 +1,119 @@ /*************************************************************************** * Copyright (C) 2006 by Till Adam * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef AKONADISERVER_H #define AKONADISERVER_H #include #include +#include + class QProcess; +class QDBusServiceWatcher; namespace Akonadi { namespace Server { class Connection; class ItemRetrievalManager; class SearchTaskManager; class SearchManager; class StorageJanitor; class CacheCleaner; class IntervalCheck; class AkLocalServer; class NotificationManager; class AkonadiServer : public QObject { Q_OBJECT public: ~AkonadiServer(); static AkonadiServer *instance(); /** * Can return a nullptr */ CacheCleaner *cacheCleaner(); /** * Returns the IntervalCheck instance. Never nullptr. */ IntervalCheck *intervalChecker(); /** * Instance-aware server .config directory */ QString serverPath() const; /** * Can return a nullptr */ NotificationManager *notificationManager(); public Q_SLOTS: /** * Triggers a clean server shutdown. */ virtual bool quit(); virtual bool init(); protected Q_SLOTS: virtual void newCmdConnection(quintptr socketDescriptor); private Q_SLOTS: void doQuit(); - void serviceOwnerChanged(const QString &name, const QString &oldOwner, const QString &newOwner); void connectionDisconnected(); private: bool startDatabaseProcess(); bool createDatabase(); void stopDatabaseProcess(); uint userId() const; protected: AkonadiServer(QObject *parent = nullptr); + std::unique_ptr mControlWatcher; + AkLocalServer *mCmdServer = nullptr; AkLocalServer *mNtfServer = nullptr; NotificationManager *mNotificationManager = nullptr; CacheCleaner *mCacheCleaner = nullptr; IntervalCheck *mIntervalCheck = nullptr; StorageJanitor *mStorageJanitor = nullptr; ItemRetrievalManager *mItemRetrieval = nullptr; SearchTaskManager *mAgentSearchManager = nullptr; QProcess *mDatabaseProcess = nullptr; QVector mConnections; SearchManager *mSearchManager = nullptr; bool mAlreadyShutdown = false; static AkonadiServer *s_instance; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/search/agentsearchinstance.cpp b/src/server/search/agentsearchinstance.cpp index d7685c025..3a40fe968 100644 --- a/src/server/search/agentsearchinstance.cpp +++ b/src/server/search/agentsearchinstance.cpp @@ -1,86 +1,76 @@ /* Copyright (c) 2013 Daniel Vrátil This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "agentsearchinstance.h" #include "agentsearchinterface.h" #include "searchtaskmanager.h" #include "dbusconnectionpool.h" #include using namespace Akonadi; using namespace Akonadi::Server; AgentSearchInstance::AgentSearchInstance(const QString &id) : mId(id) , mInterface(nullptr) - , mServiceWatcher(nullptr) { } AgentSearchInstance::~AgentSearchInstance() { delete mInterface; } bool AgentSearchInstance::init() { Q_ASSERT(!mInterface); mInterface = new OrgFreedesktopAkonadiAgentSearchInterface( DBus::agentServiceName(mId, DBus::Agent), QStringLiteral("/Search"), DBusConnectionPool::threadConnection()); if (!mInterface || !mInterface->isValid()) { delete mInterface; mInterface = nullptr; return false; } - mServiceWatcher = new QDBusServiceWatcher(DBus::agentServiceName(mId, DBus::Agent), - DBusConnectionPool::threadConnection(), - QDBusServiceWatcher::WatchForOwnerChange, - this); - connect(mServiceWatcher, &QDBusServiceWatcher::serviceOwnerChanged, - this, &AgentSearchInstance::serviceOwnerChanged); + mServiceWatcher = std::make_unique( + DBus::agentServiceName(mId, DBus::Agent), DBusConnectionPool::threadConnection(), + QDBusServiceWatcher::WatchForUnregistration); + connect(mServiceWatcher.get(), &QDBusServiceWatcher::serviceUnregistered, + this, [this]() { + SearchTaskManager::instance()->unregisterInstance(mId); + }); return true; } -void AgentSearchInstance::serviceOwnerChanged(const QString &service, const QString &oldName, const QString &newName) -{ - Q_UNUSED(service); - Q_UNUSED(oldName); - - if (newName.isEmpty()) { - SearchTaskManager::instance()->unregisterInstance(mId); - } -} - void AgentSearchInstance::search(const QByteArray &searchId, const QString &query, qlonglong collectionId) { mInterface->search(searchId, query, collectionId); } OrgFreedesktopAkonadiAgentSearchInterface *AgentSearchInstance::interface() const { return mInterface; } diff --git a/src/server/search/agentsearchinstance.h b/src/server/search/agentsearchinstance.h index d7e13c4e6..3e16ae39c 100644 --- a/src/server/search/agentsearchinstance.h +++ b/src/server/search/agentsearchinstance.h @@ -1,59 +1,58 @@ /* Copyright (c) 2013 Daniel Vrátil This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef AKONADI_AGENTSEARCHINSTANCE_H #define AKONADI_AGENTSEARCHINSTANCE_H #include #include +#include + class QDBusServiceWatcher; class OrgFreedesktopAkonadiAgentSearchInterface; namespace Akonadi { namespace Server { class AgentSearchInstance : public QObject { Q_OBJECT public: explicit AgentSearchInstance(const QString &id); ~AgentSearchInstance() override; bool init(); void search(const QByteArray &searchId, const QString &query, qlonglong collectionId); OrgFreedesktopAkonadiAgentSearchInterface *interface() const; -private Q_SLOTS: - void serviceOwnerChanged(const QString &service, const QString &oldName, const QString &newName); - private: QString mId; OrgFreedesktopAkonadiAgentSearchInterface *mInterface; - QDBusServiceWatcher *mServiceWatcher; + std::unique_ptr mServiceWatcher; }; } // namespace Server } // namespace Akonadi #endif // AKONADI_AGENTSEARCHINSTANCE_H diff --git a/src/server/storage/itemretrievalmanager.cpp b/src/server/storage/itemretrievalmanager.cpp index 542a81ab3..98bd10a15 100644 --- a/src/server/storage/itemretrievalmanager.cpp +++ b/src/server/storage/itemretrievalmanager.cpp @@ -1,222 +1,217 @@ /* 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 -#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; } ItemRetrievalManager::~ItemRetrievalManager() { quitThread(); sInstance = nullptr; } void ItemRetrievalManager::init() { AkThread::init(); QDBusConnection conn = DBusConnectionPool::threadConnection(); - connect(conn.interface(), &QDBusConnectionInterface::serviceOwnerChanged, - this, &ItemRetrievalManager::serviceOwnerChanged); + mDBusWatcher = std::make_unique( + QStringLiteral("org.freedesktop.Akonadi.Resource*"), conn, + QDBusServiceWatcher::WatchForUnregistration); + connect(mDBusWatcher.get(), &QDBusServiceWatcher::serviceUnregistered, + this, [this](const QString &serviceName) { + const auto service = DBus::parseAgentServiceName(serviceName); + if (service.has_value() && service->agentType == DBus::Resource) { + qCInfo(AKONADISERVER_LOG) << "ItemRetrievalManager has lost connection to resource" << serviceName << ", discarding cached interface."; + mResourceInterfaces.erase(service->identifier); + } + }); 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; - } - 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.erase(service->identifier); -} - // called within the retrieval thread org::freedesktop::Akonadi::Resource *ItemRetrievalManager::resourceInterface(const QString &id) { if (id.isEmpty()) { return nullptr; } auto ifaceIt = mResourceInterfaces.find(id); if (ifaceIt != mResourceInterfaces.cend() && ifaceIt->second->isValid()) { return ifaceIt->second.get(); } auto iface = std::make_unique( DBus::agentServiceName(id, DBus::Resource), QStringLiteral("/"), DBusConnectionPool::threadConnection()); if (!iface->isValid()) { qCCritical(AKONADISERVER_LOG, "Cannot connect to agent instance with identifier '%s', error message: '%s'", qUtf8Printable(id), qUtf8Printable(iface ? iface->lastError().message() : QString())); 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 std::tie(ifaceIt, std::ignore) = mResourceInterfaces.emplace(id, std::move(iface)); return ifaceIt->second.get(); } // called from any thread void ItemRetrievalManager::requestItemDelivery(ItemRetrievalRequest *req) { QWriteLocker locker(&mLock); 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); locker.unlock(); Q_EMIT requestAdded(); } // called within the retrieval thread void ItemRetrievalManager::processRequest() { QVector > newJobs; QWriteLocker locker(&mLock); // 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(); locker.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; } QWriteLocker locker(&mLock); 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; } } locker.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) { if (auto *interface = resourceInterface(resource)) { interface->synchronizeCollection(colId); } } void ItemRetrievalManager::triggerCollectionTreeSync(const QString &resource) { if (auto *interface = resourceInterface(resource)) { interface->synchronizeCollectionTree(); } } diff --git a/src/server/storage/itemretrievalmanager.h b/src/server/storage/itemretrievalmanager.h index 88384da9e..a39c33318 100644 --- a/src/server/storage/itemretrievalmanager.h +++ b/src/server/storage/itemretrievalmanager.h @@ -1,109 +1,110 @@ /* Copyright (c) 2009 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef AKONADI_ITEMRETRIEVALMANAGER_H #define AKONADI_ITEMRETRIEVALMANAGER_H #include "itemretriever.h" #include "akthread.h" #include #include #include #include #include #include class OrgFreedesktopAkonadiResourceInterface; +class QDBusServiceWatcher; namespace Akonadi { namespace Server { class Collection; class ItemRetrievalJob; class ItemRetrievalRequest; class AbstractItemRetrievalJob; class AbstractItemRetrievalJobFactory { public: explicit AbstractItemRetrievalJobFactory() {} virtual ~AbstractItemRetrievalJobFactory() {} virtual AbstractItemRetrievalJob *retrievalJob(ItemRetrievalRequest *request, QObject *parent) = 0; }; /** Manages and processes item retrieval requests. */ class ItemRetrievalManager : public AkThread { Q_OBJECT public: explicit ItemRetrievalManager(QObject *parent = nullptr); explicit ItemRetrievalManager(std::unique_ptr factory, QObject *parent = nullptr); ~ItemRetrievalManager() override; /** * Added for convenience. ItemRetrievalManager takes ownership over the * pointer and deletes it when the request is processed. */ virtual void requestItemDelivery(ItemRetrievalRequest *request); static ItemRetrievalManager *instance(); Q_SIGNALS: void requestFinished(ItemRetrievalRequest *request); void requestAdded(); private: OrgFreedesktopAkonadiResourceInterface *resourceInterface(const QString &id); private Q_SLOTS: void init() override; - void serviceOwnerChanged(const QString &serviceName, const QString &oldOwner, const QString &newOwner); void processRequest(); void triggerCollectionSync(const QString &resource, qint64 colId); void triggerCollectionTreeSync(const QString &resource); void retrievalJobFinished(ItemRetrievalRequest *request, const QString &errorMsg); protected: static ItemRetrievalManager *sInstance; std::unique_ptr mJobFactory; /// Protects mPendingRequests and every Request object posted to it QReadWriteLock mLock; /// Used to let requesting threads wait until the request has been processed QWaitCondition mWaitCondition; /// Pending requests queues, one per resource QHash > mPendingRequests; /// Currently running jobs, one per resource QHash mCurrentJobs; // resource dbus interface cache std::unordered_map> mResourceInterfaces; + std::unique_ptr mDBusWatcher; }; } // namespace Server } // namespace Akonadi #endif