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