diff --git a/akonadi/resourcescheduler.cpp b/akonadi/resourcescheduler.cpp index fc585e219..b9cf1cc42 100644 --- a/akonadi/resourcescheduler.cpp +++ b/akonadi/resourcescheduler.cpp @@ -1,259 +1,262 @@ /* 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_p.h" #include #include #include #include using namespace Akonadi; qint64 ResourceScheduler::Task::latestSerial = 0; static QDBusAbstractInterface *s_resourcetracker = 0; //@cond PRIVATE ResourceScheduler::ResourceScheduler( QObject *parent ) : QObject( parent ), mOnline( false ) { } void ResourceScheduler::scheduleFullSync() { Task t; t.type = SyncAll; if ( !mTaskList.isEmpty() && ( mTaskList.last() == t || mCurrentTask == t ) ) return; mTaskList << t; signalTaskToTracker( t, "SyncAll" ); scheduleNext(); } void ResourceScheduler::scheduleCollectionTreeSync() { Task t; t.type = SyncCollectionTree; if ( !mTaskList.isEmpty() && ( mTaskList.last() == t || mCurrentTask == t ) ) return; mTaskList << t; signalTaskToTracker( t, "SyncCollectionTree" ); scheduleNext(); } void ResourceScheduler::scheduleSync(const Collection & col) { Task t; t.type = SyncCollection; t.collection = col; if ( !mTaskList.isEmpty() && ( mTaskList.last() == t || mCurrentTask == t ) ) return; mTaskList << t; signalTaskToTracker( t, "SyncCollection" ); scheduleNext(); } void ResourceScheduler::scheduleItemFetch(const Item & item, const QSet &parts, const QDBusMessage & msg) { Task t; t.type = FetchItem; t.item = item; t.itemParts = parts; t.dbusMsg = msg; if ( !mTaskList.isEmpty() && ( mTaskList.last() == t || mCurrentTask == t ) ) return; mTaskList << t; signalTaskToTracker( t, "FetchItem" ); scheduleNext(); } void ResourceScheduler::scheduleResourceCollectionDeletion() { Task t; t.type = DeleteResourceCollection; if ( !mTaskList.isEmpty() && ( mTaskList.last() == t || mCurrentTask == t ) ) return; mTaskList << t; signalTaskToTracker( t, "DeleteResourceCollection" ); scheduleNext(); } void ResourceScheduler::scheduleChangeReplay() { Task t; t.type = ChangeReplay; if ( mTaskList.contains( t ) ) return; - mTaskList << t; + // change replays have to happen before we pull changes from the backend, otherwise + // we will overwrite our still unsaved local changes if the backend can't do + // incremental retrieval + mTaskList.prepend( t ); signalTaskToTracker( t, "ChangeReplay" ); scheduleNext(); } void Akonadi::ResourceScheduler::scheduleFullSyncCompletion() { Task t; t.type = SyncAllDone; mTaskList << t; signalTaskToTracker( t, "SyncAllDone" ); scheduleNext(); } void ResourceScheduler::taskDone() { if ( isEmpty() ) emit status( AgentBase::Idle ); if ( s_resourcetracker ) { QList argumentList; argumentList << QString::number( mCurrentTask.serial ) << QString(); s_resourcetracker->asyncCallWithArgumentList(QLatin1String("jobEnded"), argumentList); } mCurrentTask = Task(); scheduleNext(); } void ResourceScheduler::deferTask() { if ( s_resourcetracker ) { QList argumentList; argumentList << QString::number( mCurrentTask.serial ) << QString(); s_resourcetracker->asyncCallWithArgumentList(QLatin1String("jobEnded"), argumentList); } Task t = mCurrentTask; mCurrentTask = Task(); mTaskList << t; signalTaskToTracker( t, "DeferedTask" ); scheduleNext(); } bool ResourceScheduler::isEmpty() { return mTaskList.isEmpty(); } void ResourceScheduler::scheduleNext() { if ( mCurrentTask.type != Invalid || mTaskList.isEmpty() || !mOnline ) return; QTimer::singleShot( 0, this, SLOT(executeNext()) ); } void ResourceScheduler::executeNext() { if( mCurrentTask.type != Invalid || mTaskList.isEmpty() ) return; mCurrentTask = mTaskList.takeFirst(); if ( s_resourcetracker ) { QList argumentList; argumentList << QString::number( mCurrentTask.serial ); s_resourcetracker->asyncCallWithArgumentList(QLatin1String("jobStarted"), argumentList); } switch ( mCurrentTask.type ) { case SyncAll: emit executeFullSync(); break; case SyncCollectionTree: emit executeCollectionTreeSync(); break; case SyncCollection: emit executeCollectionSync( mCurrentTask.collection ); break; case FetchItem: emit executeItemFetch( mCurrentTask.item, mCurrentTask.itemParts ); break; case DeleteResourceCollection: emit executeResourceCollectionDeletion(); break; case ChangeReplay: emit executeChangeReplay(); break; case SyncAllDone: emit fullSyncComplete(); 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(); } } void ResourceScheduler::signalTaskToTracker( const Task &task, const QByteArray &taskType ) { // if there's a job tracer running, tell it about the new job if ( !s_resourcetracker && QDBusConnection::sessionBus().interface()->isServiceRegistered(QLatin1String("org.kde.akonadiconsole") ) ) { s_resourcetracker = new QDBusInterface( QLatin1String("org.kde.akonadiconsole"), QLatin1String("/resourcesJobtracker"), QLatin1String("org.freedesktop.Akonadi.JobTracker"), QDBusConnection::sessionBus(), 0 ); } if ( s_resourcetracker ) { QList argumentList; argumentList << static_cast( parent() )->identifier() << QString::number( task.serial ) << QString() << QString::fromLatin1( taskType ); s_resourcetracker->asyncCallWithArgumentList(QLatin1String("jobCreated"), argumentList); } } void ResourceScheduler::collectionRemoved( const Akonadi::Collection &collection ) { if ( !collection.isValid() ) // should not happen, but you never know... return; for ( QList::iterator it = mTaskList.begin(); it != mTaskList.end(); ) { if ( (*it).type == SyncCollection && (*it).collection == collection ) { it = mTaskList.erase( it ); kDebug() << " erasing"; } else ++it; } } //@endcond #include "resourcescheduler_p.moc" diff --git a/akonadi/tests/resourceschedulertest.cpp b/akonadi/tests/resourceschedulertest.cpp index 7005c5a73..8de8dd2be 100644 --- a/akonadi/tests/resourceschedulertest.cpp +++ b/akonadi/tests/resourceschedulertest.cpp @@ -1,121 +1,121 @@ /* Copyright (c) 2009 Thomas McGuire 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 "resourceschedulertest.h" #include "../resourcescheduler_p.h" #include using namespace Akonadi; QTEST_KDEMAIN( ResourceSchedulerTest, NoGUI ) void ResourceSchedulerTest::testTaskComparision() { ResourceScheduler::Task t1; t1.type = ResourceScheduler::ChangeReplay; ResourceScheduler::Task t2; t2.type = ResourceScheduler::ChangeReplay; QCOMPARE( t1, t2 ); QList taskList; taskList.append( t1 ); QVERIFY( taskList.contains( t2 ) ); ResourceScheduler::Task t3; t3.type = ResourceScheduler::DeleteResourceCollection; QVERIFY( !( t2 == t3 ) ); QVERIFY( !taskList.contains( t3 ) ); } void ResourceSchedulerTest::testChangeReplaySchedule() { ResourceScheduler scheduler; scheduler.setOnline( true ); qRegisterMetaType("Akonadi::Collection"); QSignalSpy changeReplaySpy( &scheduler, SIGNAL( executeChangeReplay() ) ); QSignalSpy collectionTreeSyncSpy( &scheduler, SIGNAL( executeCollectionTreeSync() ) ); QSignalSpy syncSpy( &scheduler, SIGNAL( executeCollectionSync( const Akonadi::Collection & ) ) ); QVERIFY( changeReplaySpy.isValid() ); QVERIFY( collectionTreeSyncSpy.isValid() ); QVERIFY( syncSpy.isValid() ); // Schedule a change replay, it should be executed first thing when we enter the // event loop, but not before QVERIFY( scheduler.isEmpty() ); scheduler.scheduleChangeReplay(); QVERIFY( !scheduler.isEmpty() ); QVERIFY( changeReplaySpy.isEmpty() ); QTest::qWait( 100 ); QCOMPARE( changeReplaySpy.count(), 1 ); scheduler.taskDone(); QTest::qWait( 100 ); QCOMPARE( changeReplaySpy.count(), 1 ); // Schedule two change replays. The duplicate one should not be executed. changeReplaySpy.clear(); scheduler.scheduleChangeReplay(); scheduler.scheduleChangeReplay(); QVERIFY( changeReplaySpy.isEmpty() ); QTest::qWait( 100 ); QCOMPARE( changeReplaySpy.count(), 1 ); scheduler.taskDone(); QTest::qWait( 100 ); QCOMPARE( changeReplaySpy.count(), 1 ); // // Schedule various stuff. // Collection collection( 42 ); changeReplaySpy.clear(); scheduler.scheduleCollectionTreeSync(); scheduler.scheduleChangeReplay(); scheduler.scheduleSync(collection); scheduler.scheduleChangeReplay(); QTest::qWait( 100 ); - QCOMPARE( collectionTreeSyncSpy.count(), 1 ); - QCOMPARE( changeReplaySpy.count(), 0 ); + QCOMPARE( collectionTreeSyncSpy.count(), 0 ); + QCOMPARE( changeReplaySpy.count(), 1 ); QCOMPARE( syncSpy.count(), 0 ); scheduler.taskDone(); QTest::qWait( 100 ); QCOMPARE( collectionTreeSyncSpy.count(), 1 ); QCOMPARE( changeReplaySpy.count(), 1 ); QCOMPARE( syncSpy.count(), 0 ); // Omit a taskDone() here, there shouldn't be a new signal QTest::qWait( 100 ); QCOMPARE( collectionTreeSyncSpy.count(), 1 ); QCOMPARE( changeReplaySpy.count(), 1 ); QCOMPARE( syncSpy.count(), 0 ); scheduler.taskDone(); QTest::qWait( 100 ); QCOMPARE( collectionTreeSyncSpy.count(), 1 ); QCOMPARE( changeReplaySpy.count(), 1 ); QCOMPARE( syncSpy.count(), 1 ); // At this point, we're done, check that nothing else is emitted scheduler.taskDone(); QVERIFY( scheduler.isEmpty() ); QTest::qWait( 100 ); QCOMPARE( collectionTreeSyncSpy.count(), 1 ); QCOMPARE( changeReplaySpy.count(), 1 ); QCOMPARE( syncSpy.count(), 1 ); }