From c24329bb570ee16c033228588e6d22b0f6000f95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Vr=C3=A1til?= Date: Fri, 5 Dec 2014 18:23:33 +0100 Subject: [PATCH 22/30] Implement cache for CollectionStatistics to significantly reduce amount of SQL queries Collection statistics are being requested extremely often (basically whenever a PimItem is changed, or when a Collection itself is changed), and it's always requested by at least 5 or so clients (including agents that listen to everything). To decrease the load on database we now cache the Collection statistics and we only invalidate a cache entry when respective collection (or it's content) is changed. The invalidation is invoked from NotificationCollector, which is basically a hack, but performance-wise it's the best place to avoid additional expensive queries. This patch also optimizes the SQL query needed to get up-to-date statistics. We now have only one query to get both full count and read items count, which a bit is faster as the database only has to deal with one large JOIN. Thanks to the cache the number of SQL queries for Collection statistics have reduced by 70%-80%, and average query duration is now between 20 and 80ms depending on average collection size and database used. --- server/CMakeLists.txt | 1 + server/src/handler/link.cpp | 2 +- server/src/handler/merge.cpp | 4 +- server/src/handler/select.cpp | 14 ++-- server/src/handler/status.cpp | 20 ++--- server/src/handlerhelper.cpp | 81 ++------------------ server/src/handlerhelper.h | 22 ------ server/src/storage/collectionstatistics.cpp | 108 +++++++++++++++++++++++++++ server/src/storage/collectionstatistics.h | 70 +++++++++++++++++ server/src/storage/datastore.cpp | 8 +- server/src/storage/datastore.h | 6 +- server/src/storage/notificationcollector.cpp | 8 ++ server/tests/unittest/fakedatastore.cpp | 8 +- server/tests/unittest/fakedatastore.h | 2 + 14 files changed, 224 insertions(+), 130 deletions(-) create mode 100644 server/src/storage/collectionstatistics.cpp create mode 100644 server/src/storage/collectionstatistics.h diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt index 275938d..f0e0093 100644 --- a/server/CMakeLists.txt +++ b/server/CMakeLists.txt @@ -161,6 +161,7 @@ set(libakonadiprivate_SRCS src/search/searchmanager.cpp src/storage/collectionqueryhelper.cpp + src/storage/collectionstatistics.cpp src/storage/entity.cpp ${CMAKE_CURRENT_BINARY_DIR}/entities.cpp ${CMAKE_CURRENT_BINARY_DIR}/akonadischema.cpp diff --git a/server/src/handler/link.cpp b/server/src/handler/link.cpp index ce18e47..227de11 100644 --- a/server/src/handler/link.cpp +++ b/server/src/handler/link.cpp @@ -25,10 +25,10 @@ #include "storage/itemqueryhelper.h" #include "storage/transaction.h" #include "storage/selectquerybuilder.h" +#include "storage/collectionqueryhelper.h" #include "entities.h" #include "imapstreamparser.h" -#include using namespace Akonadi::Server; diff --git a/server/src/handler/merge.cpp b/server/src/handler/merge.cpp index c26917d..5149916 100644 --- a/server/src/handler/merge.cpp +++ b/server/src/handler/merge.cpp @@ -88,7 +88,7 @@ bool Merge::mergeItem( PimItem &newItem, PimItem ¤tItem, if ( !itemFlags.removed.isEmpty() ) { const Flag::List removedFlags = HandlerHelper::resolveFlags( itemFlags.removed ); DataStore::self()->removeItemsFlags( PimItem::List() << currentItem, removedFlags, - &flagsRemoved, true ); + &flagsRemoved, col, true ); } if ( flagsAdded || flagsRemoved ) { @@ -98,7 +98,7 @@ bool Merge::mergeItem( PimItem &newItem, PimItem ¤tItem, bool flagsChanged = false; const Flag::List flags = HandlerHelper::resolveFlags( itemFlags.added ); DataStore::self()->setItemsFlags( PimItem::List() << currentItem, flags, - &flagsChanged, true ); + &flagsChanged, col, true ); if ( flagsChanged ) { mChangedParts << AKONADI_PARAM_FLAGS; } diff --git a/server/src/handler/select.cpp b/server/src/handler/select.cpp index 1c5dd8a..f1ecc44 100644 --- a/server/src/handler/select.cpp +++ b/server/src/handler/select.cpp @@ -27,6 +27,7 @@ #include "handlerhelper.h" #include "imapstreamparser.h" #include "storage/selectquerybuilder.h" +#include "storage/collectionstatistics.h" #include "commandcontext.h" #include "response.h" @@ -96,19 +97,14 @@ bool Select::parseStream() response.setString( "FLAGS (" + Flag::joinByName( Flag::retrieveAll(), QLatin1String( " " ) ).toLatin1() + ")" ); Q_EMIT responseAvailable( response ); - const int itemCount = HandlerHelper::itemCount( col ); - if ( itemCount < 0 ) { + const CollectionStatistics::Statistics stats = CollectionStatistics::instance()->statistics(col); + if ( stats.count == -1 ) { return failureResponse( "Unable to determine item count" ); } - response.setString( QByteArray::number( itemCount ) + " EXISTS" ); + response.setString( QByteArray::number( stats.count ) + " EXISTS" ); Q_EMIT responseAvailable( response ); - int readCount = HandlerHelper::itemWithFlagsCount( col, QStringList() << QLatin1String( AKONADI_FLAG_SEEN ) - << QLatin1String( AKONADI_FLAG_IGNORED ) ); - if ( readCount < 0 || itemCount < readCount ) { - return failureResponse( "Unable to retrieve unseen count" ); - } - response.setString( "OK [UNSEEN " + QByteArray::number( itemCount - readCount ) + "] Message 0 is first unseen" ); + response.setString( "OK [UNSEEN " + QByteArray::number( stats.count - stats.read ) + "] Message 0 is first unseen" ); Q_EMIT responseAvailable( response ); } diff --git a/server/src/handler/status.cpp b/server/src/handler/status.cpp index 8c6823d..283532c 100644 --- a/server/src/handler/status.cpp +++ b/server/src/handler/status.cpp @@ -25,6 +25,7 @@ #include "storage/datastore.h" #include "storage/entity.h" #include "storage/countquerybuilder.h" +#include "storage/collectionstatistics.h" #include "response.h" #include "handlerhelper.h" @@ -62,9 +63,9 @@ bool Status::parseStream() // Responses: // REQUIRED untagged responses: STATUS - qint64 itemCount, itemSize; - if ( !HandlerHelper::itemStatistics( col, itemCount, itemSize ) ) { - return failureResponse( "Failed to query statistics." ); + const CollectionStatistics::Statistics &stats = CollectionStatistics::instance()->statistics(col); + if (stats.count == -1) { + return failureResponse( "Failed to query statistics." ); } // build STATUS response @@ -72,7 +73,7 @@ bool Status::parseStream() // MESSAGES - The number of messages in the mailbox if ( attributeList.contains( AKONADI_ATTRIBUTE_MESSAGES ) ) { statusResponse += AKONADI_ATTRIBUTE_MESSAGES " "; - statusResponse += QByteArray::number( itemCount ); + statusResponse += QByteArray::number( stats.count ); } if ( attributeList.contains( AKONADI_ATTRIBUTE_UNSEEN ) ) { @@ -80,21 +81,14 @@ bool Status::parseStream() statusResponse += " "; } statusResponse += AKONADI_ATTRIBUTE_UNSEEN " "; - - // itemWithFlagCount is twice as fast as itemWithoutFlagCount... - const int count = HandlerHelper::itemWithFlagsCount( col, QStringList() << QLatin1String( AKONADI_FLAG_SEEN ) - << QLatin1String( AKONADI_FLAG_IGNORED ) ); - if ( count < 0 ) { - return failureResponse( "Unable to retrieve unread count" ); - } - statusResponse += QByteArray::number( itemCount - count ); + statusResponse += QByteArray::number( stats.count - stats.read ); } if ( attributeList.contains( AKONADI_PARAM_SIZE ) ) { if ( !statusResponse.isEmpty() ) { statusResponse += " "; } statusResponse += AKONADI_PARAM_SIZE " "; - statusResponse += QByteArray::number( itemSize ); + statusResponse += QByteArray::number( stats.size ); } Response response; diff --git a/server/src/handlerhelper.cpp b/server/src/handlerhelper.cpp index 82347b4..39583ce 100644 --- a/server/src/handlerhelper.cpp +++ b/server/src/handlerhelper.cpp @@ -22,6 +22,7 @@ #include "storage/countquerybuilder.h" #include "storage/datastore.h" #include "storage/selectquerybuilder.h" +#include "storage/collectionstatistics.h" #include "storage/queryhelper.h" #include "libs/imapparser_p.h" #include "libs/protocol_p.h" @@ -78,74 +79,6 @@ QString HandlerHelper::pathForCollection( const Collection &col ) return parts.join( QLatin1String( "/" ) ); } -bool HandlerHelper::itemStatistics( const Collection &col, qint64 &count, qint64 &size ) -{ - QueryBuilder qb( PimItem::tableName() ); - qb.addAggregation( PimItem::idColumn(), QLatin1String( "count" ) ); - qb.addAggregation( PimItem::sizeColumn(), QLatin1String( "sum" ) ); - - if ( col.isVirtual() ) { - qb.addJoin( QueryBuilder::InnerJoin, CollectionPimItemRelation::tableName(), - CollectionPimItemRelation::rightFullColumnName(), PimItem::idFullColumnName() ); - qb.addValueCondition( CollectionPimItemRelation::leftFullColumnName(), Query::Equals, col.id() ); - } else { - qb.addValueCondition( PimItem::collectionIdColumn(), Query::Equals, col.id() ); - } - - if ( !qb.exec() ) { - return false; - } - if ( !qb.query().next() ) { - akError() << "Error during retrieving result of statistics query:" << qb.query().lastError().text(); - return false; - } - count = qb.query().value( 0 ).toLongLong(); - size = qb.query().value( 1 ).toLongLong(); - return true; -} - -int HandlerHelper::itemWithFlagsCount( const Collection &col, const QStringList &flags ) -{ - CountQueryBuilder qb( PimItem::tableName(), PimItem::idFullColumnName(), CountQueryBuilder::Distinct ); - qb.addJoin( QueryBuilder::InnerJoin, PimItemFlagRelation::tableName(), - PimItem::idFullColumnName(), PimItemFlagRelation::leftFullColumnName() ); - if ( col.isVirtual() ) { - qb.addJoin( QueryBuilder::InnerJoin, CollectionPimItemRelation::tableName(), - CollectionPimItemRelation::rightFullColumnName(), PimItem::idFullColumnName() ); - qb.addValueCondition( CollectionPimItemRelation::leftFullColumnName(), Query::Equals, col.id() ); - } else { - qb.addValueCondition( PimItem::collectionIdFullColumnName(), Query::Equals, col.id() ); - } - Query::Condition cond( Query::Or ); - // We use the below instead of an inner join in the query above because postgres seems - // to struggle to optimize the two inner joins, despite having indices that should - // facilitate that. This exploits the fact that the Flag::retrieveByName is fast because - // it hits an in-memory cache. - Q_FOREACH ( const QString &flag, flags ) { - const Flag f = Flag::retrieveByName( flag ); - if (!f.isValid()) { - // since we OR this condition, we can skip invalid flags to speed up the query - continue; - } - cond.addValueCondition( PimItemFlagRelation::rightFullColumnName(), Query::Equals, f.id() ); - } - qb.addCondition( cond ); - if ( !qb.exec() ) { - return -1; - } - return qb.result(); -} - -int HandlerHelper::itemCount( const Collection &col ) -{ - CountQueryBuilder qb( PimItem::tableName() ); - qb.addValueCondition( PimItem::collectionIdColumn(), Query::Equals, col.id() ); - if ( !qb.exec() ) { - return -1; - } - return qb.result(); -} - int HandlerHelper::parseCachePolicy( const QByteArray &data, Collection &col, int start, bool *changed ) { bool inheritChanged = false; @@ -233,14 +166,12 @@ QByteArray HandlerHelper::collectionToByteArray( const Collection &col, bool hid b += " " AKONADI_PARAM_VIRTUAL " " + QByteArray::number( col.isVirtual() ) + ' '; if ( includeStatistics ) { - qint64 itemCount, itemSize; - if ( itemStatistics( col, itemCount, itemSize ) ) { - b += AKONADI_ATTRIBUTE_MESSAGES " " + QByteArray::number( itemCount ) + ' '; - // itemWithFlagCount is twice as fast as itemWithoutFlagCount, so emulated that... + const CollectionStatistics::Statistics &stats = CollectionStatistics::instance()->statistics(col); + if (stats.count > -1) { + b += AKONADI_ATTRIBUTE_MESSAGES " " + QByteArray::number( stats.count ) + ' '; b += AKONADI_ATTRIBUTE_UNSEEN " "; - b += QByteArray::number( itemCount - itemWithFlagsCount( col, QStringList() << QLatin1String( AKONADI_FLAG_SEEN ) - << QLatin1String( AKONADI_FLAG_IGNORED ) ) ); - b += " " AKONADI_PARAM_SIZE " " + QByteArray::number( itemSize ) + ' '; + b += QByteArray::number( stats.count - stats.read) ; + b += " " AKONADI_PARAM_SIZE " " + QByteArray::number( stats.size ) + ' '; } } diff --git a/server/src/handlerhelper.h b/server/src/handlerhelper.h index 22e6e1c..cf9ac22 100644 --- a/server/src/handlerhelper.h +++ b/server/src/handlerhelper.h @@ -52,28 +52,6 @@ class HandlerHelper static QString pathForCollection( const Collection &col ); /** - Returns the amount of existing items in the given collection. - @return -1 on error - */ - static int itemCount( const Collection &col ); - - /** - * Queries for collection statistics. - * @param col The collection to query. - * @param count The total amount of items in this collection. - * @param size The size of all items in this collection. - * @return @c false on a query error, @c true otherwise - */ - static bool itemStatistics( const Collection &col, qint64 &count, qint64 &size ); - - /** - Returns the amount of existing items in the given collection - which have a given flag set. - @return -1 on error. - */ - static int itemWithFlagsCount( const Collection &col, const QStringList &flags ); - - /** Parse cache policy and update the given Collection object accoordingly. @param changed Indicates whether or not the cache policy already available in @p col has actually changed diff --git a/server/src/storage/collectionstatistics.cpp b/server/src/storage/collectionstatistics.cpp new file mode 100644 index 0000000..85ee449 --- /dev/null +++ b/server/src/storage/collectionstatistics.cpp @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2014 Daniel Vrátil + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "collectionstatistics.h" +#include "querybuilder.h" +#include "countquerybuilder.h" +#include "akdebug.h" +#include "entities.h" + +#include + +#include + +using namespace Akonadi::Server; + +CollectionStatistics *CollectionStatistics::sInstance = 0; + +CollectionStatistics* CollectionStatistics::instance() +{ + static QMutex lock; + lock.lock(); + if (sInstance == 0) { + sInstance = new CollectionStatistics(); + } + lock.unlock(); + return sInstance; +} + +void CollectionStatistics::invalidateCollection(const Collection &col) +{ + QMutexLocker lock(&mCacheLock); + mCache.remove(col.id()); +} + +const CollectionStatistics::Statistics& CollectionStatistics::statistics(const Collection &col) +{ + QMutexLocker lock(&mCacheLock); + auto it = mCache.find(col.id()); + if (it == mCache.constEnd()) { + it = mCache.insert(col.id(), getCollectionStatistics(col)); + } + return it.value(); +} + +CollectionStatistics::Statistics CollectionStatistics::getCollectionStatistics(const Collection &col) +{ + QueryBuilder qb(PimItem::tableName()); + // COUNT(DISTINCT PimItemTable.id) + qb.addAggregation(QString::fromLatin1("DISTINCT %1") + .arg(PimItem::idFullColumnName()), + QLatin1String("count")); + // SUM(PimItemTable.size) + qb.addAggregation(PimItem::sizeFullColumnName(), QLatin1String("sum")); + // SUM(CASE WHEN FlagTable.name IN ('\SEEN', '$IGNORED') THEN 1 ELSE 0 END) + // This allows us to get read messages count in a single query with the other + // statistics. It is much than doing two queries, because the database + // only has to calculate the JOINs once. + // + // Flag::retrieveByName() will hit the Entity cache, which allows us to avoid + // a second JOIN with FlagTable, which PostgreSQL seems to struggle to optimize. + Query::Condition cond(Query::Or); + cond.addValueCondition(PimItemFlagRelation::rightFullColumnName(), + Query::Equals, + Flag::retrieveByName(QLatin1String(AKONADI_FLAG_SEEN)).id()); + cond.addValueCondition(PimItemFlagRelation::rightFullColumnName(), + Query::Equals, + Flag::retrieveByName(QLatin1String(AKONADI_FLAG_IGNORED)).id()); + Query::Case caseStmt(cond, QLatin1String("1"), QLatin1String("0")); + qb.addAggregation(caseStmt, QLatin1String("sum")); + + qb.addJoin(QueryBuilder::LeftJoin, PimItemFlagRelation::tableName(), + PimItem::idFullColumnName(), PimItemFlagRelation::leftFullColumnName()); + if (col.isVirtual()) { + qb.addJoin(QueryBuilder::InnerJoin, CollectionPimItemRelation::tableName(), + CollectionPimItemRelation::rightFullColumnName(), PimItem::idFullColumnName()); + qb.addValueCondition(CollectionPimItemRelation::leftFullColumnName(), Query::Equals, col.id()); + } else { + qb.addValueCondition(PimItem::collectionIdColumn(), Query::Equals, col.id()); + } + + if (!qb.exec()) { + return { -1, -1, -1 }; + } + if (!qb.query().next()) { + akError() << "Error during retrieving result of statistics query:" << qb.query().lastError().text(); + return { -1, -1, -1 }; + } + + return { qb.query().value(0).toLongLong(), + qb.query().value(1).toLongLong(), + qb.query().value(2).toLongLong() }; +} diff --git a/server/src/storage/collectionstatistics.h b/server/src/storage/collectionstatistics.h new file mode 100644 index 0000000..2c0af6a --- /dev/null +++ b/server/src/storage/collectionstatistics.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2014 Daniel Vrátil + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef AKONADI_SERVER_COLLECTIONSTATISTICS_H +#define AKONADI_SERVER_COLLECTIONSTATISTICS_H + +class QMutex; + +#include +#include + +namespace Akonadi { +namespace Server { + +class Collection; + +/** + * Provides cache for collection statistics + * + * Collection statistics are requested very often, so to take some load from the + * database we cache the results until the statistics are invalidated (see + * NotificationCollector, which takes care for invalidating the statistics). + * + * The cache (together with optimization of the actual SQL query) seems to + * massively improve initial folder listing on system start (when IO and CPU loads + * are very high). + */ +class CollectionStatistics +{ +public: + struct Statistics { + qint64 count; + qint64 size; + qint64 read; + }; + + static CollectionStatistics* instance(); + + const Statistics& statistics(const Collection &col); + void invalidateCollection(const Collection &col); + +private: + Statistics getCollectionStatistics(const Collection &col); + + QMutex mCacheLock; + QHash mCache; + + static CollectionStatistics *sInstance; +}; + +} // namespace Server +} // namespace Akonadi + +#endif // AKONADI_SERVER_COLLECTIONSTATISTICS_H diff --git a/server/src/storage/datastore.cpp b/server/src/storage/datastore.cpp index 304f0e8..0983d84 100644 --- a/server/src/storage/datastore.cpp +++ b/server/src/storage/datastore.cpp @@ -209,7 +209,7 @@ DataStore *DataStore::self() /* --- ItemFlags ----------------------------------------------------- */ bool DataStore::setItemsFlags( const PimItem::List &items, const QVector &flags, - bool *flagsChanged, bool silent ) + bool *flagsChanged, const Collection &col, bool silent ) { QSet removedFlags; QSet addedFlags; @@ -258,7 +258,7 @@ bool DataStore::setItemsFlags( const PimItem::List &items, const QVector & } if ( !silent && ( !addedFlags.isEmpty() || !removedFlags.isEmpty() ) ) { - mNotificationCollector->itemsFlagsChanged( items, addedFlags, removedFlags ); + mNotificationCollector->itemsFlagsChanged( items, addedFlags, removedFlags, col ); } setBoolPtr( flagsChanged, ( addedFlags != removedFlags ) ); @@ -361,7 +361,7 @@ bool DataStore::appendItemsFlags( const PimItem::List &items, const QVector &flags, - bool *flagsChanged, bool silent ) + bool *flagsChanged, const Collection &col, bool silent ) { QSet removedFlags; QVariantList itemsIds; @@ -393,7 +393,7 @@ bool DataStore::removeItemsFlags( const PimItem::List &items, const QVectoritemsFlagsChanged( items, QSet(), removedFlags ); + mNotificationCollector->itemsFlagsChanged( items, QSet(), removedFlags, col ); } } diff --git a/server/src/storage/datastore.h b/server/src/storage/datastore.h index 395b227..a2d8a42 100644 --- a/server/src/storage/datastore.h +++ b/server/src/storage/datastore.h @@ -119,10 +119,12 @@ class DataStore : public QObject static DataStore *self(); /* --- ItemFlags ----------------------------------------------------- */ - virtual bool setItemsFlags( const PimItem::List &items, const QVector &flags, bool *flagsChanged = 0, bool silent = false ); + virtual bool setItemsFlags( const PimItem::List &items, const QVector &flags, + bool *flagsChanged = 0, const Collection &col = Collection(), bool silent = false ); virtual bool appendItemsFlags( const PimItem::List &items, const QVector &flags, bool *flagsChanged = 0, bool checkIfExists = true, const Collection &col = Collection(), bool silent = false ); - virtual bool removeItemsFlags( const PimItem::List &items, const QVector &flags, bool *tagsChanged = 0, bool silent = false ); + virtual bool removeItemsFlags( const PimItem::List &items, const QVector &flags, bool *tagsChanged = 0, + const Collection &collection = Collection(), bool silent = false ); /* --- ItemTags ----------------------------------------------------- */ virtual bool setItemsTags( const PimItem::List &items, const Tag::List &tags, bool *tagsChanged = 0, bool silent = false ); diff --git a/server/src/storage/notificationcollector.cpp b/server/src/storage/notificationcollector.cpp index 67f57d1..dbc7883 100644 --- a/server/src/storage/notificationcollector.cpp +++ b/server/src/storage/notificationcollector.cpp @@ -20,6 +20,7 @@ #include "notificationcollector.h" #include "storage/datastore.h" #include "storage/entity.h" +#include "storage/collectionstatistics.h" #include "handlerhelper.h" #include "cachecleaner.h" #include "intervalcheck.h" @@ -133,6 +134,7 @@ void NotificationCollector::collectionChanged( const Collection &collection, if ( AkonadiServer::instance()->intervalChecker() ) { AkonadiServer::instance()->intervalChecker()->collectionAdded( collection.id() ); } + CollectionStatistics::instance()->invalidateCollection(collection); collectionNotification( NotificationMessageV2::Modify, collection, collection.parentId(), -1, resource, changes.toSet() ); } @@ -159,6 +161,8 @@ void NotificationCollector::collectionRemoved( const Collection &collection, if ( AkonadiServer::instance()->intervalChecker() ) { AkonadiServer::instance()->intervalChecker()->collectionRemoved( collection.id() ); } + CollectionStatistics::instance()->invalidateCollection(collection); + collectionNotification( NotificationMessageV2::Remove, collection, collection.parentId(), -1, resource ); } @@ -183,6 +187,8 @@ void NotificationCollector::collectionUnsubscribed( const Collection &collection if ( AkonadiServer::instance()->intervalChecker() ) { AkonadiServer::instance()->intervalChecker()->collectionRemoved( collection.id() ); } + CollectionStatistics::instance()->invalidateCollection(collection); + collectionNotification( NotificationMessageV2::Unsubscribe, collection, collection.parentId(), -1, resource, QSet() ); } @@ -282,6 +288,7 @@ void NotificationCollector::itemNotification( NotificationMessageV2::Operation o copy.setParentCollection( iter.key() ); copy.setResource( resource ); + CollectionStatistics::instance()->invalidateCollection(Collection::retrieveById(iter.key())); dispatchNotification( copy ); } @@ -304,6 +311,7 @@ void NotificationCollector::itemNotification( NotificationMessageV2::Operation o } msg.setResource( res ); + CollectionStatistics::instance()->invalidateCollection(col); dispatchNotification( msg ); } diff --git a/server/tests/unittest/fakedatastore.cpp b/server/tests/unittest/fakedatastore.cpp index 12214fa..43ef7e6 100644 --- a/server/tests/unittest/fakedatastore.cpp +++ b/server/tests/unittest/fakedatastore.cpp @@ -91,13 +91,15 @@ bool FakeDataStore::init() bool FakeDataStore::setItemsFlags( const PimItem::List &items, const QVector &flags, bool *flagsChanged, + const Collection &col, bool silent ) { mChanges.insert( QLatin1String( "setItemsFlags" ), QVariantList() << QVariant::fromValue( items ) << QVariant::fromValue( flags ) + << QVariant::fromValue( col ) << silent ); - return DataStore::setItemsFlags( items, flags, flagsChanged, silent ); + return DataStore::setItemsFlags( items, flags, flagsChanged, col, silent ); } bool FakeDataStore::appendItemsFlags( const PimItem::List &items, @@ -119,13 +121,15 @@ bool FakeDataStore::appendItemsFlags( const PimItem::List &items, bool FakeDataStore::removeItemsFlags( const PimItem::List &items, const QVector &flags, bool *flagsChanged, + const Collection &col, bool silent ) { mChanges.insert( QLatin1String( "removeItemsFlags" ), QVariantList() << QVariant::fromValue( items ) << QVariant::fromValue( flags ) + << QVariant::fromValue( col ) << silent ); - return DataStore::removeItemsFlags( items, flags, flagsChanged, silent ); + return DataStore::removeItemsFlags( items, flags, flagsChanged, col, silent ); } diff --git a/server/tests/unittest/fakedatastore.h b/server/tests/unittest/fakedatastore.h index 62c5b75..cd9ab13 100644 --- a/server/tests/unittest/fakedatastore.h +++ b/server/tests/unittest/fakedatastore.h @@ -41,6 +41,7 @@ class FakeDataStore: public DataStore virtual bool setItemsFlags( const PimItem::List &items, const QVector &flags, bool *flagsChanged = 0, + const Collection &col = Collection(), bool silent = false ); virtual bool appendItemsFlags( const PimItem::List &items, const QVector &flags, @@ -51,6 +52,7 @@ class FakeDataStore: public DataStore virtual bool removeItemsFlags( const PimItem::List &items, const QVector &flags, bool *flagsChanged = 0, + const Collection &col = Collection(), bool silent = false ); virtual bool setItemsTags( const PimItem::List &items, -- 2.1.0