diff --git a/autotests/server/CMakeLists.txt b/autotests/server/CMakeLists.txt index 2f9e20c3f..a855fd063 100644 --- a/autotests/server/CMakeLists.txt +++ b/autotests/server/CMakeLists.txt @@ -1,107 +1,109 @@ ########### next target ############### # QTEST_MAIN is using QApplication when QT_GUI_LIB is defined remove_definitions(-DQT_GUI_LIB) set(EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR}) include_directories(${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_BINARY_DIR}/src/server ${Akonadi_SOURCE_DIR}/src/server) akonadi_run_xsltproc( XSL ${Akonadi_SOURCE_DIR}/src/server/storage/schema.xsl XML ${CMAKE_CURRENT_SOURCE_DIR}/dbtest_data/unittest_schema.xml BASENAME unittestschema CLASSNAME UnitTestSchema ) akonadi_run_xsltproc( XSL ${CMAKE_CURRENT_SOURCE_DIR}/dbpopulator.xsl XML ${CMAKE_CURRENT_SOURCE_DIR}/dbtest_data/dbdata.xml BASENAME dbpopulator ) set_property(SOURCE ${CMAKE_CURRENT_BINARY_DIR}/dbpopulator.cpp PROPERTY SKIP_AUTOMOC TRUE) set(common_SRCS unittestschema.cpp fakeconnection.cpp + fakecollectionscheduler.cpp fakedatastore.cpp fakeclient.cpp fakeakonadiserver.cpp fakesearchmanager.cpp fakeitemretrievalmanager.cpp dbinitializer.cpp inspectablenotificationcollector.cpp ${CMAKE_CURRENT_BINARY_DIR}/dbpopulator.cpp ) add_library(akonadi_unittest_common STATIC ${common_SRCS}) target_link_libraries(akonadi_unittest_common KF5AkonadiPrivate libakonadiserver Qt5::Core Qt5::DBus Qt5::Test Qt5::Sql Qt5::Network ) macro(add_server_test _source) set(_test ${_source} ../../src/server/akonadiserver_debug.cpp ../../src/server/akonadiserver_search_debug.cpp) get_filename_component(_name ${_source} NAME_WE) qt5_add_resources(_test dbtest_data/dbtest_data.qrc) add_executable(${_name} ${_test}) add_test(NAME AkonadiServer-${_name} COMMAND ${_name}) if (ENABLE_ASAN) set_tests_properties(AkonadiServer-${_name} PROPERTIES ENVIRONMENT ASAN_OPTIONS=symbolize=1 ) endif() set_tests_properties(AkonadiServer-${_name} PROPERTIES ENVIRONMENT "QT_HASH_SEED=0;QT_NO_CPU_FEATURE=sse4.2" ) target_link_libraries(${_name} akonadi_shared akonadi_unittest_common libakonadiserver KF5AkonadiPrivate Qt5::Core Qt5::DBus Qt5::Test Qt5::Sql Qt5::Network ${CMAKE_SHARED_LINKER_FLAGS_ASAN} ) endmacro() add_server_test(dbdeadlockcatchertest.cpp) add_server_test(dbtypetest.cpp) add_server_test(dbintrospectortest.cpp) add_server_test(querybuildertest.cpp) add_server_test(dbinitializertest.cpp) add_server_test(dbupdatertest.cpp) add_server_test(handlertest.cpp) add_server_test(dbconfigtest.cpp) add_server_test(parthelpertest.cpp) add_server_test(itemretrievertest.cpp) add_server_test(notificationsubscribertest.cpp) add_server_test(notificationmanagertest.cpp) add_server_test(parttypehelpertest.cpp) add_server_test(collectionstatisticstest.cpp) add_server_test(aggregatedfetchscopetest.cpp) +add_server_test(collectionschedulertest.cpp) if (SQLITE_FOUND) # tests using the fake server need the QSQLITE3 plugin add_server_test(partstreamertest.cpp) add_server_test(itemcreatehandlertest.cpp) add_server_test(itemlinkhandlertest.cpp) add_server_test(itemmovehandlertest.cpp) add_server_test(collectioncreatehandlertest.cpp) add_server_test(collectionfetchhandlertest.cpp) add_server_test(collectionmodifyhandlertest.cpp) add_server_test(collectionreferencetest.cpp) add_server_test(searchtest.cpp akonadiprivate) add_server_test(relationhandlertest.cpp akonadiprivate) add_server_test(taghandlertest.cpp akonadiprivate) add_server_test(fetchhandlertest.cpp akonadiprivate) endif() diff --git a/autotests/server/collectionschedulertest.cpp b/autotests/server/collectionschedulertest.cpp new file mode 100644 index 000000000..b4353e900 --- /dev/null +++ b/autotests/server/collectionschedulertest.cpp @@ -0,0 +1,122 @@ +/* + Copyright (c) 2019 David Faure + + 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 + +#include +#include "fakeakonadiserver.h" +#include "fakecollectionscheduler.h" + +#include + +using namespace Akonadi; +using namespace Akonadi::Server; + +class CollectionSchedulerTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void initTestCase() + { + FakeAkonadiServer::instance()->init(); + } + + void shouldInitializeSyncIntervals() + { + // WHEN + FakeCollectionScheduler sched; + sched.waitForInit(); + const qint64 now = QDateTime::currentSecsSinceEpoch(); + // THEN + // Collections root (1), ColA (2), ColB (3), ColD (5), virtual (6) and virtual2 (7) + // should have a check scheduled in 5 minutes (default value) + for (qint64 collectionId : {1, 2, 3, 5, 6, 7}) { + QVERIFY(sched.nextScheduledTime(collectionId) > now + 4 * 60); + QVERIFY(sched.nextScheduledTime(collectionId) < now + 6 * 60); + } + QCOMPARE(sched.nextScheduledTime(4), 0); // ColC is skipped because syncPref=false + QCOMPARE(sched.nextScheduledTime(314), 0); // no such collection + } + + // (not that this feature is really used right now, it defaults to 5 and CacheCleaner sets it to 5) + void shouldObeyMinimumInterval() + { + // GIVEN + FakeCollectionScheduler sched; + // WHEN + sched.setMinimumInterval(10); + sched.waitForInit(); + // THEN + const qint64 now = QDateTime::currentSecsSinceEpoch(); + QTRY_VERIFY(sched.nextScheduledTime(2) > 0); + QVERIFY(sched.nextScheduledTime(2) > now + 9 * 60); + QVERIFY(sched.nextScheduledTime(2) < now + 11 * 60); + } + + void shouldRemoveAndAddCollectionFromSchedule() + { + // GIVEN + FakeCollectionScheduler sched; + sched.waitForInit(); + const auto timeForRoot = sched.nextScheduledTime(1); + const auto timeForColB = sched.nextScheduledTime(3); + QVERIFY(sched.nextScheduledTime(2) <= timeForColB); + // WHEN + sched.collectionRemoved(2); + // THEN + QTRY_COMPARE(sched.nextScheduledTime(2), 0); + QCOMPARE(sched.nextScheduledTime(1), timeForRoot); // unchanged + QCOMPARE(sched.nextScheduledTime(3), timeForColB); // unchanged + + // AND WHEN re-adding the collection + QTest::qWait(1000); // we only have precision to the second... + sched.collectionAdded(2); + // THEN + QTRY_VERIFY(sched.nextScheduledTime(2) > 0); + // This is unchanged, even though it would normally have been 1s later. See "minor optimization" in scheduler. + QCOMPARE(sched.nextScheduledTime(2), timeForColB); + QCOMPARE(sched.nextScheduledTime(1), timeForRoot); // unchanged + QCOMPARE(sched.nextScheduledTime(3), timeForColB); // unchanged + } + + void shouldHonourIntervalChange() + { + // GIVEN + FakeCollectionScheduler sched; + sched.waitForInit(); + const auto timeForColB = sched.nextScheduledTime(3); + Collection colA = Collection::retrieveByName(QStringLiteral("Collection A")); + QCOMPARE(colA.id(), 2); + QVERIFY(sched.nextScheduledTime(2) <= timeForColB); + // WHEN + colA.setCachePolicyInherit(false); + colA.setCachePolicyCheckInterval(20); // in minutes + QVERIFY(colA.update()); + sched.collectionChanged(2); + // THEN + // "in 20 minutes" is 15 minutes later than "in 5 minutes" + QTRY_VERIFY(sched.nextScheduledTime(2) >= timeForColB + 14 * 60); + QVERIFY(sched.nextScheduledTime(2) <= timeForColB + 16 * 60); + } +}; + +AKTEST_FAKESERVER_MAIN(CollectionSchedulerTest) + +#include "collectionschedulertest.moc" diff --git a/autotests/server/fakecollectionscheduler.cpp b/autotests/server/fakecollectionscheduler.cpp new file mode 100644 index 000000000..47d1f43bd --- /dev/null +++ b/autotests/server/fakecollectionscheduler.cpp @@ -0,0 +1,55 @@ +/* + Copyright (c) 2019 David Faure + + 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 "fakecollectionscheduler.h" + +using namespace Akonadi::Server; + +FakeCollectionScheduler::FakeCollectionScheduler(QObject *parent) + : CollectionScheduler(QStringLiteral("FakeCollectionScheduler"), QThread::NormalPriority, parent) +{ +} + +void FakeCollectionScheduler::waitForInit() +{ + m_initCalled.acquire(); +} + +void FakeCollectionScheduler::init() +{ + CollectionScheduler::init(); + m_initCalled.release(); +} + +bool FakeCollectionScheduler::hasChanged(const Collection &collection, const Collection &changed) +{ + Q_ASSERT(collection.id() == changed.id()); + return collection.cachePolicyCheckInterval() != changed.cachePolicyCheckInterval(); +} + +int FakeCollectionScheduler::collectionScheduleInterval(const Collection &collection) +{ + return collection.cachePolicyCheckInterval(); +} + +void FakeCollectionScheduler::collectionExpired(const Collection &collection) +{ + Q_UNUSED(collection); + // Nothing here. The granularity is in whole minutes, we don't have time to wait for that in a unittest. +} diff --git a/autotests/server/fakecollectionscheduler.h b/autotests/server/fakecollectionscheduler.h new file mode 100644 index 000000000..92698d512 --- /dev/null +++ b/autotests/server/fakecollectionscheduler.h @@ -0,0 +1,52 @@ +/* + Copyright (c) 2019 David Faure + + 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 FAKECOLLECTIONSCHEDULER_H +#define FAKECOLLECTIONSCHEDULER_H + +#include "collectionscheduler.h" + +#include + +namespace Akonadi { +namespace Server { + +class FakeCollectionScheduler : public CollectionScheduler +{ + Q_OBJECT +public: + FakeCollectionScheduler(QObject *parent = nullptr); + void waitForInit(); + +protected: + void init() override; + + bool shouldScheduleCollection(const Collection &) override { return true; } + bool hasChanged(const Collection &collection, const Collection &changed) override; + int collectionScheduleInterval(const Collection &collection) override; + void collectionExpired(const Collection &collection) override; + +private: + QSemaphore m_initCalled; +}; + +} // namespace Server +} // namespace Akonadi + +#endif diff --git a/src/server/collectionscheduler.cpp b/src/server/collectionscheduler.cpp index 2c4592107..2003ae61b 100644 --- a/src/server/collectionscheduler.cpp +++ b/src/server/collectionscheduler.cpp @@ -1,315 +1,340 @@ /* 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 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 "collectionscheduler.h" #include "storage/datastore.h" #include "storage/selectquerybuilder.h" #include "akonadiserver_debug.h" #include #include #include namespace Akonadi { namespace Server { /** * @warning: QTimer's methods are not virtual, so it's necessary to always call * methods on pointer to PauseableTimer! */ class PauseableTimer : public QTimer { Q_OBJECT public: PauseableTimer(QObject *parent = nullptr) : QTimer(parent) { } void start(int interval) { - mStarted = QDateTime::currentDateTime(); + mStarted = QDateTime::currentDateTimeUtc(); mPaused = QDateTime(); setInterval(interval); QTimer::start(interval); } void start() { start(interval()); } void stop() { mStarted = QDateTime(); mPaused = QDateTime(); QTimer::stop(); } Q_INVOKABLE void pause() { if (!isActive() || isPaused()) { return; } - mPaused = QDateTime::currentDateTime(); + mPaused = QDateTime::currentDateTimeUtc(); QTimer::stop(); } Q_INVOKABLE void resume() { if (!isPaused()) { return; } const int remainder = interval() - (mStarted.secsTo(mPaused) * 1000); start(qMax(0, remainder)); mPaused = QDateTime(); // Update mStarted so that pause() can be called repeatedly - mStarted = QDateTime::currentDateTime(); + mStarted = QDateTime::currentDateTimeUtc(); } bool isPaused() const { return mPaused.isValid(); } private: QDateTime mStarted; QDateTime mPaused; }; } // namespace Server } // namespace Akonadi using namespace Akonadi::Server; CollectionScheduler::CollectionScheduler(const QString &threadName, QThread::Priority priority, QObject *parent) : AkThread(threadName, priority, parent) - , mScheduler(nullptr) - , mMinInterval(5) { } CollectionScheduler::~CollectionScheduler() { } +// Called in secondary thread void CollectionScheduler::quit() { delete mScheduler; mScheduler = nullptr; AkThread::quit(); } void CollectionScheduler::inhibit(bool inhibit) { if (inhibit) { const bool success = QMetaObject::invokeMethod(mScheduler, &PauseableTimer::pause, Qt::QueuedConnection); Q_ASSERT(success); Q_UNUSED(success); } else { const bool success = QMetaObject::invokeMethod(mScheduler, &PauseableTimer::resume, Qt::QueuedConnection); Q_ASSERT(success); Q_UNUSED(success); } } int CollectionScheduler::minimumInterval() const { return mMinInterval; } +uint CollectionScheduler::nextScheduledTime(qint64 collectionId) const +{ + QMutexLocker locker(&mScheduleLock); + const auto i = constFind(collectionId); + if (i != mSchedule.cend()) { + return i.key(); + } + return 0; +} + void CollectionScheduler::setMinimumInterval(int intervalMinutes) { + // No mutex -- you can only call this before starting the thread mMinInterval = intervalMinutes; } void CollectionScheduler::collectionAdded(qint64 collectionId) { Collection collection = Collection::retrieveById(collectionId); DataStore::self()->activeCachePolicy(collection); if (shouldScheduleCollection(collection)) { QMetaObject::invokeMethod(this, [this, collection]() {scheduleCollection(collection);}, Qt::QueuedConnection); } } void CollectionScheduler::collectionChanged(qint64 collectionId) { QMutexLocker locker(&mScheduleLock); - for (const Collection &collection : qAsConst(mSchedule)) { - if (collection.id() == collectionId) { - Collection changed = Collection::retrieveById(collectionId); - DataStore::self()->activeCachePolicy(changed); - if (hasChanged(collection, changed)) { - if (shouldScheduleCollection(changed)) { - locker.unlock(); - // Scheduling the changed collection will automatically remove the old one - scheduleCollection(changed); - } else { - locker.unlock(); - // If the collection should no longer be scheduled then remove it - collectionRemoved(collectionId); - } + const auto it = constFind(collectionId); + if (it != mSchedule.cend()) { + const Collection oldCollection = it.value(); + Collection changed = Collection::retrieveById(collectionId); + DataStore::self()->activeCachePolicy(changed); + if (hasChanged(oldCollection, changed)) { + if (shouldScheduleCollection(changed)) { + locker.unlock(); + // Scheduling the changed collection will automatically remove the old one + QMetaObject::invokeMethod(this, [this, changed]() {scheduleCollection(changed);}, Qt::QueuedConnection); + } else { + locker.unlock(); + // If the collection should no longer be scheduled then remove it + collectionRemoved(collectionId); } - - return; } + } else { + // We don't know the collection yet, but maybe now it can be scheduled + collectionAdded(collectionId); } - - // We don't know the collection yet, but maybe now it can be scheduled - collectionAdded(collectionId); } void CollectionScheduler::collectionRemoved(qint64 collectionId) { QMutexLocker locker(&mScheduleLock); - for (const Collection &collection : qAsConst(mSchedule)) { - if (collection.id() == collectionId) { - const uint key = mSchedule.key(collection); - const bool reschedule = (key == mSchedule.constBegin().key()); - mSchedule.remove(key); - locker.unlock(); - - // If we just remove currently scheduled collection, schedule the next one - if (reschedule) { - startScheduler(); - } - - return; + auto it = find(collectionId); + if (it != mSchedule.end()) { + const bool reschedule = it == mSchedule.begin(); + mSchedule.erase(it); + + // If we just remove currently scheduled collection, schedule the next one + if (reschedule) { + QMetaObject::invokeMethod(this, &CollectionScheduler::startScheduler, Qt::QueuedConnection); } } } +// Called in secondary thread void CollectionScheduler::startScheduler() { + QMutexLocker locker(&mScheduleLock); // Don't restart timer if we are paused. if (mScheduler->isPaused()) { return; } - QMutexLocker locker(&mScheduleLock); if (mSchedule.isEmpty()) { // Stop the timer. It will be started again once some collection is scheduled mScheduler->stop(); return; } // Get next collection to expire and start the timer const uint next = mSchedule.constBegin().key(); // cast next - now() to int, so that we get negative result when next is in the past mScheduler->start(qMax(0, (int)(next - QDateTime::currentDateTimeUtc().toTime_t()) * 1000)); } +// Called in secondary thread void CollectionScheduler::scheduleCollection(Collection collection, bool shouldStartScheduler) { QMutexLocker locker(&mScheduleLock); - auto i = std::find(mSchedule.cbegin(), mSchedule.cend(), collection); - if (i != mSchedule.cend()) { - mSchedule.remove(i.key(), i.value()); + auto i = find(collection.id()); + if (i != mSchedule.end()) { + mSchedule.erase(i); } DataStore::self()->activeCachePolicy(collection); if (!shouldScheduleCollection(collection)) { return; } const int expireMinutes = qMax(mMinInterval, collectionScheduleInterval(collection)); + // TODO: port to qint64 and toSecsSinceEpoch uint nextCheck = QDateTime::currentDateTimeUtc().toTime_t() + (expireMinutes * 60); // Check whether there's another check scheduled within a minute after this one. // If yes, then delay this check so that it's scheduled together with the others // This is a minor optimization to reduce wakeups and SQL queries - QMap::iterator it = mSchedule.lowerBound(nextCheck); - if (it != mSchedule.end() && it.key() - nextCheck < 60) { + auto it = constLowerBound(nextCheck); + if (it != mSchedule.cend() && it.key() - nextCheck < 60) { nextCheck = it.key(); // Also check whether there's another checked scheduled within a minute before // this one. - } else if (it != mSchedule.begin()) { + } else if (it != mSchedule.cbegin()) { --it; if (nextCheck - it.key() < 60) { nextCheck = it.key(); } } mSchedule.insert(nextCheck, collection); if (shouldStartScheduler && !mScheduler->isActive()) { locker.unlock(); startScheduler(); } } +CollectionScheduler::ScheduleMap::const_iterator CollectionScheduler::constFind(qint64 collectionId) const +{ + return std::find_if(mSchedule.cbegin(), mSchedule.cend(), [collectionId](const Collection &c) { return c.id() == collectionId; }); +} + +CollectionScheduler::ScheduleMap::iterator CollectionScheduler::find(qint64 collectionId) +{ + return std::find_if(mSchedule.begin(), mSchedule.end(), [collectionId](const Collection &c) { return c.id() == collectionId; }); +} + +// separate method so we call the const version of QMap::lowerBound +CollectionScheduler::ScheduleMap::const_iterator CollectionScheduler::constLowerBound(qint64 collectionId) const +{ + return mSchedule.lowerBound(collectionId); +} + +// Called in secondary thread void CollectionScheduler::init() { AkThread::init(); mScheduler = new PauseableTimer(); mScheduler->setSingleShot(true); connect(mScheduler, &QTimer::timeout, this, &CollectionScheduler::schedulerTimeout); // Only retrieve enabled collections and referenced collections, we don't care // about anything else SelectQueryBuilder qb; Query::Condition orCondition(Query::Or); orCondition.addValueCondition(Collection::syncPrefFullColumnName(), Query::Equals, (int)Akonadi::Tristate::True); Query::Condition andCondition(Query::And); andCondition.addValueCondition(Collection::syncPrefFullColumnName(), Query::Equals, (int)Akonadi::Tristate::Undefined); andCondition.addValueCondition(Collection::enabledFullColumnName(), Query::Equals, true); orCondition.addCondition(andCondition); orCondition.addValueCondition(Collection::referencedFullColumnName(), Query::Equals, true); qb.addCondition(orCondition); if (!qb.exec()) { qCWarning(AKONADISERVER_LOG) << "Failed to query initial collections for scheduler!"; qCWarning(AKONADISERVER_LOG) << "Not a fatal error, no collections will be scheduled for sync or cache expiration!"; } const Collection::List collections = qb.result(); for (const Collection &collection : collections) { scheduleCollection(collection); } startScheduler(); } +// Called in secondary thread void CollectionScheduler::schedulerTimeout() { + QMutexLocker locker(&mScheduleLock); + // Call stop() explicitly to reset the timer mScheduler->stop(); - mScheduleLock.lock(); const uint timestamp = mSchedule.constBegin().key(); const QList collections = mSchedule.values(timestamp); mSchedule.remove(timestamp); - mScheduleLock.unlock(); + locker.unlock(); for (const Collection &collection : collections) { collectionExpired(collection); scheduleCollection(collection, false); } startScheduler(); } #include "collectionscheduler.moc" diff --git a/src/server/collectionscheduler.h b/src/server/collectionscheduler.h index 73083a924..83f340dc3 100644 --- a/src/server/collectionscheduler.h +++ b/src/server/collectionscheduler.h @@ -1,89 +1,105 @@ /* 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 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_SERVER_COLLECTIONSCHEDULER_H #define AKONADI_SERVER_COLLECTIONSCHEDULER_H #include #include #include #include "entities.h" #include "akthread.h" namespace Akonadi { namespace Server { class Collection; class PauseableTimer; class CollectionScheduler : public AkThread { Q_OBJECT public: explicit CollectionScheduler(const QString &threadName, QThread::Priority priority, QObject *parent = nullptr); ~CollectionScheduler() override; void collectionChanged(qint64 collectionId); void collectionRemoved(qint64 collectionId); void collectionAdded(qint64 collectionId); /** * Sets the minimum timeout interval. * * Default value is 5. * * @p intervalMinutes Minimum timeout interval in minutes. */ void setMinimumInterval(int intervalMinutes); Q_REQUIRED_RESULT int minimumInterval() const; + /** + * @return the timestamp (in seconds since epoch) when collectionExpired + * will next be called on the given collection, or 0 if we don't know about the collection. + * Only used by the unittest. + */ + uint nextScheduledTime(qint64 collectionId) const; + protected: void init() override; void quit() override; virtual bool shouldScheduleCollection(const Collection &collection) = 0; virtual bool hasChanged(const Collection &collection, const Collection &changed) = 0; /** * @return Return cache timeout in minutes */ virtual int collectionScheduleInterval(const Collection &collection) = 0; + /** + * Called when it's time to do something on that collection. + * Notice: this method is called in the secondary thread + */ virtual void collectionExpired(const Collection &collection) = 0; void inhibit(bool inhibit = true); -protected Q_SLOTS: +private Q_SLOTS: void schedulerTimeout(); void startScheduler(); void scheduleCollection(/*sic!*/ Collection collection, bool shouldStartScheduler = true); -protected: - QMutex mScheduleLock; - QMultiMap mSchedule; +private: + using ScheduleMap = QMultiMap; + ScheduleMap::const_iterator constFind(qint64 collectionId) const; + ScheduleMap::iterator find(qint64 collectionId); + ScheduleMap::const_iterator constLowerBound(qint64 collectionId) const; + + mutable QMutex mScheduleLock; + ScheduleMap mSchedule; PauseableTimer *mScheduler = nullptr; - int mMinInterval; + int mMinInterval = 5; }; } // namespace Server } // namespace Akonadi #endif // AKONADI_SERVER_COLLECTIONSCHEDULER_H