00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020 #include "specialcollectionshelperjobs_p.h"
00021
00022 #include "dbusconnectionpool.h"
00023 #include "specialcollectionattribute_p.h"
00024 #include "specialcollections.h"
00025
00026 #include <akonadi/agentinstance.h>
00027 #include <akonadi/agentinstancecreatejob.h>
00028 #include <akonadi/agentmanager.h>
00029 #include <akonadi/collectionfetchjob.h>
00030 #include <akonadi/collectionfetchscope.h>
00031 #include <akonadi/collectionmodifyjob.h>
00032 #include <akonadi/entitydisplayattribute.h>
00033 #include <akonadi/resourcesynchronizationjob.h>
00034
00035 #include <KDebug>
00036 #include <KLocalizedString>
00037 #include <KStandardDirs>
00038 #include <kcoreconfigskeleton.h>
00039
00040 #include <QtDBus/QDBusConnectionInterface>
00041 #include <QtDBus/QDBusInterface>
00042 #include <QtDBus/QDBusServiceWatcher>
00043 #include <QtCore/QMetaMethod>
00044 #include <QtCore/QTime>
00045 #include <QtCore/QTimer>
00046
00047 #define DBUS_SERVICE_NAME QLatin1String( "org.kde.pim.SpecialCollections" )
00048 #define LOCK_WAIT_TIMEOUT_SECONDS 3
00049
00050 using namespace Akonadi;
00051
00052
00053 static void setDefaultResourceId( KCoreConfigSkeleton *settings, const QString &value )
00054 {
00055 KConfigSkeletonItem *item = settings->findItem( QLatin1String( "DefaultResourceId" ) );
00056 Q_ASSERT( item );
00057 item->setProperty( value );
00058 }
00059
00060 static QString defaultResourceId( KCoreConfigSkeleton *settings )
00061 {
00062 const KConfigSkeletonItem *item = settings->findItem( QLatin1String( "DefaultResourceId" ) );
00063 Q_ASSERT( item );
00064 return item->property().toString();
00065 }
00066
00067 static QVariant::Type argumentType( const QMetaObject *mo, const QString &method )
00068 {
00069 QMetaMethod m;
00070 for ( int i = 0; i < mo->methodCount(); ++i ) {
00071 const QString signature = QString::fromLatin1( mo->method( i ).signature() );
00072 if ( signature.startsWith( method ) )
00073 m = mo->method( i );
00074 }
00075
00076 if ( !m.signature() )
00077 return QVariant::Invalid;
00078
00079 const QList<QByteArray> argTypes = m.parameterTypes();
00080 if ( argTypes.count() != 1 )
00081 return QVariant::Invalid;
00082
00083 return QVariant::nameToType( argTypes.first() );
00084 }
00085
00086
00087
00091 class Akonadi::ResourceScanJob::Private
00092 {
00093 public:
00094 Private( KCoreConfigSkeleton *settings, ResourceScanJob *qq );
00095
00096 void fetchResult( KJob *job );
00097
00098 ResourceScanJob *const q;
00099
00100
00101 QString mResourceId;
00102 KCoreConfigSkeleton *mSettings;
00103
00104
00105 Collection mRootCollection;
00106 Collection::List mSpecialCollections;
00107 };
00108
00109 ResourceScanJob::Private::Private( KCoreConfigSkeleton *settings, ResourceScanJob *qq )
00110 : q( qq ), mSettings( settings )
00111 {
00112 }
00113
00114 void ResourceScanJob::Private::fetchResult( KJob *job )
00115 {
00116 if ( job->error() ) {
00117 kWarning() << job->errorText();
00118 return;
00119 }
00120
00121 CollectionFetchJob *fetchJob = qobject_cast<CollectionFetchJob *>( job );
00122 Q_ASSERT( fetchJob );
00123
00124 Q_ASSERT( !mRootCollection.isValid() );
00125 Q_ASSERT( mSpecialCollections.isEmpty() );
00126 foreach ( const Collection &collection, fetchJob->collections() ) {
00127 if ( collection.parentCollection() == Collection::root() ) {
00128 if ( mRootCollection.isValid() )
00129 kWarning() << "Resource has more than one root collection. I don't know what to do.";
00130 else
00131 mRootCollection = collection;
00132 }
00133
00134 if ( collection.hasAttribute<SpecialCollectionAttribute>() )
00135 mSpecialCollections.append( collection );
00136 }
00137
00138 kDebug() << "Fetched root collection" << mRootCollection.id()
00139 << "and" << mSpecialCollections.count() << "local folders"
00140 << "(total" << fetchJob->collections().count() << "collections).";
00141
00142 if ( !mRootCollection.isValid() ) {
00143 q->setError( Unknown );
00144 q->setErrorText( i18n( "Could not fetch root collection of resource %1.", mResourceId ) );
00145 q->emitResult();
00146 return;
00147 }
00148
00149
00150 q->emitResult();
00151 }
00152
00153
00154
00155 ResourceScanJob::ResourceScanJob( const QString &resourceId, KCoreConfigSkeleton *settings, QObject *parent )
00156 : Job( parent ),
00157 d( new Private( settings, this ) )
00158 {
00159 setResourceId( resourceId );
00160 }
00161
00162 ResourceScanJob::~ResourceScanJob()
00163 {
00164 delete d;
00165 }
00166
00167 QString ResourceScanJob::resourceId() const
00168 {
00169 return d->mResourceId;
00170 }
00171
00172 void ResourceScanJob::setResourceId( const QString &resourceId )
00173 {
00174 d->mResourceId = resourceId;
00175 }
00176
00177 Akonadi::Collection ResourceScanJob::rootResourceCollection() const
00178 {
00179 return d->mRootCollection;
00180 }
00181
00182 Akonadi::Collection::List ResourceScanJob::specialCollections() const
00183 {
00184 return d->mSpecialCollections;
00185 }
00186
00187 void ResourceScanJob::doStart()
00188 {
00189 if ( d->mResourceId.isEmpty() ) {
00190 kError() << "No resource ID given.";
00191 setError( Job::Unknown );
00192 setErrorText( i18n( "No resource ID given." ) );
00193 emitResult();
00194 return;
00195 }
00196
00197 CollectionFetchJob *fetchJob = new CollectionFetchJob( Collection::root(),
00198 CollectionFetchJob::Recursive, this );
00199 fetchJob->fetchScope().setResource( d->mResourceId );
00200 fetchJob->fetchScope().setIncludeStatistics( true );
00201 connect( fetchJob, SIGNAL( result( KJob* ) ), this, SLOT( fetchResult( KJob* ) ) );
00202 }
00203
00204
00205
00206
00210 class Akonadi::DefaultResourceJobPrivate
00211 {
00212 public:
00213 DefaultResourceJobPrivate( KCoreConfigSkeleton *settings, DefaultResourceJob *qq );
00214
00215 void tryFetchResource();
00216 void resourceCreateResult( KJob *job );
00217 void resourceSyncResult( KJob *job );
00218 void collectionFetchResult( KJob *job );
00219 void collectionModifyResult( KJob *job );
00220
00221 DefaultResourceJob *const q;
00222 KCoreConfigSkeleton *mSettings;
00223 bool mResourceWasPreexisting;
00224 int mPendingModifyJobs;
00225 QString mDefaultResourceType;
00226 QVariantMap mDefaultResourceOptions;
00227 QList<QByteArray> mKnownTypes;
00228 QMap<QByteArray, QString> mNameForTypeMap;
00229 QMap<QByteArray, QString> mIconForTypeMap;
00230 };
00231
00232 DefaultResourceJobPrivate::DefaultResourceJobPrivate( KCoreConfigSkeleton *settings, DefaultResourceJob *qq )
00233 : q( qq ),
00234 mSettings( settings ),
00235 mResourceWasPreexisting( true ),
00236 mPendingModifyJobs( 0 )
00237 {
00238 }
00239
00240 void DefaultResourceJobPrivate::tryFetchResource()
00241 {
00242
00243 mSettings->readConfig();
00244
00245 const QString resourceId = defaultResourceId( mSettings );
00246
00247 kDebug() << "Read defaultResourceId" << resourceId << "from config.";
00248
00249 const AgentInstance resource = AgentManager::self()->instance( resourceId );
00250 if ( resource.isValid() ) {
00251
00252 mResourceWasPreexisting = true;
00253 kDebug() << "Found resource" << resourceId;
00254 q->setResourceId( resourceId );
00255
00256 CollectionFetchJob *fetchJob = new CollectionFetchJob( Collection::root(), CollectionFetchJob::Recursive, q );
00257 fetchJob->fetchScope().setResource( resourceId );
00258 fetchJob->fetchScope().setIncludeStatistics( true );
00259 q->connect( fetchJob, SIGNAL( result( KJob* ) ), q, SLOT( collectionFetchResult( KJob* ) ) );
00260 } else {
00261
00262
00263
00264 const AgentInstance::List resources = AgentManager::self()->instances();
00265 foreach ( const AgentInstance &resource, resources ) {
00266 if ( resource.type().identifier() == mDefaultResourceType ) {
00267 if ( resource.name() == mDefaultResourceOptions.value( QLatin1String( "Name" ) ).toString() ) {
00268
00269 setDefaultResourceId( mSettings, resource.identifier() );
00270 mSettings->writeConfig();
00271 mResourceWasPreexisting = true;
00272 kDebug() << "Found resource" << resource.identifier();
00273 q->setResourceId( resource.identifier() );
00274 q->ResourceScanJob::doStart();
00275 return;
00276 }
00277 }
00278 }
00279
00280
00281 mResourceWasPreexisting = false;
00282 kDebug() << "Creating maildir resource.";
00283 const AgentType type = AgentManager::self()->type( mDefaultResourceType );
00284 AgentInstanceCreateJob *job = new AgentInstanceCreateJob( type, q );
00285 QObject::connect( job, SIGNAL( result( KJob* ) ), q, SLOT( resourceCreateResult( KJob* ) ) );
00286 job->start();
00287 }
00288 }
00289
00290 void DefaultResourceJobPrivate::resourceCreateResult( KJob *job )
00291 {
00292 if ( job->error() ) {
00293 kWarning() << job->errorText();
00294
00295 q->setError( job->error() );
00296 q->setErrorText( job->errorText() );
00297 q->emitResult();
00298 return;
00299 }
00300
00301 AgentInstance agent;
00302
00303
00304 {
00305 AgentInstanceCreateJob *createJob = qobject_cast<AgentInstanceCreateJob*>( job );
00306 Q_ASSERT( createJob );
00307 agent = createJob->instance();
00308 setDefaultResourceId( mSettings, agent.identifier() );
00309 kDebug() << "Created maildir resource with id" << defaultResourceId( mSettings );
00310 }
00311
00312 const QString defaultId = defaultResourceId( mSettings );
00313
00314
00315 {
00316 agent.setName( mDefaultResourceOptions.value( QLatin1String( "Name" ) ).toString() );
00317
00318 QDBusInterface conf( QString::fromLatin1( "org.freedesktop.Akonadi.Resource." ) + defaultId,
00319 QString::fromLatin1( "/Settings" ), QString() );
00320
00321 if ( !conf.isValid() ) {
00322 q->setError( -1 );
00323 q->setErrorText( i18n( "Invalid resource identifier '%1'", defaultId ) );
00324 q->emitResult();
00325 return;
00326 }
00327
00328 QMapIterator<QString, QVariant> it( mDefaultResourceOptions );
00329 while ( it.hasNext() ) {
00330 it.next();
00331
00332 if ( it.key() == QLatin1String( "Name" ) )
00333 continue;
00334
00335 const QString methodName = QString::fromLatin1( "set%1" ).arg( it.key() );
00336 const QVariant::Type argType = argumentType( conf.metaObject(), methodName );
00337 if ( argType == QVariant::Invalid ) {
00338 q->setError( Job::Unknown );
00339 q->setErrorText( i18n( "Failed to configure default resource via D-Bus." ) );
00340 q->emitResult();
00341 return;
00342 }
00343
00344 QDBusReply<void> reply = conf.call( methodName, it.value() );
00345 if ( !reply.isValid() ) {
00346 q->setError( Job::Unknown );
00347 q->setErrorText( i18n( "Failed to configure default resource via D-Bus." ) );
00348 q->emitResult();
00349 return;
00350 }
00351 }
00352
00353 agent.reconfigure();
00354 }
00355
00356
00357 {
00358 ResourceSynchronizationJob *syncJob = new ResourceSynchronizationJob( agent, q );
00359 QObject::connect( syncJob, SIGNAL( result( KJob* ) ), q, SLOT( resourceSyncResult( KJob* ) ) );
00360 syncJob->start();
00361 }
00362 }
00363
00364 void DefaultResourceJobPrivate::resourceSyncResult( KJob *job )
00365 {
00366 if ( job->error() ) {
00367 kWarning() << job->errorText();
00368
00369 return;
00370 }
00371
00372
00373 kDebug() << "Fetching maildir collections.";
00374 CollectionFetchJob *fetchJob = new CollectionFetchJob( Collection::root(), CollectionFetchJob::Recursive, q );
00375 fetchJob->fetchScope().setResource( defaultResourceId( mSettings ) );
00376 QObject::connect( fetchJob, SIGNAL( result( KJob* ) ), q, SLOT( collectionFetchResult( KJob* ) ) );
00377 }
00378
00379 void DefaultResourceJobPrivate::collectionFetchResult( KJob *job )
00380 {
00381 if ( job->error() ) {
00382 kWarning() << job->errorText();
00383
00384 return;
00385 }
00386
00387 CollectionFetchJob *fetchJob = qobject_cast<CollectionFetchJob *>( job );
00388 Q_ASSERT( fetchJob );
00389
00390 const Collection::List collections = fetchJob->collections();
00391 kDebug() << "Fetched" << collections.count() << "collections.";
00392
00393
00394 Collection::List toRecover;
00395 Collection resourceCollection;
00396 foreach ( const Collection &collection, collections ) {
00397 if ( collection.parentCollection() == Collection::root() ) {
00398 resourceCollection = collection;
00399 toRecover.append( collection );
00400 break;
00401 }
00402 }
00403
00404 if ( !resourceCollection.isValid() ) {
00405 q->setError( Job::Unknown );
00406 q->setErrorText( i18n( "Failed to fetch the resource collection." ) );
00407 q->emitResult();
00408 return;
00409 }
00410
00411
00412 foreach ( const Collection &collection, collections ) {
00413 if ( collection.parentCollection() == resourceCollection ) {
00414 toRecover.append( collection );
00415 }
00416 }
00417
00418 QHash<QString, QByteArray> typeForName;
00419 foreach ( const QByteArray &type, mKnownTypes ) {
00420 const QString displayName = mNameForTypeMap.value( type );
00421 typeForName[ displayName ] = type;
00422 }
00423
00424
00425
00426 Q_ASSERT( mPendingModifyJobs == 0 );
00427 foreach ( Collection collection, toRecover ) {
00428
00429 if ( collection.hasAttribute<SpecialCollectionAttribute>() )
00430 continue;
00431
00432
00433 QString name = collection.name();
00434 if ( collection.hasAttribute<EntityDisplayAttribute>() ) {
00435 const QString displayName = collection.attribute<EntityDisplayAttribute>()->displayName();
00436 if (!displayName.isEmpty())
00437 name = displayName;
00438 }
00439 const QByteArray type = typeForName.value( name );
00440
00441 if ( !type.isEmpty() ) {
00442 kDebug() << "Recovering collection" << name;
00443 setCollectionAttributes( collection, type, mNameForTypeMap, mIconForTypeMap );
00444
00445 CollectionModifyJob *modifyJob = new CollectionModifyJob( collection, q );
00446 QObject::connect( modifyJob, SIGNAL( result( KJob* ) ), q, SLOT( collectionModifyResult( KJob* ) ) );
00447 mPendingModifyJobs++;
00448 } else {
00449 kDebug() << "Searching for names: " << typeForName.keys();
00450 kDebug() << "Unknown collection name" << name << "-- not recovering.";
00451 }
00452 }
00453
00454 if ( mPendingModifyJobs == 0 ) {
00455
00456 q->setResourceId( defaultResourceId( mSettings ) );
00457 q->ResourceScanJob::doStart();
00458 }
00459 }
00460
00461 void DefaultResourceJobPrivate::collectionModifyResult( KJob *job )
00462 {
00463 if ( job->error() ) {
00464 kWarning() << job->errorText();
00465
00466 return;
00467 }
00468
00469 Q_ASSERT( mPendingModifyJobs > 0 );
00470 mPendingModifyJobs--;
00471 kDebug() << "pendingModifyJobs now" << mPendingModifyJobs;
00472 if ( mPendingModifyJobs == 0 ) {
00473
00474 kDebug() << "Writing defaultResourceId" << defaultResourceId( mSettings ) << "to config.";
00475 mSettings->writeConfig();
00476
00477
00478 q->setResourceId( defaultResourceId( mSettings ) );
00479 q->ResourceScanJob::doStart();
00480 }
00481 }
00482
00483
00484
00485 DefaultResourceJob::DefaultResourceJob( KCoreConfigSkeleton *settings, QObject *parent )
00486 : ResourceScanJob( QString(), settings, parent ),
00487 d( new DefaultResourceJobPrivate( settings, this ) )
00488 {
00489 }
00490
00491 DefaultResourceJob::~DefaultResourceJob()
00492 {
00493 delete d;
00494 }
00495
00496 void DefaultResourceJob::setDefaultResourceType( const QString &type )
00497 {
00498 d->mDefaultResourceType = type;
00499 }
00500
00501 void DefaultResourceJob::setDefaultResourceOptions( const QVariantMap &options )
00502 {
00503 d->mDefaultResourceOptions = options;
00504 }
00505
00506 void DefaultResourceJob::setTypes( const QList<QByteArray> &types )
00507 {
00508 d->mKnownTypes = types;
00509 }
00510
00511 void DefaultResourceJob::setNameForTypeMap( const QMap<QByteArray, QString> &map )
00512 {
00513 d->mNameForTypeMap = map;
00514 }
00515
00516 void DefaultResourceJob::setIconForTypeMap( const QMap<QByteArray, QString> &map )
00517 {
00518 d->mIconForTypeMap = map;
00519 }
00520
00521 void DefaultResourceJob::doStart()
00522 {
00523 d->tryFetchResource();
00524 }
00525
00526 void DefaultResourceJob::slotResult( KJob *job )
00527 {
00528 if ( job->error() ) {
00529 kWarning() << job->errorText();
00530
00531 if ( !d->mResourceWasPreexisting ) {
00532
00533
00534 const AgentInstance resource = AgentManager::self()->instance( defaultResourceId( d->mSettings ) );
00535 kDebug() << "Removing resource" << resource.identifier();
00536 AgentManager::self()->removeInstance( resource );
00537 }
00538 }
00539
00540 Job::slotResult( job );
00541 }
00542
00543
00544
00545 class Akonadi::GetLockJob::Private
00546 {
00547 public:
00548 Private( GetLockJob *qq );
00549
00550 void doStart();
00551 void serviceOwnerChanged( const QString &name, const QString &oldOwner,
00552 const QString &newOwner );
00553 void timeout();
00554
00555 GetLockJob *const q;
00556 QTimer *mSafetyTimer;
00557 };
00558
00559 GetLockJob::Private::Private( GetLockJob *qq )
00560 : q( qq ),
00561 mSafetyTimer( 0 )
00562 {
00563 }
00564
00565 void GetLockJob::Private::doStart()
00566 {
00567
00568
00569
00570 QDBusConnection bus = DBusConnectionPool::threadConnection();
00571 const bool alreadyLocked = bus.interface()->isServiceRegistered( DBUS_SERVICE_NAME );
00572 const bool gotIt = bus.registerService( DBUS_SERVICE_NAME );
00573
00574 if ( gotIt && !alreadyLocked ) {
00575
00576 q->emitResult();
00577 } else {
00578 QDBusServiceWatcher *watcher = new QDBusServiceWatcher( DBUS_SERVICE_NAME, DBusConnectionPool::threadConnection(),
00579 QDBusServiceWatcher::WatchForOwnerChange, q );
00580
00581 connect( watcher, SIGNAL( serviceOwnerChanged( const QString&, const QString&, const QString& ) ),
00582 q, SLOT( serviceOwnerChanged( const QString&, const QString&, const QString& ) ) );
00583
00584 mSafetyTimer = new QTimer( q );
00585 mSafetyTimer->setSingleShot( true );
00586 mSafetyTimer->setInterval( LOCK_WAIT_TIMEOUT_SECONDS * 1000 );
00587 mSafetyTimer->start();
00588 connect( mSafetyTimer, SIGNAL( timeout() ), q, SLOT( timeout() ) );
00589 }
00590 }
00591
00592 void GetLockJob::Private::serviceOwnerChanged( const QString&, const QString&, const QString &newOwner )
00593 {
00594 if ( newOwner.isEmpty() ) {
00595 const bool gotIt = DBusConnectionPool::threadConnection().registerService( DBUS_SERVICE_NAME );
00596 if ( gotIt ) {
00597 mSafetyTimer->stop();
00598 q->emitResult();
00599 }
00600 }
00601 }
00602
00603 void GetLockJob::Private::timeout()
00604 {
00605 kWarning() << "Timeout trying to get lock.";
00606 q->setError( Job::Unknown );
00607 q->setErrorText( i18n( "Timeout trying to get lock." ) );
00608 q->emitResult();
00609 }
00610
00611
00612 GetLockJob::GetLockJob( QObject *parent )
00613 : KJob( parent ),
00614 d( new Private( this ) )
00615 {
00616 }
00617
00618 GetLockJob::~GetLockJob()
00619 {
00620 delete d;
00621 }
00622
00623 void GetLockJob::start()
00624 {
00625 QTimer::singleShot( 0, this, SLOT( doStart() ) );
00626 }
00627
00628 void Akonadi::setCollectionAttributes( Akonadi::Collection &collection, const QByteArray &type,
00629 const QMap<QByteArray, QString> &nameForType,
00630 const QMap<QByteArray, QString> &iconForType )
00631 {
00632 {
00633 EntityDisplayAttribute *attr = new EntityDisplayAttribute;
00634 attr->setIconName( iconForType.value( type ) );
00635 attr->setDisplayName( nameForType.value( type ) );
00636 collection.addAttribute( attr );
00637 }
00638
00639 {
00640 SpecialCollectionAttribute *attr = new SpecialCollectionAttribute;
00641 attr->setCollectionType( type );
00642 collection.addAttribute( attr );
00643 }
00644 }
00645
00646 bool Akonadi::releaseLock()
00647 {
00648 return DBusConnectionPool::threadConnection().unregisterService( DBUS_SERVICE_NAME );
00649 }
00650
00651 #include "specialcollectionshelperjobs_p.moc"