/* Copyright (c) 2009 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_ENTITYCACHE_P_H #define AKONADI_ENTITYCACHE_P_H #include #include #include #include #include #include #include #include "akonadiprivate_export.h" #include #include #include #include class KJob; namespace Akonadi { /** @internal QObject part of EntityCache. */ class AKONADI_TESTS_EXPORT EntityCacheBase : public QObject { Q_OBJECT public: explicit EntityCacheBase ( Session *session, QObject * parent = 0 ); void setSession(Session *session); protected: Session *session; signals: void dataAvailable(); private slots: virtual void processResult( KJob* job ) = 0; }; template struct EntityCacheNode { EntityCacheNode() : pending( false ), invalid( false ) {} EntityCacheNode( typename T::Id id ) : entity( T( id ) ), pending( true ), invalid( false ) {} T entity; bool pending; bool invalid; }; /** * @internal * A in-memory FIFO cache for a small amount of Entity objects. */ template class EntityCache : public EntityCacheBase { public: typedef FetchScope_ FetchScope; explicit EntityCache( int maxCapacity, Session *session = 0, QObject *parent = 0 ) : EntityCacheBase( session, parent ), mCapacity( maxCapacity ) {} ~EntityCache() { qDeleteAll( mCache ); } /** Object is available in the cache and can be retrieved. */ bool isCached( typename T::Id id ) const { EntityCacheNode* node = cacheNodeForId( id ); return node && !node->pending; } /** Object has been requested but is not yet loaded into the cache or is already available. */ bool isRequested( typename T::Id id ) const { return cacheNodeForId( id ); } /** Returns the cached object if available, an empty instance otherwise. */ virtual T retrieve( typename T::Id id ) const { EntityCacheNode* node = cacheNodeForId( id ); if ( node && !node->pending && !node->invalid ) return node->entity; return T(); } /** Marks the cache entry as invalid, use in case the object has been deleted on the server. */ void invalidate( typename T::Id id ) { EntityCacheNode* node = cacheNodeForId( id ); if ( node ) node->invalid = true; } /** Triggers a re-fetching of a cache entry, use if it has changed on the server. */ void update( typename T::Id id, const FetchScope &scope ) { EntityCacheNode* node = cacheNodeForId( id ); if ( node ) { mCache.removeAll( node ); if ( node->pending ) request( id, scope ); delete node; } } /** Requests the object to be cached if it is not yet in the cache. @returns @c true if it was in the cache already. */ virtual bool ensureCached( typename T::Id id, const FetchScope &scope ) { EntityCacheNode* node = cacheNodeForId( id ); if ( !node ) { request( id, scope ); return false; } return !node->pending; } /** Asks the cache to retrieve @p id. @p request is used as a token to indicate which request has been finished in the dataAvailable() signal. */ virtual void request( typename T::Id id, const FetchScope &scope ) { Q_ASSERT( !isRequested( id ) ); shrinkCache(); EntityCacheNode *node = new EntityCacheNode( id ); FetchJob* job = createFetchJob( id ); job->setFetchScope( scope ); job->setProperty( "EntityCacheNode", QVariant::fromValue( id ) ); connect( job, SIGNAL( result( KJob* )), SLOT(processResult( KJob* ) ) ); mCache.enqueue( node ); } private: EntityCacheNode* cacheNodeForId( typename T::Id id ) const { for ( typename QQueue*>::const_iterator it = mCache.constBegin(), endIt = mCache.constEnd(); it != endIt; ++it ) { if ( (*it)->entity.id() == id ) return *it; } return 0; } void processResult( KJob* job ) { // Error handling? typename T::Id id = job->property( "EntityCacheNode" ).template value(); EntityCacheNode *node = cacheNodeForId( id ); if ( !node ) return; // got replaced in the meantime node->pending = false; extractResult( node, job ); // make sure we find this node again if something went wrong here, // most likely the object got deleted from the server in the meantime if ( node->entity.id() != id ) { // TODO: Recursion guard? If this is called with non-existing ids, the if will never be true! node->entity.setId( id ); node->invalid = true; } emit dataAvailable(); } void extractResult( EntityCacheNode* node, KJob* job ) const; inline FetchJob* createFetchJob( typename T::Id id ) { return new FetchJob( T( id ), session ); } /** Tries to reduce the cache size until at least one more object fits in. */ void shrinkCache() { while ( mCache.size() >= mCapacity && !mCache.first()->pending ) delete mCache.dequeue(); } private: QQueue*> mCache; int mCapacity; }; template<> inline void EntityCache::extractResult( EntityCacheNode* node, KJob *job ) const { CollectionFetchJob* fetch = qobject_cast( job ); Q_ASSERT( fetch ); if ( fetch->collections().isEmpty() ) node->entity = Collection(); else node->entity = fetch->collections().first(); } template<> inline void EntityCache::extractResult( EntityCacheNode* node, KJob *job ) const { ItemFetchJob* fetch = qobject_cast( job ); Q_ASSERT( fetch ); if ( fetch->items().isEmpty() ) node->entity = Item(); else node->entity = fetch->items().first(); } template<> inline CollectionFetchJob* EntityCache::createFetchJob( Collection::Id id ) { return new CollectionFetchJob( Collection( id ), CollectionFetchJob::Base, session ); } typedef EntityCache CollectionCache; typedef EntityCache ItemCache; template class Comparator { public: static bool compare(const typename T::List &lhs_, const QList &rhs_ ) { if (lhs_.size() != rhs_.size()) return false; typename T::List lhs = lhs_; QList rhs = rhs_; qSort(lhs); qSort(rhs); return lhs == rhs; } static bool compare(const QList &l1, const typename T::List &l2) { return compare(l2, l1); } static bool compare(const typename T::List &l1, const typename T::List &l2) { typename T::List l1_ = l1; typename T::List l2_ = l2; qSort(l1_); qSort(l2_); return l1_ == l2_; } }; template struct EntityListCacheNode { EntityListCacheNode( const typename T::List &list ) : entityList( list ), pending( false ), invalid( false ) {} EntityListCacheNode( const QList &list ) : pending( false ), invalid( false ) { foreach( typename T::Id id, list) entityList.append(T(id)); } typename T::List entityList; bool pending; bool invalid; }; template class EntityListCache : public EntityCacheBase { public: typedef FetchScope_ FetchScope; explicit EntityListCache( int maxCapacity, Session *session = 0, QObject *parent = 0 ) : EntityCacheBase( session, parent ), mCapacity( maxCapacity ) {} ~EntityListCache() { qDeleteAll( mCache ); } /** Returns the cached object if available, an empty instance otherwise. */ template typename T::List retrieve( const QList &id ) const { EntityListCacheNode* node = cacheNodeForId( id ); if ( node && !node->pending && !node->invalid ) return node->entityList; return typename T::List(); } /** Requests the object to be cached if it is not yet in the cache. @returns @c true if it was in the cache already. */ template bool ensureCached( const QList &id, const FetchScope &scope ) { EntityListCacheNode* node = cacheNodeForId( id ); if ( !node ) { request( id, scope ); return false; } return !node->pending; } /** Marks the cache entry as invalid, use in case the object has been deleted on the server. */ template void invalidate( const QList &id ) { EntityListCacheNode* node = cacheNodeForId( id ); if ( node ) node->invalid = true; } /** Triggers a re-fetching of a cache entry, use if it has changed on the server. */ template void update( const QList &id, const FetchScope &scope ) { EntityListCacheNode* node = cacheNodeForId( id ); if ( node ) { mCache.removeAll( node ); if ( node->pending ) request( id, scope ); delete node; } } /** Object is available in the cache and can be retrieved. */ template bool isCached( const QList &id ) const { EntityListCacheNode* node = cacheNodeForId( id ); return node && !node->pending; } private: typename T::List getTList( const QList &id ) { typename T::List ids; foreach(typename T::Id id_, id) ids.append(T(id_)); return ids; } typename T::List getTList( const typename T::List &id ) { return id; } /** Asks the cache to retrieve @p id. @p request is used as a token to indicate which request has been finished in the dataAvailable() signal. */ template void request( const QList &id, const FetchScope &scope ) { Q_ASSERT( !isRequested( id ) ); shrinkCache(); EntityListCacheNode *node = new EntityListCacheNode( id ); FetchJob* job = createFetchJob( id ); job->setFetchScope( scope ); job->setProperty( "EntityListCacheNode", QVariant::fromValue( getTList( id ) ) ); connect( job, SIGNAL(result(KJob*)), SLOT(processResult(KJob*)) ); mCache.enqueue( node ); } /** Tries to reduce the cache size until at least one more object fits in. */ void shrinkCache() { while ( mCache.size() >= mCapacity && !mCache.first()->pending ) delete mCache.dequeue(); } /** Object has been requested but is not yet loaded into the cache or is already available. */ template bool isRequested( const QList &id ) const { return cacheNodeForId( id ); } template EntityListCacheNode* cacheNodeForId( const QList &id ) const { for ( typename QQueue*>::const_iterator it = mCache.constBegin(), endIt = mCache.constEnd(); it != endIt; ++it ) { if ( Comparator::compare( ( *it )->entityList, id ) ) return *it; } return 0; } template inline FetchJob* createFetchJob( const QList &id ) { return new FetchJob( id, session ); } void processResult( KJob* job ) { typename T::List ids = job->property( "EntityListCacheNode" ).template value(); EntityListCacheNode *node = cacheNodeForId( ids ); if ( !node ) return; // got replaced in the meantime node->pending = false; extractResult( node, job ); // make sure we find this node again if something went wrong here, // most likely the object got deleted from the server in the meantime if ( node->entityList != ids ) { node->entityList = ids; node->invalid = true; } emit dataAvailable(); } void extractResult( EntityListCacheNode* node, KJob* job ) const; private: QQueue*> mCache; int mCapacity; }; template<> inline void EntityListCache::extractResult( EntityListCacheNode* node, KJob *job ) const { CollectionFetchJob* fetch = qobject_cast( job ); Q_ASSERT( fetch ); node->entityList = fetch->collections(); } template<> inline void EntityListCache::extractResult( EntityListCacheNode* node, KJob *job ) const { ItemFetchJob* fetch = qobject_cast( job ); Q_ASSERT( fetch ); node->entityList = fetch->items(); } template<> template inline CollectionFetchJob* EntityListCache::createFetchJob( const QList &id ) { return new CollectionFetchJob( id, CollectionFetchJob::Base, session ); } typedef EntityListCache CollectionListCache; typedef EntityListCache ItemListCache; } #endif