From c24329bb570ee16c033228588e6d22b0f6000f95 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Dan=20Vr=C3=A1til?= <dvratil@redhat.com>
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 <storage/collectionqueryhelper.h>
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 <dvratil@redhat.com>
+ *
+ * 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 <libs/protocol_p.h>
+
+#include <QDateTime>
+
+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 <dvratil@redhat.com>
+ *
+ * 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 <QHash>
+#include <QMutex>
+
+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<qint64, Statistics> 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<Flag> &flags,
- bool *flagsChanged, bool silent )
+ bool *flagsChanged, const Collection &col, bool silent )
{
QSet<QByteArray> removedFlags;
QSet<QByteArray> addedFlags;
@@ -258,7 +258,7 @@ bool DataStore::setItemsFlags( const PimItem::List &items, const QVector<Flag> &
}
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<Flag
}
bool DataStore::removeItemsFlags( const PimItem::List &items, const QVector<Flag> &flags,
- bool *flagsChanged, bool silent )
+ bool *flagsChanged, const Collection &col, bool silent )
{
QSet<QByteArray> removedFlags;
QVariantList itemsIds;
@@ -393,7 +393,7 @@ bool DataStore::removeItemsFlags( const PimItem::List &items, const QVector<Flag
if ( qb.query().numRowsAffected() != 0 ) {
setBoolPtr( flagsChanged, true );
if ( !silent ) {
- mNotificationCollector->itemsFlagsChanged( items, QSet<QByteArray>(), removedFlags );
+ mNotificationCollector->itemsFlagsChanged( items, QSet<QByteArray>(), 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<Flag> &flags, bool *flagsChanged = 0, bool silent = false );
+ virtual bool setItemsFlags( const PimItem::List &items, const QVector<Flag> &flags,
+ bool *flagsChanged = 0, const Collection &col = Collection(), bool silent = false );
virtual bool appendItemsFlags( const PimItem::List &items, const QVector<Flag> &flags, bool *flagsChanged = 0,
bool checkIfExists = true, const Collection &col = Collection(), bool silent = false );
- virtual bool removeItemsFlags( const PimItem::List &items, const QVector<Flag> &flags, bool *tagsChanged = 0, bool silent = false );
+ virtual bool removeItemsFlags( const PimItem::List &items, const QVector<Flag> &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<QByteArray>() );
}
@@ -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<Flag> &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<Flag> &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<Flag> &flags,
bool *flagsChanged = 0,
+ const Collection &col = Collection(),
bool silent = false );
virtual bool appendItemsFlags( const PimItem::List &items,
const QVector<Flag> &flags,
@@ -51,6 +52,7 @@ class FakeDataStore: public DataStore
virtual bool removeItemsFlags( const PimItem::List &items,
const QVector<Flag> &flags,
bool *flagsChanged = 0,
+ const Collection &col = Collection(),
bool silent = false );
virtual bool setItemsTags( const PimItem::List &items,
--
2.1.0