kde-playground/kdepimlibs/akonadi/specialcollectionshelperjobs.cpp
2015-04-14 21:49:29 +00:00

660 lines
21 KiB
C++

/*
Copyright (c) 2009 Constantin Berzan <exit3219@gmail.com>
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 "dbusconnectionpool.h"
#include "specialcollectionattribute_p.h"
#include "specialcollections.h"
#include "servermanager.h"
#include <akonadi/agentinstance.h>
#include <akonadi/agentinstancecreatejob.h>
#include <akonadi/agentmanager.h>
#include <akonadi/collectionfetchjob.h>
#include <akonadi/collectionfetchscope.h>
#include <akonadi/collectionmodifyjob.h>
#include <akonadi/entitydisplayattribute.h>
#include <akonadi/resourcesynchronizationjob.h>
#include <KDebug>
#include <KLocalizedString>
#include <kcoreconfigskeleton.h>
#include <QtDBus/QDBusConnectionInterface>
#include <QtDBus/QDBusInterface>
#include <QtDBus/QDBusServiceWatcher>
#include <QtCore/QMetaMethod>
#include <QtCore/QTime>
#include <QtCore/QTimer>
#define LOCK_WAIT_TIMEOUT_SECONDS 10
using namespace Akonadi;
// convenient methods to get/set the default resource id
static void setDefaultResourceId(KCoreConfigSkeleton *settings, const QString &value)
{
KConfigSkeletonItem *item = settings->findItem(QLatin1String("DefaultResourceId"));
Q_ASSERT(item);
item->setProperty(value);
}
static QString defaultResourceId(KCoreConfigSkeleton *settings)
{
const KConfigSkeletonItem *item = settings->findItem(QLatin1String("DefaultResourceId"));
Q_ASSERT(item);
return item->property().toString();
}
static QString dbusServiceName()
{
QString service = QString::fromLatin1("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).signature());
if (signature.startsWith(method)) {
m = mo->method(i);
}
}
if (!m.signature()) {
return QVariant::Invalid;
}
const QList<QByteArray> argTypes = m.parameterTypes();
if (argTypes.count() != 1) {
return QVariant::Invalid;
}
return QVariant::nameToType(argTypes.first());
}
// ===================== ResourceScanJob ============================
/**
@internal
*/
class Akonadi::ResourceScanJob::Private
{
public:
Private(KCoreConfigSkeleton *settings, ResourceScanJob *qq);
void fetchResult(KJob *job); // slot
ResourceScanJob *const q;
// Input:
QString mResourceId;
KCoreConfigSkeleton *mSettings;
// 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()) {
kWarning() << job->errorText();
return;
}
CollectionFetchJob *fetchJob = qobject_cast<CollectionFetchJob *>(job);
Q_ASSERT(fetchJob);
Q_ASSERT(!mRootCollection.isValid());
Q_ASSERT(mSpecialCollections.isEmpty());
foreach (const Collection &collection, fetchJob->collections()) {
if (collection.parentCollection() == Collection::root()) {
if (mRootCollection.isValid()) {
kWarning() << "Resource has more than one root collection. I don't know what to do.";
} else {
mRootCollection = collection;
}
}
if (collection.hasAttribute<SpecialCollectionAttribute>()) {
mSpecialCollections.append(collection);
}
}
kDebug() << "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<DefaultResourceJob *>(this)) {
kError() << "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);
connect(fetchJob, SIGNAL(result(KJob*)), this, SLOT(fetchResult(KJob*)));
}
// ===================== 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;
bool mResourceWasPreexisting;
int mPendingModifyJobs;
QString mDefaultResourceType;
QVariantMap mDefaultResourceOptions;
QList<QByteArray> mKnownTypes;
QMap<QByteArray, QString> mNameForTypeMap;
QMap<QByteArray, QString> mIconForTypeMap;
};
DefaultResourceJobPrivate::DefaultResourceJobPrivate(KCoreConfigSkeleton *settings, DefaultResourceJob *qq)
: q(qq)
, mSettings(settings)
, mResourceWasPreexisting(true /* for safety, so as not to accidentally delete data */)
, mPendingModifyJobs(0)
{
}
void DefaultResourceJobPrivate::tryFetchResource()
{
// Get the resourceId from config. Another instance might have changed it in the meantime.
mSettings->readConfig();
const QString resourceId = defaultResourceId(mSettings);
kDebug() << "Read defaultResourceId" << resourceId << "from config.";
const AgentInstance resource = AgentManager::self()->instance(resourceId);
if (resource.isValid()) {
// The resource exists; scan it.
mResourceWasPreexisting = true;
kDebug() << "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, SIGNAL(result(KJob*)), q, SLOT(collectionFetchResult(KJob*)));
} 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();
foreach (const AgentInstance &resource, resources) {
if (resource.type().identifier() == mDefaultResourceType) {
if (resource.name() == mDefaultResourceOptions.value(QLatin1String("Name")).toString()) {
// found a matching one...
setDefaultResourceId(mSettings, resource.identifier());
mSettings->writeConfig();
mResourceWasPreexisting = true;
kDebug() << "Found resource" << resource.identifier();
q->setResourceId(resource.identifier());
q->ResourceScanJob::doStart();
return;
}
}
}
// Create the resource.
mResourceWasPreexisting = false;
kDebug() << "Creating maildir resource.";
const AgentType type = AgentManager::self()->type(mDefaultResourceType);
AgentInstanceCreateJob *job = new AgentInstanceCreateJob(type, q);
QObject::connect(job, SIGNAL(result(KJob*)), q, SLOT(resourceCreateResult(KJob*)));
job->start(); // non-Akonadi::Job
}
}
void DefaultResourceJobPrivate::resourceCreateResult(KJob *job)
{
if (job->error()) {
kWarning() << 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<AgentInstanceCreateJob *>(job);
Q_ASSERT(createJob);
agent = createJob->instance();
setDefaultResourceId(mSettings, agent.identifier());
kDebug() << "Created maildir resource with id" << defaultResourceId(mSettings);
}
const QString defaultId = defaultResourceId(mSettings);
// Configure the resource.
{
agent.setName(mDefaultResourceOptions.value(QLatin1String("Name")).toString());
QDBusInterface conf(QString::fromLatin1("org.freedesktop.Akonadi.Resource.") + defaultId,
QString::fromLatin1("/Settings"), QString());
if (!conf.isValid()) {
q->setError(-1);
q->setErrorText(i18n("Invalid resource identifier '%1'", defaultId));
q->emitResult();
return;
}
QMapIterator<QString, QVariant> it(mDefaultResourceOptions);
while (it.hasNext()) {
it.next();
if (it.key() == QLatin1String("Name")) {
continue;
}
const QString methodName = QString::fromLatin1("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<void> 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(QLatin1String("writeConfig"));
agent.reconfigure();
}
// Sync the resource.
{
ResourceSynchronizationJob *syncJob = new ResourceSynchronizationJob(agent, q);
QObject::connect(syncJob, SIGNAL(result(KJob*)), q, SLOT(resourceSyncResult(KJob*)));
syncJob->start(); // non-Akonadi
}
}
void DefaultResourceJobPrivate::resourceSyncResult(KJob *job)
{
if (job->error()) {
kWarning() << job->errorText();
//fail( i18n( "ResourceSynchronizationJob failed (%1).", job->errorString() ) );
return;
}
// Fetch the collections of the resource.
kDebug() << "Fetching maildir collections.";
CollectionFetchJob *fetchJob = new CollectionFetchJob(Collection::root(), CollectionFetchJob::Recursive, q);
fetchJob->fetchScope().setResource(defaultResourceId(mSettings));
QObject::connect(fetchJob, SIGNAL(result(KJob*)), q, SLOT(collectionFetchResult(KJob*)));
}
void DefaultResourceJobPrivate::collectionFetchResult(KJob *job)
{
if (job->error()) {
kWarning() << job->errorText();
//fail( i18n( "Failed to fetch the root maildir collection (%1).", job->errorString() ) );
return;
}
CollectionFetchJob *fetchJob = qobject_cast<CollectionFetchJob *>(job);
Q_ASSERT(fetchJob);
const Collection::List collections = fetchJob->collections();
kDebug() << "Fetched" << collections.count() << "collections.";
// Find the root maildir collection.
Collection::List toRecover;
Collection resourceCollection;
foreach (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.
foreach (const Collection &collection, collections) {
if (collection.parentCollection() == resourceCollection) {
toRecover.append(collection);
}
}
QHash<QString, QByteArray> typeForName;
foreach (const QByteArray &type, 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);
foreach (Collection collection, toRecover) { // krazy:exclude=foreach
if (collection.hasAttribute<SpecialCollectionAttribute>()) {
continue;
}
// Find the type for the collection.
const QString name = collection.displayName();
const QByteArray type = typeForName.value(name);
if (!type.isEmpty()) {
kDebug() << "Recovering collection" << name;
setCollectionAttributes(collection, type, mNameForTypeMap, mIconForTypeMap);
CollectionModifyJob *modifyJob = new CollectionModifyJob(collection, q);
QObject::connect(modifyJob, SIGNAL(result(KJob*)), q, SLOT(collectionModifyResult(KJob*)));
mPendingModifyJobs++;
} else {
kDebug() << "Searching for names: " << typeForName.keys();
kDebug() << "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()) {
kWarning() << job->errorText();
//fail( i18n( "Failed to modify the root maildir collection (%1).", job->errorString() ) );
return;
}
Q_ASSERT(mPendingModifyJobs > 0);
mPendingModifyJobs--;
kDebug() << "pendingModifyJobs now" << mPendingModifyJobs;
if (mPendingModifyJobs == 0) {
// Write the updated config.
kDebug() << "Writing defaultResourceId" << defaultResourceId(mSettings) << "to config.";
mSettings->writeConfig();
// 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<QByteArray> &types)
{
d->mKnownTypes = types;
}
void DefaultResourceJob::setNameForTypeMap(const QMap<QByteArray, QString> &map)
{
d->mNameForTypeMap = map;
}
void DefaultResourceJob::setIconForTypeMap(const QMap<QByteArray, QString> &map)
{
d->mIconForTypeMap = map;
}
void DefaultResourceJob::doStart()
{
d->tryFetchResource();
}
void DefaultResourceJob::slotResult(KJob *job)
{
if (job->error()) {
kWarning() << 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));
kDebug() << "Removing resource" << resource.identifier();
AgentManager::self()->removeInstance(resource);
}
}
Job::slotResult(job);
}
// ===================== GetLockJob ============================
class 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;
};
GetLockJob::Private::Private(GetLockJob *qq)
: q(qq)
, mSafetyTimer(0)
{
}
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 = DBusConnectionPool::threadConnection();
const bool alreadyLocked = bus.interface()->isServiceRegistered(dbusServiceName());
const bool gotIt = bus.registerService(dbusServiceName());
if (gotIt && !alreadyLocked) {
//kDebug() << "Got lock immediately.";
q->emitResult();
} else {
QDBusServiceWatcher *watcher = new QDBusServiceWatcher(dbusServiceName(), DBusConnectionPool::threadConnection(),
QDBusServiceWatcher::WatchForOwnerChange, q);
//kDebug() << "Waiting for lock.";
connect(watcher, SIGNAL(serviceOwnerChanged(QString,QString,QString)),
q, SLOT(serviceOwnerChanged(QString,QString,QString)));
mSafetyTimer = new QTimer(q);
mSafetyTimer->setSingleShot(true);
mSafetyTimer->setInterval(LOCK_WAIT_TIMEOUT_SECONDS * 1000);
mSafetyTimer->start();
connect(mSafetyTimer, SIGNAL(timeout()), q, SLOT(timeout()));
}
}
void GetLockJob::Private::serviceOwnerChanged(const QString &name, const QString &oldOwner, const QString &newOwner)
{
if (newOwner.isEmpty()) {
const bool gotIt = DBusConnectionPool::threadConnection().registerService(dbusServiceName());
if (gotIt) {
mSafetyTimer->stop();
q->emitResult();
}
}
}
void GetLockJob::Private::timeout()
{
kWarning() << "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, SLOT(doStart()));
}
void Akonadi::setCollectionAttributes(Akonadi::Collection &collection, const QByteArray &type,
const QMap<QByteArray, QString> &nameForType,
const QMap<QByteArray, QString> &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 DBusConnectionPool::threadConnection().unregisterService(dbusServiceName());
}
#include "moc_specialcollectionshelperjobs_p.cpp"