diff --git a/akonadi/resource.h b/akonadi/resource.h index fd609af45..0a719840b 100644 --- a/akonadi/resource.h +++ b/akonadi/resource.h @@ -1,160 +1,166 @@ /* This file is part of akonadiresources. Copyright (c) 2006 Till Adam 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_RESOURCE_H #define AKONADI_RESOURCE_H #include "libakonadi_export.h" #include #ifndef Q_NOREPLY #define Q_NOREPLY #endif namespace Akonadi { /** * Abstract interface for all resource agent classes. * You should however use @see ResourceBase as base class, as it * provides a lot of convenience methods and abstracts parts * of the protocol. */ class AKONADI_EXPORT Resource : public QObject { Q_OBJECT Q_CLASSINFO( "D-Bus Interface", "org.kde.Akonadi.Resource" ) public: typedef QList List; /** * Destroys the resource. */ virtual ~Resource() { } public Q_SLOTS: /** * This method returns the current status code of the resource. * * The following return values are possible: * * 0 - Ready * 1 - Syncing * 2 - Error */ virtual int status() const = 0; /** * This method returns an i18n'ed description of the current status code. */ virtual QString statusMessage() const = 0; /** * This method returns the current progress of the resource in percentage. */ virtual uint progress() const = 0; /** * This method returns an i18n'ed description of the current progress. */ virtual QString progressMessage() const = 0; /** * This method is called whenever an external query for putting data in the * storage is received. * * @param uid The Akonadi uid of the item that is requested. * @param remoteId The remote identifier of the item that is requested. * @param parts The item parts that should be fetched. */ virtual bool requestItemDelivery( int uid, const QString &remoteId, const QStringList &parts ) = 0; /** * This method is called whenever the resource shall show its configuration dialog * to the user. */ virtual Q_NOREPLY void configure() = 0; /** - * This method is called whenever the resource should start synchronization. + * This method is called whenever the resource should start synchronize all data. */ virtual Q_NOREPLY void synchronize() = 0; + /** + * Synchronize the collection tree. + */ + virtual Q_NOREPLY void synchronizeCollectionTree() = 0; + /** * Synchronize the given collection. * @param collectionId The identifier of the collection to synchronize. + * @param parts The items parts that should be synchronized. */ - virtual Q_NOREPLY void synchronizeCollection( int collectionId ) = 0; + virtual Q_NOREPLY void synchronizeCollection( int collectionId, const QStringList &parts ) = 0; /** * This method is used to set the name of the resource. */ virtual void setName( const QString &name ) = 0; /** * Returns the name of the resource. */ virtual QString name() const = 0; /** * Returns true if the resource is in online mode. */ virtual bool isOnline() const = 0; /** * Sets the online/offline mode. In offline mode, the resource * should not do any network operations and instead record all changes * locally until switched back into online mode. * @param state Switch to online mode if true, to offline mode otherwise. */ virtual void setOnline( bool state ) = 0; Q_SIGNALS: /** * This signal is emitted whenever the status of the resource has changed. * * @param status The status id of the resource (@see Status). * @param message An i18n'ed message which describes the status in detail. */ void statusChanged( int status, const QString &message ); /** * This signal is emitted whenever the progress information of the resource * has changed. * * @param progress The progress in percent (0 - 100). * @param message An i18n'ed message which describes the progress in detail. */ void progressChanged( uint progress, const QString &message ); /** * This signal is emitted whenever the name of the resource has changed. * * @param name The new name of the resource. */ void nameChanged( const QString &name ); }; } #endif diff --git a/akonadi/resourcebase.cpp b/akonadi/resourcebase.cpp index 04e880b10..ae1bfa8db 100644 --- a/akonadi/resourcebase.cpp +++ b/akonadi/resourcebase.cpp @@ -1,564 +1,626 @@ /* This file is part of libakonadi. Copyright (c) 2006 Till Adam Copyright (c) 2007 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 "resourcebase.h" #include "agentbase_p.h" #include "kcrash.h" #include "resourceadaptor.h" #include "collectionsync.h" #include "itemsync.h" #include "resourcescheduler.h" #include "tracerinterface.h" #include "xdgbasedirs.h" #include +#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Akonadi; static ResourceBase *sResourceBase = 0; void crashHandler( int signal ) { if ( sResourceBase ) sResourceBase->crashHandler( signal ); exit( 255 ); } class Akonadi::ResourceBasePrivate : public AgentBasePrivate { public: ResourceBasePrivate( ResourceBase *parent ) : AgentBasePrivate( parent ), mStatusCode( ResourceBase::Ready ), mProgress( 0 ), online( true ), scheduler( 0 ) { mStatusMessage = defaultReadyMessage(); } Q_DECLARE_PUBLIC( ResourceBase ) void slotDeliveryDone( KJob* job ); void slotCollectionSyncDone( KJob *job ); void slotLocalListDone( KJob *job ); void slotSynchronizeCollection( const Collection &col, const QStringList &parts ); void slotCollectionListDone( KJob *job ); void slotItemSyncDone( KJob *job ); void slotPercent( KJob* job, unsigned long percent ); QString defaultReadyMessage() const; QString defaultSyncingMessage() const; + void changeProcessed(); + QString mName; int mStatusCode; QString mStatusMessage; uint mProgress; QString mProgressMessage; bool online; // synchronize states Collection currentCollection; ResourceScheduler *scheduler; }; QString ResourceBasePrivate::defaultReadyMessage() const { if ( online ) return i18nc( "@info:status, application ready for work", "Ready" ); return i18nc( "@info:status", "Offline" ); } QString ResourceBasePrivate::defaultSyncingMessage() const { return i18nc( "@info:status", "Syncing..." ); } +void ResourceBasePrivate::changeProcessed() +{ + monitor->changeProcessed(); + if ( !monitor->isEmpty() ) + scheduler->scheduleChangeReplay(); + scheduler->taskDone(); +} + ResourceBase::ResourceBase( const QString & id ) : AgentBase( new ResourceBasePrivate( this ), id ) { Q_D( ResourceBase ); KCrash::init(); KCrash::setEmergencyMethod( ::crashHandler ); sResourceBase = this; if ( !QDBusConnection::sessionBus().registerService( QLatin1String( "org.kde.Akonadi.Resource." ) + id ) ) error( QString::fromLatin1( "Unable to register service at dbus: %1" ).arg( QDBusConnection::sessionBus().lastError().message() ) ); new ResourceAdaptor( this ); if ( !QDBusConnection::sessionBus().registerObject( QLatin1String( "/" ), this, QDBusConnection::ExportAdaptors ) ) error( QString::fromLatin1( "Unable to register object at dbus: %1" ).arg( QDBusConnection::sessionBus().lastError().message() ) ); const QString name = d->mSettings->value( QLatin1String( "Resource/Name" ) ).toString(); if ( !name.isEmpty() ) d->mName = name; d->scheduler = new ResourceScheduler( this ); d->online = settings()->value( QLatin1String( "Resource/Online" ), true ).toBool(); if ( d->online ) d->monitor->fetchAllParts(); d->monitor->setChangeRecordingEnabled( true ); connect( d->monitor, SIGNAL(changesAdded()), d->scheduler, SLOT(scheduleChangeReplay()) ); d->monitor->monitorResource( d->mId.toLatin1() ); connect( d->scheduler, SIGNAL(executeFullSync()), SLOT(retrieveCollections()) ); + connect( d->scheduler, SIGNAL(executeCollectionTreeSync()), + SLOT(retrieveCollections()) ); connect( d->scheduler, SIGNAL(executeCollectionSync(Akonadi::Collection,QStringList)), SLOT(slotSynchronizeCollection(Akonadi::Collection,QStringList)) ); connect( d->scheduler, SIGNAL(executeItemFetch(Akonadi::Item,QStringList)), SLOT(retrieveItem(Akonadi::Item,QStringList)) ); connect( d->scheduler, SIGNAL(executeChangeReplay()), d->monitor, SLOT(replayNext()) ); d->scheduler->setOnline( d->online ); if ( !d->monitor->isEmpty() ) d->scheduler->scheduleChangeReplay(); // initial configuration bool initialized = settings()->value( QLatin1String( "Resource/Initialized" ), false ).toBool(); if ( !initialized ) { QTimer::singleShot( 0, this, SLOT(configure()) ); // finish construction first settings()->setValue( QLatin1String( "Resource/Initialized" ), true ); } } ResourceBase::~ResourceBase() { } int ResourceBase::status() const { return d_func()->mStatusCode; } QString ResourceBase::statusMessage() const { return d_func()->mStatusMessage; } uint ResourceBase::progress() const { return d_func()->mProgress; } QString ResourceBase::progressMessage() const { return d_func()->mProgressMessage; } void ResourceBase::changeStatus( Status status, const QString &message ) { Q_D( ResourceBase ); d->mStatusMessage = message; d->mStatusCode = 0; switch ( status ) { case Ready: if ( d->mStatusMessage.isEmpty() ) d->mStatusMessage = d->defaultReadyMessage(); d->mStatusCode = 0; break; case Syncing: if ( d->mStatusMessage.isEmpty() ) d->mStatusMessage = d->defaultSyncingMessage(); d->mStatusCode = 1; break; case Error: if ( d->mStatusMessage.isEmpty() ) d->mStatusMessage = d->defaultErrorMessage(); d->mStatusCode = 2; break; default: Q_ASSERT( !"Unknown status passed" ); break; } emit statusChanged( d->mStatusCode, d->mStatusMessage ); } void ResourceBase::changeProgress( uint progress, const QString &message ) { Q_D( ResourceBase ); d->mProgress = progress; d->mProgressMessage = message; emit progressChanged( d->mProgress, d->mProgressMessage ); } void ResourceBase::configure() { } void ResourceBase::synchronize() { d_func()->scheduler->scheduleFullSync(); } void ResourceBase::setName( const QString &name ) { Q_D( ResourceBase ); if ( name == d->mName ) return; // TODO: rename collection d->mName = name; if ( d->mName.isEmpty() || d->mName == d->mId ) d->mSettings->remove( QLatin1String( "Resource/Name" ) ); else d->mSettings->setValue( QLatin1String( "Resource/Name" ), d->mName ); d->mSettings->sync(); emit nameChanged( d->mName ); } QString ResourceBase::name() const { const Q_D( ResourceBase ); if ( d->mName.isEmpty() ) return d->mId; else return d->mName; } static char* sAppName = 0; QString ResourceBase::parseArguments( int argc, char **argv ) { QString identifier; if ( argc < 3 ) { kDebug( 5250 ) << "Not enough arguments passed..."; exit( 1 ); } for ( int i = 1; i < argc - 1; ++i ) { if ( QLatin1String( argv[ i ] ) == QLatin1String( "--identifier" ) ) identifier = QLatin1String( argv[ i + 1 ] ); } if ( identifier.isEmpty() ) { kDebug( 5250 ) << "Identifier argument missing"; exit( 1 ); } sAppName = qstrdup( identifier.toLatin1().constData() ); KCmdLineArgs::init( argc, argv, sAppName, 0, ki18nc("@title, application name", "Akonadi Resource"), "0.1", ki18nc("@title, application description", "Akonadi Resource") ); KCmdLineOptions options; options.add("identifier ", ki18nc("@label, commandline option", "Resource identifier")); KCmdLineArgs::addCmdLineOptions( options ); return identifier; } int ResourceBase::init( ResourceBase *r ) { QApplication::setQuitOnLastWindowClosed( false ); int rv = kapp->exec(); delete r; delete[] sAppName; return rv; } void ResourceBase::crashHandler( int signal ) { /** * If we retrieved a SIGINT or SIGTERM we close normally */ if ( signal == SIGINT || signal == SIGTERM ) quit(); } void ResourceBase::itemRetrieved( const Item &item ) { Q_D( ResourceBase ); Q_ASSERT( d->scheduler->currentTask().type == ResourceScheduler::FetchItem ); if ( !item.isValid() ) { QDBusMessage reply( d->scheduler->currentTask().dbusMsg ); reply << false; QDBusConnection::sessionBus().send( reply ); d->scheduler->taskDone(); return; } Item i( item ); QStringList requestedParts = d->scheduler->currentTask().itemParts; foreach ( QString part, requestedParts ) { if ( !item.availableParts().contains( part ) ) { kWarning( 5250 ) << "Item does not provide part" << part; i.addPart( part, QByteArray() ); } } ItemStoreJob *job = new ItemStoreJob( i, session() ); job->storePayload(); // FIXME: remove once the item with which we call retrieveItem() has a revision number job->noRevCheck(); connect( job, SIGNAL(result(KJob*)), SLOT(slotDeliveryDone(KJob*)) ); } void ResourceBasePrivate::slotDeliveryDone(KJob * job) { Q_Q( ResourceBase ); Q_ASSERT( scheduler->currentTask().type == ResourceScheduler::FetchItem ); QDBusMessage reply( scheduler->currentTask().dbusMsg ); if ( job->error() ) { q->error( QLatin1String( "Error while creating item: " ) + job->errorString() ); reply << false; } else { reply << true; } QDBusConnection::sessionBus().send( reply ); scheduler->taskDone(); } void ResourceBase::changesCommitted(const DataReference & ref) { Q_D( ResourceBase ); ItemStoreJob *job = new ItemStoreJob( Item( ref ), session() ); job->setClean(); - d->monitor->changeProcessed(); - if ( !d->monitor->isEmpty() ) - d->scheduler->scheduleChangeReplay(); - d->scheduler->taskDone(); + job->noRevCheck(); + d->changeProcessed(); +} + +void ResourceBase::changesCommitted( const Collection &collection ) +{ + Q_D( ResourceBase ); + CollectionModifyJob *job = new CollectionModifyJob( collection, session() ); + d->changeProcessed(); } bool ResourceBase::requestItemDelivery(int uid, const QString & remoteId, const QStringList &parts ) { Q_D( ResourceBase ); if ( !isOnline() ) { error( i18nc( "@info", "Cannot fetch item in offline mode." ) ); return false; } setDelayedReply( true ); // FIXME: we need at least the revision number too d->scheduler->scheduleItemFetch( Item( DataReference( uid, remoteId ) ), parts, message().createReply() ); return true; } bool ResourceBase::isOnline() const { return d_func()->online; } void ResourceBase::setOnline(bool state) { Q_D( ResourceBase ); d->online = state; settings()->setValue( QLatin1String( "Resource/Online" ), state ); d->monitor->fetchCollection( state ); // TODO: d->monitor->fetchItemData( state ); d->scheduler->setOnline( state ); } void ResourceBase::collectionsRetrieved(const Collection::List & collections) { Q_D( ResourceBase ); CollectionSync *syncer = new CollectionSync( d->mId, session() ); syncer->setRemoteCollections( collections ); connect( syncer, SIGNAL(result(KJob*)), SLOT(slotCollectionSyncDone(KJob*)) ); } void ResourceBase::collectionsRetrievedIncremental(const Collection::List & changedCollections, const Collection::List & removedCollections) { Q_D( ResourceBase ); CollectionSync *syncer = new CollectionSync( d->mId, session() ); syncer->setRemoteCollections( changedCollections, removedCollections ); connect( syncer, SIGNAL(result(KJob*)), SLOT(slotCollectionSyncDone(KJob*)) ); } void ResourceBasePrivate::slotCollectionSyncDone(KJob * job) { Q_Q( ResourceBase ); if ( job->error() ) { q->error( job->errorString() ); } else { if ( scheduler->currentTask().type == ResourceScheduler::SyncAll ) { CollectionListJob *list = new CollectionListJob( Collection::root(), CollectionListJob::Recursive, session ); list->setResource( mId ); q->connect( list, SIGNAL(result(KJob*)), q, SLOT(slotLocalListDone(KJob*)) ); return; } } q->changeStatus( ResourceBase::Ready ); scheduler->taskDone(); } void ResourceBasePrivate::slotLocalListDone(KJob * job) { Q_Q( ResourceBase ); if ( job->error() ) { q->error( job->errorString() ); } else { Collection::List cols = static_cast( job )->collections(); foreach ( const Collection &col, cols ) { - // FIXME: add item parts to the dbus interface - scheduler->scheduleSync( col, QStringList() ); + scheduler->scheduleSync( col, scheduler->currentTask().itemParts ); } } scheduler->taskDone(); } void ResourceBasePrivate::slotSynchronizeCollection( const Collection &col, const QStringList &parts ) { Q_Q( ResourceBase ); currentCollection = col; // check if this collection actually can contain anything QStringList contentTypes = currentCollection.contentTypes(); contentTypes.removeAll( Collection::collectionMimeType() ); if ( !contentTypes.isEmpty() ) { q->changeStatus( ResourceBase::Syncing, i18nc( "@info:status", "Syncing collection '%1'", currentCollection.name() ) ); q->retrieveItems( currentCollection, parts ); return; } scheduler->taskDone(); } void ResourceBase::itemsRetrieved() { Q_D( ResourceBase ); changeStatus( Ready ); d->scheduler->taskDone(); } Collection ResourceBase::currentCollection() const { const Q_D( ResourceBase ); Q_ASSERT_X( d->scheduler->currentTask().type == ResourceScheduler::SyncCollection , "ResourceBase::currentCollection()", "Trying to access current collection although no item retrieval is in progress" ); return d->currentCollection; } Item ResourceBase::currentItem() const { const Q_D( ResourceBase ); Q_ASSERT_X( d->scheduler->currentTask().type == ResourceScheduler::FetchItem , "ResourceBase::currentItem()", "Trying to access current item although no item retrieval is in progress" ); return d->scheduler->currentTask().item; } -void ResourceBase::synchronizeCollection(int collectionId) +void ResourceBase::synchronizeCollectionTree() +{ + d_func()->scheduler->scheduleCollectionTreeSync(); +} + +void ResourceBase::synchronizeCollection(int collectionId, const QStringList &parts) { CollectionListJob* job = new CollectionListJob( Collection(collectionId), CollectionListJob::Local, session() ); job->setResource( identifier() ); + job->setProperty( "akonadi-parts", parts ); connect( job, SIGNAL(result(KJob*)), SLOT(slotCollectionListDone(KJob*)) ); } void ResourceBasePrivate::slotCollectionListDone( KJob *job ) { if ( !job->error() ) { Collection::List list = static_cast( job )->collections(); if ( !list.isEmpty() ) { Collection col = list.first(); - // FIXME: get the needed item parts from somewhere - scheduler->scheduleSync( col, QStringList() ); + scheduler->scheduleSync( col, job->property( "akonadi-parts" ).toStringList() ); } } // TODO: error handling } void ResourceBase::itemsRetrieved(const Item::List & items) { Q_D( ResourceBase ); Q_ASSERT_X( d->scheduler->currentTask().type == ResourceScheduler::SyncCollection, "ResourceBase::itemsRetrieved()", "Calling itemsRetrieved() although no item retrieval is in progress" ); ItemSync *syncer = new ItemSync( currentCollection(), session() ); connect( syncer, SIGNAL(percent(KJob*,unsigned long)), SLOT(slotPercent(KJob*,unsigned long)) ); connect( syncer, SIGNAL(result(KJob*)), SLOT(slotItemSyncDone(KJob*)) ); syncer->setRemoteItems( items ); } void ResourceBase::itemsRetrievedIncremental(const Item::List & changedItems, const Item::List & removedItems) { Q_D( ResourceBase ); Q_ASSERT_X( d->scheduler->currentTask().type == ResourceScheduler::SyncCollection, "ResourceBase::itemsRetrievedIncremental()", "Calling itemsRetrievedIncremental() although no item retrieval is in progress" ); ItemSync *syncer = new ItemSync( currentCollection(), session() ); connect( syncer, SIGNAL(percent(KJob*,unsigned long)), SLOT(slotPercent(KJob*,unsigned long)) ); connect( syncer, SIGNAL(result(KJob*)), SLOT(slotItemSyncDone(KJob*)) ); syncer->setRemoteItems( changedItems, removedItems ); } void ResourceBasePrivate::slotItemSyncDone( KJob *job ) { Q_Q( ResourceBase ); if ( job->error() ) { q->error( job->errorString() ); } q->changeStatus( ResourceBase::Ready ); scheduler->taskDone(); } void ResourceBasePrivate::slotPercent( KJob *job, unsigned long percent ) { Q_Q( ResourceBase ); Q_UNUSED( job ); q->changeProgress( percent ); } +void ResourceBase::itemAdded( const Akonadi::Item &item, const Akonadi::Collection &collection ) +{ + Q_UNUSED( item ); + Q_UNUSED( collection ); + d_func()->changeProcessed(); +} + +void ResourceBase::itemChanged( const Akonadi::Item &item, const QStringList &partIdentifiers ) +{ + Q_UNUSED( item ); + Q_UNUSED( partIdentifiers ); + d_func()->changeProcessed(); +} + +void ResourceBase::itemRemoved( const Akonadi::DataReference &ref ) +{ + Q_UNUSED( ref ); + d_func()->changeProcessed(); +} + +void ResourceBase::collectionAdded( const Akonadi::Collection &collection, const Akonadi::Collection &parent ) +{ + Q_UNUSED( collection ); + Q_UNUSED( parent ); + d_func()->changeProcessed(); +} + +void ResourceBase::collectionChanged( const Akonadi::Collection &collection ) +{ + Q_UNUSED( collection ); + d_func()->changeProcessed(); +} + +void ResourceBase::collectionRemoved( int id, const QString &remoteId ) +{ + Q_UNUSED( id ); + Q_UNUSED( remoteId ); + d_func()->changeProcessed(); +} + #include "resource.moc" #include "resourcebase.moc" diff --git a/akonadi/resourcebase.h b/akonadi/resourcebase.h index a549f3445..c584207ed 100644 --- a/akonadi/resourcebase.h +++ b/akonadi/resourcebase.h @@ -1,425 +1,444 @@ /* This file is part of akonadiresources. Copyright (c) 2006 Till Adam Copyright (c) 2007 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_RESOURCEBASE_H #define AKONADI_RESOURCEBASE_H #include "libakonadi_export.h" #include #include #include #include #include class KJob; class ResourceAdaptor; namespace Akonadi { class Item; class Job; class Session; class ResourceBasePrivate; /** * This class should be used as a base class by all resource agents, * since it encapsulates large parts of the protocol between * resource agent, agent manager and storage. * * It provides many convenience methods to make implementing a * new Akonadi resource agent as simple as possible. * *

How to write a resource

* * The following provides an overview of what you need to do to implement * your own Akonadi resource. In the following, the term 'backend' refers * to the entity the resource connects with Akonadi, be it a single file * or a remote server. * * @todo Complete this (online/offline state management) * *
Basic %Resource Framework
* * The following is needed to create a new resource: * - A new class deriving from Akonadi::ResourceBase, implementing at least all * pure-virtual methods, see below for further details. * - call init() in your main() function. * - a .desktop file similar to the following example * \code * [Desktop Entry] * Encoding=UTF-8 * Name=My Akonadi Resource * Type=AkonadiResource * Exec=akonadi_my_resource * Icon=my-icon * * X-Akonadi-MimeTypes= * X-Akonadi-Capabilities=Resource * X-Akonadi-Identifier=akonadi_my_resource * \endcode * *
Handling PIM Items
* * To follow item changes in the backend, the following steps are necessary: * - Implement retrieveItems() to synchronize all items in the given * collection. If the backend supports incremental retrieval, * implementing support for that is recommended to improve performance. * - Convert the items provided by the backend to Akonadi items. * This typically happens either in retrieveItems() if you retrieved * the collection synchronously (not recommended for network backends) or * in the result slot of the asynchronous retrieval job. * Converting means to create Akonadi::Item objects for every retrieved * item. It's very important that every object has its remote identifier set. * - Call itemsRetrieved() or itemsRetrievedIncremental() respectively * with the item objects created above. The Akonadi storage will then be * updated automatically. Note that it is usually not necessary to manipulate * any item in the Akonadi storage manually. * * To fetch item data on demand, the method retrieveItem() needs to be * reimplemented. Fetch the requested data there and call itemRetrieved() * with the result item. * * To write local changes back to the backend, you need to re-implement * the following three methods: * - itemAdded() * - itemChanged() * - itemRemoved() + * Once you have handled changes in these methods call changesCommitted(). * These methods are called whenever a local item related to this resource is * added, modified or deleted. They are only called if the resource is online, otherwise * all changes are recorded and replayed as soon the resource is online again. * *
Handling Collections
* * To follow collection changes in the backend, the following steps are necessary: * - Implement retrieveCollections() to retrieve collections from the backend. * If the backend supports incremental collections updates, implementing * support for that is recommended to improve performance. * - Convert the collections of the backend to Akonadi collections. * This typically happens either in retrieveCollections() if you retrieved * the collection synchronously (not recommended for network backends) or * in the result slot of the asynchronous retrieval job. * Converting means to create Akonadi::Collection objects for every retrieved * collection. It's very important that every object has its remote identifier * and its parent remote identifier set. * - Call collectionsRetrieved() or collectionsRetrievedIncremental() respectively * with the collection objects created above. The Akonadi storage will then be * updated automatically. Note that it is usually not necessary to manipulate * any collection in the Akonadi storage manually. * * * To write local collection changes back to the backend, you need to re-implement * the following three methods: * - collectionAdded() * - collectionChanged() * - collectionRemoved() + * Once you have handled changes in these methods call changesCommitted(). * These methods are called whenever a local collection related to this resource is * added, modified or deleted. They are only called if the resource is online, otherwise * all changes are recorded and replayed as soon the resource is online again. * * @todo Convenience base class for collection-less resources */ class AKONADI_EXPORT ResourceBase : public AgentBase { Q_OBJECT public: /** * This enum describes the different states the * resource can be in. */ enum Status { Ready = 0, Syncing, Error }; /** * Use this method in the main function of your resource * application to initialize your resource subclass. * This method also takes care of creating a KApplication * object and parsing command line arguments. * * \code * * class MyResource : public ResourceBase * { * ... * }; * * int main( int argc, char **argv ) * { * return ResourceBase::init( argc, argv ); * } * * \endcode */ template static int init( int argc, char **argv ) { QString id = parseArguments( argc, argv ); KApplication app; T* r = new T( id ); return init( r ); } /** * This method returns the current status code of the resource. * * The following return values are possible: * * 0 - Ready * 1 - Syncing * 2 - Error */ virtual int status() const; /** * This method returns an i18n'ed description of the current status code. */ virtual QString statusMessage() const; /** * This method returns the current progress of the resource in percentage. */ virtual uint progress() const; /** * This method returns an i18n'ed description of the current progress. */ virtual QString progressMessage() const; /** - * This method is called whenever the resource should start synchronization. + * This method is called whenever the resource should start synchronize all data. */ virtual void synchronize(); /** * This method is used to set the name of the resource. */ virtual void setName( const QString &name ); /** * Returns the name of the resource. */ virtual QString name() const; /** * This method is called from the crash handler, don't call * it manually. */ void crashHandler( int signal ); virtual bool isOnline() const; virtual void setOnline( bool state ); public Q_SLOTS: /** * This method is called whenever the resource shall show its configuration dialog * to the user. It will be automatically called when the resource is started for * the first time. */ virtual void configure(); Q_SIGNALS: /** * This signal is emitted whenever the status of the resource has changed. * * @param status The status id of the resource (@see Status). * @param message An i18n'ed message which describes the status in detail. */ void statusChanged( int status, const QString &message ); /** * This signal is emitted whenever the progress information of the resource * has changed. * * @param progress The progress in percent (0 - 100). * @param message An i18n'ed message which describes the progress in detail. */ void progressChanged( uint progress, const QString &message ); /** * This signal is emitted whenever the name of the resource has changed. * * @param name The new name of the resource. */ void nameChanged( const QString &name ); protected Q_SLOTS: /** Retrieve the collection tree from the remote server and supply it via collectionsRetrieved() or collectionsRetrievedIncremental(). @see collectionsRetrieved(), collectionsRetrievedIncremental() */ virtual void retrieveCollections() = 0; /** Retrieve all (new/changed) items in collection @p collection. It is recommended to use incremental retrieval if the backend supports that and provide the result by calling itemsRetrievedIncremental(). If incremental retrieval is not possible, provide the full listing by calling itemsRetrieved( const Item::List& ). In any case, ensure that all items have a correctly set remote identifier to allow synchronizing with already locally existing items. In case you don't want to use the built-in item syncing code, store the retrived items manually and call itemsRetrieved() once you are done. @param collection The collection to sync. @param parts The items parts that should be retrieved. @see itemsRetrieved( const Item::List &), itemsRetrievedIncremental(), itemsRetrieved(), currentCollection() */ virtual void retrieveItems( const Akonadi::Collection &collection, const QStringList &parts ) = 0; /** Retrieve a single item from the backend. The item to retrieve is provided as @p item. Add the requested payload parts and call itemRetrieved() when done. @param item The empty item which payload should be retrieved. Use this object when delivering the result instead of creating a new item to ensure conflict detection to work. @param parts The item parts that should be retrieved. @return false if there is an immediate error when retrieving the item. @see itemRetrieved() */ virtual bool retrieveItem( const Akonadi::Item &item, const QStringList &parts ) = 0; + // reimplemnted from AgentBase + void itemAdded( const Akonadi::Item &item, const Akonadi::Collection &collection ); + void itemChanged( const Akonadi::Item &item, const QStringList &partIdentifiers ); + void itemRemoved( const Akonadi::DataReference &ref ); + void collectionAdded( const Akonadi::Collection &collection, const Akonadi::Collection &parent ); + void collectionChanged( const Akonadi::Collection &collection ); + void collectionRemoved( int id, const QString &remoteId ); + protected: /** * Creates a base resource. * * @param id The instance id of the resource. */ ResourceBase( const QString & id ); /** * Destroys the base resource. */ ~ResourceBase(); /** * This method shall be used to signal a state change. * * @param status The new status of the resource. * @param message An i18n'ed description of the status. If message * is empty, the default description for the status is used. */ void changeStatus( Status status, const QString &message = QString() ); /** * This method shall be used to signal a progress change. * * @param progress The new progress of the resource in percentage. * @param message An i18n'ed description of the progress. */ void changeProgress( uint progress, const QString &message = QString() ); /** Call this method from retrieveItem() once the result is available. @param job The job which actually delivers the item. @param msg The D-Bus message requesting the delivery. */ void itemRetrieved( const Item &item ); /** Resets the dirty flag of the given item and updates the remote id. Call whenever you have successfully written changes back to the server. @param ref DataReference of the item. */ void changesCommitted( const DataReference &ref ); + /** + Call whenever you have successfully handled or ignored a collection + change notification. This will update the remote identifier of + @p collection if necessary, as well as any other collection attributes. + @param collection The collection which changes have been handled. + */ + void changesCommitted( const Collection &collection ); + /** Call this to supply the full folder tree retrieved from the remote server. @param collections A list of collections. @see collectionsRetrievedIncremental() */ void collectionsRetrieved( const Collection::List &collections ); /** Call this to supply incrementally retrieved collections from the remote server. @param changedCollections Collections that have been added or changed. @param removedCollections Collections that have been deleted. @see collectionsRetrieved() */ void collectionsRetrievedIncremental( const Collection::List &changedCollections, const Collection::List &removedCollections ); /** Call this methods to supply the full collection listing from the remote server. If the remote server supports incremental listing, it's strongly recommended to use itemsRetrievedIncremental() instead. @param items A list of items. @see itemsRetrievedIncremental(). */ void itemsRetrieved( const Item::List &items ); /** Call this method to supply incrementally retrieved items from the remote server. @param changedItems Items changed in the backend @param removedItems Items removed from the backend */ void itemsRetrievedIncremental( const Item::List &changedItems, const Item::List &removedItems ); /** Call this method to indicate you finished synchronizing the current collection. This is not needed if you use the built in syncing and call itemsRetrieved() or itemsRetrievedIncremental() instead. @see retrieveItems() */ void itemsRetrieved(); /** Returns the collection that is currently synchronized. */ Collection currentCollection() const; /** Returns the item that is currently retrieved. */ Item currentItem() const; private: static QString parseArguments( int, char** ); static int init( ResourceBase *r ); // dbus resource interface friend class ::ResourceAdaptor; - void synchronizeCollection( int collectionId ); + void synchronizeCollectionTree(); + void synchronizeCollection( int collectionId, const QStringList &parts ); bool requestItemDelivery( int uid, const QString &remoteId, const QStringList &parts ); private: Q_DECLARE_PRIVATE( ResourceBase ) Q_PRIVATE_SLOT( d_func(), void slotDeliveryDone( KJob* ) ) Q_PRIVATE_SLOT( d_func(), void slotCollectionSyncDone( KJob* ) ) Q_PRIVATE_SLOT( d_func(), void slotLocalListDone( KJob* ) ) Q_PRIVATE_SLOT( d_func(), void slotSynchronizeCollection(const Akonadi::Collection &col, const QStringList &parts) ) Q_PRIVATE_SLOT( d_func(), void slotCollectionListDone( KJob* ) ) Q_PRIVATE_SLOT( d_func(), void slotItemSyncDone( KJob* ) ) Q_PRIVATE_SLOT( d_func(), void slotPercent( KJob*, unsigned long ) ) }; } #endif diff --git a/akonadi/resourcescheduler.cpp b/akonadi/resourcescheduler.cpp index 65136b4ef..82479e54f 100644 --- a/akonadi/resourcescheduler.cpp +++ b/akonadi/resourcescheduler.cpp @@ -1,126 +1,137 @@ /* Copyright (c) 2007 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 "resourcescheduler.h" #include #include using namespace Akonadi; ResourceScheduler::ResourceScheduler( QObject *parent ) : QObject( parent ), mOnline( false ) { } void ResourceScheduler::scheduleFullSync() { Task t; t.type = SyncAll; mTaskList << t; scheduleNext(); } +void ResourceScheduler::scheduleCollectionTreeSync() +{ + Task t; + t.type = SyncCollectionTree; + mTaskList << t; + scheduleNext(); +} + void ResourceScheduler::scheduleSync(const Collection & col, const QStringList &parts) { Task t; t.type = SyncCollection; t.collection = col; t.itemParts = parts; mTaskList << t; scheduleNext(); } void ResourceScheduler::scheduleItemFetch(const Item & item, const QStringList &parts, const QDBusMessage & msg) { Task t; t.type = FetchItem; t.item = item; t.itemParts = parts; t.dbusMsg = msg; mTaskList << t; scheduleNext(); } void ResourceScheduler::scheduleChangeReplay() { Task t; t.type = ChangeReplay; if ( mTaskList.contains( t ) ) return; mTaskList << t; scheduleNext(); } void ResourceScheduler::taskDone() { mCurrentTask = Task(); scheduleNext(); } void ResourceScheduler::scheduleNext() { if ( mCurrentTask.type != Invalid || mTaskList.isEmpty() || !mOnline ) return; QTimer::singleShot( 0, this, SLOT(executeNext()) ); } void ResourceScheduler::executeNext() { mCurrentTask = mTaskList.takeFirst(); switch ( mCurrentTask.type ) { case SyncAll: emit executeFullSync(); break; + case SyncCollectionTree: + emit executeCollectionTreeSync(); + break; case SyncCollection: emit executeCollectionSync( mCurrentTask.collection, mCurrentTask.itemParts ); break; case FetchItem: emit executeItemFetch( mCurrentTask.item, mCurrentTask.itemParts ); break; case ChangeReplay: emit executeChangeReplay(); break; default: Q_ASSERT( false ); } } ResourceScheduler::Task ResourceScheduler::currentTask() const { return mCurrentTask; } void ResourceScheduler::setOnline(bool state) { if ( mOnline == state ) return; mOnline = state; if ( mOnline ) { scheduleNext(); } else if ( mCurrentTask.type != Invalid ) { // abort running task mTaskList.prepend( mCurrentTask ); mCurrentTask = Task(); } } #include "resourcescheduler.moc" diff --git a/akonadi/resourcescheduler.h b/akonadi/resourcescheduler.h index 33b360477..8ce10d11c 100644 --- a/akonadi/resourcescheduler.h +++ b/akonadi/resourcescheduler.h @@ -1,130 +1,136 @@ /* Copyright (c) 2007 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_RESOURCESCHEDULER_H #define AKONADI_RESOURCESCHEDULER_H #include #include #include #include namespace Akonadi { /** @internal Manages synchronization and fetch requests for a resource. @todo Attach to the ResourceBase Monitor, */ class ResourceScheduler : public QObject { Q_OBJECT public: enum TaskType { Invalid, SyncAll, SyncCollectionTree, SyncCollection, FetchItem, ChangeReplay }; class Task { public: Task() : type( Invalid ) {} TaskType type; Collection collection; Item item; QStringList itemParts; QDBusMessage dbusMsg; bool operator==( const Task &other ) const { return type == other.type && collection == other.collection && item.reference() == other.item.reference() && itemParts == other.itemParts; } }; ResourceScheduler( QObject *parent = 0 ); /** Schedules a full synchronization. */ void scheduleFullSync(); + /** + Schedules a collection tree sync. + */ + void scheduleCollectionTreeSync(); + /** Schedules the synchronization of a single collection. @param col The collection to synchronize. */ void scheduleSync( const Collection &col, const QStringList &parts ); /** Schedules fetching of a single PIM item. @param item the item to fetch. @param parts List of names of the parts of the item to fetch. @param msg The associated D-Bus message. */ void scheduleItemFetch( const Item &item, const QStringList &parts, const QDBusMessage &msg ); /** The current task has been finished */ void taskDone(); /** Returns the current task. */ Task currentTask() const; /** Sets the online state. */ void setOnline( bool state ); public Q_SLOTS: /** Schedules replaying changes. */ void scheduleChangeReplay(); Q_SIGNALS: void executeFullSync(); void executeCollectionSync( const Akonadi::Collection &col, const QStringList &parts ); + void executeCollectionTreeSync(); void executeItemFetch( const Akonadi::Item &item, const QStringList &parts ); void executeChangeReplay(); private slots: void scheduleNext(); void executeNext(); private: QList mTaskList; Task mCurrentTask; bool mOnline; }; } #endif