File indexing completed on 2024-11-10 04:40:31

0001 /*
0002     SPDX-FileCopyrightText: 2009 Constantin Berzan <exit3219@gmail.com>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "specialcollectionshelperjobs_p.h"
0008 
0009 #include "servermanager.h"
0010 #include "specialcollectionattribute.h"
0011 #include <QDBusConnection>
0012 
0013 #include "agentinstance.h"
0014 #include "agentinstancecreatejob.h"
0015 #include "agentmanager.h"
0016 #include "collectionfetchjob.h"
0017 #include "collectionfetchscope.h"
0018 #include "collectionmodifyjob.h"
0019 #include "entitydisplayattribute.h"
0020 #include "resourcesynchronizationjob.h"
0021 
0022 #include "akonadicore_debug.h"
0023 
0024 #include <KCoreConfigSkeleton>
0025 #include <KLocalizedString>
0026 
0027 #include <QDBusConnectionInterface>
0028 #include <QDBusInterface>
0029 #include <QDBusServiceWatcher>
0030 #include <QMetaMethod>
0031 #include <QTime>
0032 #include <QTimer>
0033 
0034 #define LOCK_WAIT_TIMEOUT_SECONDS 30
0035 
0036 using namespace Akonadi;
0037 
0038 // convenient methods to get/set the default resource id
0039 static void setDefaultResourceId(KCoreConfigSkeleton *settings, const QString &value)
0040 {
0041     KConfigSkeletonItem *item = settings->findItem(QStringLiteral("DefaultResourceId"));
0042     Q_ASSERT(item);
0043     item->setProperty(value);
0044 }
0045 
0046 static QString defaultResourceId(KCoreConfigSkeleton *settings)
0047 {
0048     const KConfigSkeletonItem *item = settings->findItem(QStringLiteral("DefaultResourceId"));
0049     Q_ASSERT(item);
0050     return item->property().toString();
0051 }
0052 
0053 static QString dbusServiceName()
0054 {
0055     const QString service = QStringLiteral("org.kde.pim.SpecialCollections");
0056     if (ServerManager::hasInstanceIdentifier()) {
0057         return service + ServerManager::instanceIdentifier();
0058     }
0059     return service;
0060 }
0061 
0062 static QMetaType::Type argumentType(const QMetaObject *metaObject, const QString &method)
0063 {
0064     QMetaMethod metaMethod;
0065     for (int i = 0; i < metaObject->methodCount(); ++i) {
0066         const QString signature = QString::fromLatin1(metaObject->method(i).methodSignature());
0067         if (signature.startsWith(method)) {
0068             metaMethod = metaObject->method(i);
0069         }
0070     }
0071 
0072     if (metaMethod.methodSignature().isEmpty()) {
0073         return QMetaType::UnknownType;
0074     }
0075 
0076     const QList<QByteArray> argTypes = metaMethod.parameterTypes();
0077     if (argTypes.count() != 1) {
0078         return QMetaType::UnknownType;
0079     }
0080 
0081     return static_cast<QMetaType::Type>(QMetaType::fromName(argTypes.first().constData()).id());
0082 }
0083 
0084 // ===================== ResourceScanJob ============================
0085 
0086 /**
0087   @internal
0088 */
0089 class Akonadi::ResourceScanJobPrivate
0090 {
0091 public:
0092     ResourceScanJobPrivate(KCoreConfigSkeleton *settings, ResourceScanJob *qq);
0093 
0094     void fetchResult(KJob *job); // slot
0095 
0096     ResourceScanJob *const q;
0097 
0098     // Input:
0099     QString mResourceId;
0100     KCoreConfigSkeleton *mSettings = nullptr;
0101 
0102     // Output:
0103     Collection mRootCollection;
0104     Collection::List mSpecialCollections;
0105 };
0106 
0107 ResourceScanJobPrivate::ResourceScanJobPrivate(KCoreConfigSkeleton *settings, ResourceScanJob *qq)
0108     : q(qq)
0109     , mSettings(settings)
0110 {
0111 }
0112 
0113 void ResourceScanJobPrivate::fetchResult(KJob *job)
0114 {
0115     if (job->error()) {
0116         qCWarning(AKONADICORE_LOG) << job->errorText();
0117         return;
0118     }
0119 
0120     auto fetchJob = qobject_cast<CollectionFetchJob *>(job);
0121     Q_ASSERT(fetchJob);
0122 
0123     Q_ASSERT(!mRootCollection.isValid());
0124     Q_ASSERT(mSpecialCollections.isEmpty());
0125     const Akonadi::Collection::List lstCols = fetchJob->collections();
0126     for (const Collection &collection : lstCols) {
0127         if (collection.parentCollection() == Collection::root()) {
0128             if (mRootCollection.isValid()) {
0129                 qCWarning(AKONADICORE_LOG) << "Resource has more than one root collection. I don't know what to do.";
0130             } else {
0131                 mRootCollection = collection;
0132             }
0133         }
0134 
0135         if (collection.hasAttribute<SpecialCollectionAttribute>()) {
0136             mSpecialCollections.append(collection);
0137         }
0138     }
0139 
0140     qCDebug(AKONADICORE_LOG) << "Fetched root collection" << mRootCollection.id() << "and" << mSpecialCollections.count() << "local folders"
0141                              << "(total" << fetchJob->collections().count() << "collections).";
0142 
0143     if (!mRootCollection.isValid()) {
0144         q->setError(ResourceScanJob::Unknown);
0145         q->setErrorText(i18n("Could not fetch root collection of resource %1.", mResourceId));
0146         q->emitResult();
0147         return;
0148     }
0149 
0150     // We are done!
0151     q->emitResult();
0152 }
0153 
0154 ResourceScanJob::ResourceScanJob(const QString &resourceId, KCoreConfigSkeleton *settings, QObject *parent)
0155     : Job(parent)
0156     , d(new ResourceScanJobPrivate(settings, this))
0157 {
0158     setResourceId(resourceId);
0159 }
0160 
0161 ResourceScanJob::~ResourceScanJob() = default;
0162 
0163 QString ResourceScanJob::resourceId() const
0164 {
0165     return d->mResourceId;
0166 }
0167 
0168 void ResourceScanJob::setResourceId(const QString &resourceId)
0169 {
0170     d->mResourceId = resourceId;
0171 }
0172 
0173 Akonadi::Collection ResourceScanJob::rootResourceCollection() const
0174 {
0175     return d->mRootCollection;
0176 }
0177 
0178 Akonadi::Collection::List ResourceScanJob::specialCollections() const
0179 {
0180     return d->mSpecialCollections;
0181 }
0182 
0183 void ResourceScanJob::doStart()
0184 {
0185     if (d->mResourceId.isEmpty()) {
0186         if (!qobject_cast<DefaultResourceJob *>(this)) {
0187             qCCritical(AKONADICORE_LOG) << "No resource ID given.";
0188             setError(Job::Unknown);
0189             setErrorText(i18n("No resource ID given."));
0190         }
0191         emitResult();
0192         return;
0193     }
0194 
0195     auto fetchJob = new CollectionFetchJob(Collection::root(), CollectionFetchJob::Recursive, this);
0196     fetchJob->fetchScope().setResource(d->mResourceId);
0197     fetchJob->fetchScope().setIncludeStatistics(true);
0198     fetchJob->fetchScope().setListFilter(CollectionFetchScope::Display);
0199     connect(fetchJob, &CollectionFetchJob::result, this, [this](KJob *job) {
0200         d->fetchResult(job);
0201     });
0202 }
0203 
0204 // ===================== DefaultResourceJob ============================
0205 
0206 /**
0207   @internal
0208 */
0209 class Akonadi::DefaultResourceJobPrivate
0210 {
0211 public:
0212     DefaultResourceJobPrivate(KCoreConfigSkeleton *settings, DefaultResourceJob *qq);
0213 
0214     void tryFetchResource();
0215     void resourceCreateResult(KJob *job); // slot
0216     void resourceSyncResult(KJob *job); // slot
0217     void collectionFetchResult(KJob *job); // slot
0218     void collectionModifyResult(KJob *job); // slot
0219 
0220     DefaultResourceJob *const q;
0221     KCoreConfigSkeleton *mSettings = nullptr;
0222     QVariantMap mDefaultResourceOptions;
0223     QList<QByteArray> mKnownTypes;
0224     QMap<QByteArray, QString> mNameForTypeMap;
0225     QMap<QByteArray, QString> mIconForTypeMap;
0226     QString mDefaultResourceType;
0227     int mPendingModifyJobs = 0;
0228     bool mResourceWasPreexisting = true;
0229 };
0230 
0231 DefaultResourceJobPrivate::DefaultResourceJobPrivate(KCoreConfigSkeleton *settings, DefaultResourceJob *qq)
0232     : q(qq)
0233     , mSettings(settings)
0234     , mPendingModifyJobs(0)
0235     , mResourceWasPreexisting(true /* for safety, so as not to accidentally delete data */)
0236 {
0237 }
0238 
0239 void DefaultResourceJobPrivate::tryFetchResource()
0240 {
0241     // Get the resourceId from config. Another instance might have changed it in the meantime.
0242     mSettings->load();
0243 
0244     const QString resourceId = defaultResourceId(mSettings);
0245 
0246     qCDebug(AKONADICORE_LOG) << "Read defaultResourceId" << resourceId << "from config.";
0247 
0248     const AgentInstance resource = AgentManager::self()->instance(resourceId);
0249     if (resource.isValid()) {
0250         // The resource exists; scan it.
0251         mResourceWasPreexisting = true;
0252         qCDebug(AKONADICORE_LOG) << "Found resource" << resourceId;
0253         q->setResourceId(resourceId);
0254 
0255         auto fetchJob = new CollectionFetchJob(Collection::root(), CollectionFetchJob::Recursive, q);
0256         fetchJob->fetchScope().setResource(resourceId);
0257         fetchJob->fetchScope().setIncludeStatistics(true);
0258         q->connect(fetchJob, &CollectionFetchJob::result, q, [this](KJob *job) {
0259             collectionFetchResult(job);
0260         });
0261     } else {
0262         // Try harder: maybe the default resource has been removed and another one added
0263         //             without updating the config file, in this case search for a resource
0264         //             of the same type and the default name
0265         const AgentInstance::List resources = AgentManager::self()->instances();
0266         for (const AgentInstance &resourceInstance : resources) {
0267             if (resourceInstance.type().identifier() == mDefaultResourceType) {
0268                 if (resourceInstance.name() == mDefaultResourceOptions.value(QStringLiteral("Name")).toString()) {
0269                     // found a matching one...
0270                     setDefaultResourceId(mSettings, resourceInstance.identifier());
0271                     mSettings->save();
0272                     mResourceWasPreexisting = true;
0273                     qCDebug(AKONADICORE_LOG) << "Found resource" << resourceInstance.identifier();
0274                     q->setResourceId(resourceInstance.identifier());
0275                     q->ResourceScanJob::doStart();
0276                     return;
0277                 }
0278             }
0279         }
0280 
0281         // Create the resource.
0282         mResourceWasPreexisting = false;
0283         qCDebug(AKONADICORE_LOG) << "Creating maildir resource.";
0284         const AgentType type = AgentManager::self()->type(mDefaultResourceType);
0285         auto job = new AgentInstanceCreateJob(type, q);
0286         QObject::connect(job, &AgentInstanceCreateJob::result, q, [this](KJob *job) {
0287             resourceCreateResult(job);
0288         });
0289         job->start(); // non-Akonadi::Job
0290     }
0291 }
0292 
0293 void DefaultResourceJobPrivate::resourceCreateResult(KJob *job)
0294 {
0295     if (job->error()) {
0296         qCWarning(AKONADICORE_LOG) << job->errorText();
0297         // fail( i18n( "Failed to create the default resource (%1).", job->errorString() ) );
0298         q->setError(job->error());
0299         q->setErrorText(job->errorText());
0300         q->emitResult();
0301         return;
0302     }
0303 
0304     AgentInstance agent;
0305 
0306     // Get the resource instance.
0307     {
0308         auto createJob = qobject_cast<AgentInstanceCreateJob *>(job);
0309         Q_ASSERT(createJob);
0310         agent = createJob->instance();
0311         setDefaultResourceId(mSettings, agent.identifier());
0312         qCDebug(AKONADICORE_LOG) << "Created maildir resource with id" << defaultResourceId(mSettings);
0313     }
0314 
0315     const QString defaultId = defaultResourceId(mSettings);
0316 
0317     // Configure the resource.
0318     {
0319         agent.setName(mDefaultResourceOptions.value(QStringLiteral("Name")).toString());
0320 
0321         const auto service = ServerManager::agentServiceName(ServerManager::Resource, defaultId);
0322         QDBusInterface conf(service, QStringLiteral("/Settings"), QString());
0323 
0324         if (!conf.isValid()) {
0325             q->setError(-1);
0326             q->setErrorText(i18n("Invalid resource identifier '%1'", defaultId));
0327             q->emitResult();
0328             return;
0329         }
0330 
0331         QMap<QString, QVariant>::const_iterator it = mDefaultResourceOptions.cbegin();
0332         const QMap<QString, QVariant>::const_iterator itEnd = mDefaultResourceOptions.cend();
0333         for (; it != itEnd; ++it) {
0334             if (it.key() == QLatin1StringView("Name")) {
0335                 continue;
0336             }
0337 
0338             const QString methodName = QStringLiteral("set%1").arg(it.key());
0339             const QMetaType::Type argType = argumentType(conf.metaObject(), methodName);
0340             if (argType == QMetaType::UnknownType) {
0341                 q->setError(Job::Unknown);
0342                 q->setErrorText(i18n("Failed to configure default resource via D-Bus."));
0343                 q->emitResult();
0344                 return;
0345             }
0346 
0347             QDBusReply<void> reply = conf.call(methodName, it.value());
0348             if (!reply.isValid()) {
0349                 q->setError(Job::Unknown);
0350                 q->setErrorText(i18n("Failed to configure default resource via D-Bus."));
0351                 q->emitResult();
0352                 return;
0353             }
0354         }
0355 
0356         conf.call(QStringLiteral("save"));
0357 
0358         agent.reconfigure();
0359     }
0360 
0361     // Sync the resource.
0362     {
0363         auto syncJob = new ResourceSynchronizationJob(agent, q);
0364         QObject::connect(syncJob, &ResourceSynchronizationJob::result, q, [this](KJob *job) {
0365             resourceSyncResult(job);
0366         });
0367         syncJob->start(); // non-Akonadi
0368     }
0369 }
0370 
0371 void DefaultResourceJobPrivate::resourceSyncResult(KJob *job)
0372 {
0373     if (job->error()) {
0374         qCWarning(AKONADICORE_LOG) << job->errorText();
0375         // fail( i18n( "ResourceSynchronizationJob failed (%1).", job->errorString() ) );
0376         return;
0377     }
0378 
0379     // Fetch the collections of the resource.
0380     qCDebug(AKONADICORE_LOG) << "Fetching maildir collections.";
0381     auto fetchJob = new CollectionFetchJob(Collection::root(), CollectionFetchJob::Recursive, q);
0382     fetchJob->fetchScope().setResource(defaultResourceId(mSettings));
0383     QObject::connect(fetchJob, &CollectionFetchJob::result, q, [this](KJob *job) {
0384         collectionFetchResult(job);
0385     });
0386 }
0387 
0388 void DefaultResourceJobPrivate::collectionFetchResult(KJob *job)
0389 {
0390     if (job->error()) {
0391         qCWarning(AKONADICORE_LOG) << job->errorText();
0392         // fail( i18n( "Failed to fetch the root maildir collection (%1).", job->errorString() ) );
0393         return;
0394     }
0395 
0396     auto fetchJob = qobject_cast<CollectionFetchJob *>(job);
0397     Q_ASSERT(fetchJob);
0398 
0399     const Collection::List collections = fetchJob->collections();
0400     qCDebug(AKONADICORE_LOG) << "Fetched" << collections.count() << "collections.";
0401 
0402     // Find the root maildir collection.
0403     Collection::List toRecover;
0404     Collection resourceCollection;
0405     for (const Collection &collection : collections) {
0406         if (collection.parentCollection() == Collection::root()) {
0407             resourceCollection = collection;
0408             toRecover.append(collection);
0409             break;
0410         }
0411     }
0412 
0413     if (!resourceCollection.isValid()) {
0414         q->setError(Job::Unknown);
0415         q->setErrorText(i18n("Failed to fetch the resource collection."));
0416         q->emitResult();
0417         return;
0418     }
0419 
0420     // Find all children of the resource collection.
0421     for (const Collection &collection : std::as_const(collections)) {
0422         if (collection.parentCollection() == resourceCollection) {
0423             toRecover.append(collection);
0424         }
0425     }
0426 
0427     QHash<QString, QByteArray> typeForName;
0428     for (const QByteArray &type : std::as_const(mKnownTypes)) {
0429         const QString displayName = mNameForTypeMap.value(type);
0430         typeForName[displayName] = type;
0431     }
0432 
0433     // These collections have been created by the maildir resource, when it
0434     // found the folders on disk. So give them the necessary attributes now.
0435     Q_ASSERT(mPendingModifyJobs == 0);
0436     for (Collection collection : std::as_const(toRecover)) {
0437         if (collection.hasAttribute<SpecialCollectionAttribute>()) {
0438             continue;
0439         }
0440 
0441         // Find the type for the collection.
0442         const QString name = collection.displayName();
0443         const QByteArray type = typeForName.value(name);
0444 
0445         if (!type.isEmpty()) {
0446             qCDebug(AKONADICORE_LOG) << "Recovering collection" << name;
0447             setCollectionAttributes(collection, type, mNameForTypeMap, mIconForTypeMap);
0448 
0449             auto modifyJob = new CollectionModifyJob(collection, q);
0450             QObject::connect(modifyJob, &CollectionModifyJob::result, q, [this](KJob *job) {
0451                 collectionModifyResult(job);
0452             });
0453             mPendingModifyJobs++;
0454         } else {
0455             qCDebug(AKONADICORE_LOG) << "Searching for names: " << typeForName.keys();
0456             qCDebug(AKONADICORE_LOG) << "Unknown collection name" << name << "-- not recovering.";
0457         }
0458     }
0459 
0460     if (mPendingModifyJobs == 0) {
0461         // Scan the resource.
0462         q->setResourceId(defaultResourceId(mSettings));
0463         q->ResourceScanJob::doStart();
0464     }
0465 }
0466 
0467 void DefaultResourceJobPrivate::collectionModifyResult(KJob *job)
0468 {
0469     if (job->error()) {
0470         qCWarning(AKONADICORE_LOG) << job->errorText();
0471         // fail( i18n( "Failed to modify the root maildir collection (%1).", job->errorString() ) );
0472         return;
0473     }
0474 
0475     Q_ASSERT(mPendingModifyJobs > 0);
0476     mPendingModifyJobs--;
0477     qCDebug(AKONADICORE_LOG) << "pendingModifyJobs now" << mPendingModifyJobs;
0478     if (mPendingModifyJobs == 0) {
0479         // Write the updated config.
0480         qCDebug(AKONADICORE_LOG) << "Writing defaultResourceId" << defaultResourceId(mSettings) << "to config.";
0481         mSettings->save();
0482 
0483         // Scan the resource.
0484         q->setResourceId(defaultResourceId(mSettings));
0485         q->ResourceScanJob::doStart();
0486     }
0487 }
0488 
0489 DefaultResourceJob::DefaultResourceJob(KCoreConfigSkeleton *settings, QObject *parent)
0490     : ResourceScanJob(QString(), settings, parent)
0491     , d(new DefaultResourceJobPrivate(settings, this))
0492 {
0493 }
0494 
0495 DefaultResourceJob::~DefaultResourceJob() = default;
0496 
0497 void DefaultResourceJob::setDefaultResourceType(const QString &type)
0498 {
0499     d->mDefaultResourceType = type;
0500 }
0501 
0502 void DefaultResourceJob::setDefaultResourceOptions(const QVariantMap &options)
0503 {
0504     d->mDefaultResourceOptions = options;
0505 }
0506 
0507 void DefaultResourceJob::setTypes(const QList<QByteArray> &types)
0508 {
0509     d->mKnownTypes = types;
0510 }
0511 
0512 void DefaultResourceJob::setNameForTypeMap(const QMap<QByteArray, QString> &map)
0513 {
0514     d->mNameForTypeMap = map;
0515 }
0516 
0517 void DefaultResourceJob::setIconForTypeMap(const QMap<QByteArray, QString> &map)
0518 {
0519     d->mIconForTypeMap = map;
0520 }
0521 
0522 void DefaultResourceJob::doStart()
0523 {
0524     d->tryFetchResource();
0525 }
0526 
0527 void DefaultResourceJob::slotResult(KJob *job)
0528 {
0529     if (job->error()) {
0530         qCWarning(AKONADICORE_LOG) << job->errorText();
0531         // Do some cleanup.
0532         if (!d->mResourceWasPreexisting) {
0533             // We only removed the resource instance if we have created it.
0534             // Otherwise we might lose the user's data.
0535             const AgentInstance resource = AgentManager::self()->instance(defaultResourceId(d->mSettings));
0536             qCDebug(AKONADICORE_LOG) << "Removing resource" << resource.identifier();
0537             AgentManager::self()->removeInstance(resource);
0538         }
0539     }
0540 
0541     Job::slotResult(job);
0542 }
0543 
0544 // ===================== GetLockJob ============================
0545 
0546 class Akonadi::GetLockJobPrivate
0547 {
0548 public:
0549     explicit GetLockJobPrivate(GetLockJob *qq);
0550 
0551     void doStart(); // slot
0552     void timeout(); // slot
0553 
0554     GetLockJob *const q;
0555     QTimer *mSafetyTimer = nullptr;
0556 };
0557 
0558 GetLockJobPrivate::GetLockJobPrivate(GetLockJob *qq)
0559     : q(qq)
0560     , mSafetyTimer(nullptr)
0561 {
0562 }
0563 
0564 void GetLockJobPrivate::doStart()
0565 {
0566     // Just doing registerService() and checking its return value is not sufficient,
0567     // since we may *already* own the name, and then registerService() returns true.
0568 
0569     QDBusConnection bus = QDBusConnection::sessionBus();
0570     const bool alreadyLocked = bus.interface()->isServiceRegistered(dbusServiceName());
0571     const bool gotIt = bus.registerService(dbusServiceName());
0572 
0573     if (gotIt && !alreadyLocked) {
0574         // qCDebug(AKONADICORE_LOG) << "Got lock immediately.";
0575         q->emitResult();
0576     } else {
0577         auto watcher = new QDBusServiceWatcher(dbusServiceName(), QDBusConnection::sessionBus(), QDBusServiceWatcher::WatchForUnregistration, q);
0578         QObject::connect(watcher, &QDBusServiceWatcher::serviceUnregistered, q, [this]() {
0579             if (QDBusConnection::sessionBus().registerService(dbusServiceName())) {
0580                 mSafetyTimer->stop();
0581                 q->emitResult();
0582             }
0583         });
0584 
0585         mSafetyTimer = new QTimer(q);
0586         mSafetyTimer->setSingleShot(true);
0587         mSafetyTimer->setInterval(LOCK_WAIT_TIMEOUT_SECONDS * 1000);
0588         mSafetyTimer->start();
0589         QObject::connect(mSafetyTimer, &QTimer::timeout, q, [this]() {
0590             timeout();
0591         });
0592     }
0593 }
0594 
0595 void GetLockJobPrivate::timeout()
0596 {
0597     qCWarning(AKONADICORE_LOG) << "Timeout trying to get lock. Check who has acquired the name" << dbusServiceName() << "on DBus, using qdbus or qdbusviewer.";
0598     q->setError(Job::Unknown);
0599     q->setErrorText(i18n("Timeout trying to get lock."));
0600     q->emitResult();
0601 }
0602 
0603 GetLockJob::GetLockJob(QObject *parent)
0604     : KJob(parent)
0605     , d(new GetLockJobPrivate(this))
0606 {
0607 }
0608 
0609 GetLockJob::~GetLockJob() = default;
0610 
0611 void GetLockJob::start()
0612 {
0613     QTimer::singleShot(0, this, [this]() {
0614         d->doStart();
0615     });
0616 }
0617 
0618 void Akonadi::setCollectionAttributes(Akonadi::Collection &collection,
0619                                       const QByteArray &type,
0620                                       const QMap<QByteArray, QString> &nameForType,
0621                                       const QMap<QByteArray, QString> &iconForType)
0622 {
0623     {
0624         auto attr = new EntityDisplayAttribute;
0625         attr->setIconName(iconForType.value(type));
0626         attr->setDisplayName(nameForType.value(type));
0627         collection.addAttribute(attr);
0628     }
0629 
0630     {
0631         auto attr = new SpecialCollectionAttribute;
0632         attr->setCollectionType(type);
0633         collection.addAttribute(attr);
0634     }
0635 }
0636 
0637 bool Akonadi::releaseLock()
0638 {
0639     return QDBusConnection::sessionBus().unregisterService(dbusServiceName());
0640 }
0641 
0642 #include "moc_specialcollectionshelperjobs_p.cpp"