/* This file is part of the kcal library. Copyright © 2006 by David Jarvie Copyright (c) 2003,2004 Cornelius Schumacher 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 "resourcecached.h" #include "calendarlocal.h" #include "event.h" #include "exceptions.h" #include "incidence.h" #include "journal.h" #include "todo.h" #include "kresources/idmapper.h" #include #include #include #include #include #include #include #include #include #include using namespace KCal; //@cond PRIVATE class ResourceCached::Private { public: Private() : mCalendar( QLatin1String( "UTC" ) ), mReloadPolicy( ResourceCached::ReloadNever ), mReloadInterval( 10 ), mInhibitReload( false ), mReloaded( false ), mSavePending( false ), mSavePolicy( ResourceCached::SaveNever ), mSaveInterval( 10 ), mIdMapper( "kcal/uidmaps/" ) {} CalendarLocal mCalendar; int mReloadPolicy; int mReloadInterval; QTimer mReloadTimer; bool mInhibitReload; // true to prevent downloads by load(DefaultCache) bool mReloaded; // true once it has been downloaded bool mSavePending; // true if a save of changes has been scheduled on the timer int mSavePolicy; int mSaveInterval; QTimer mSaveTimer; KDateTime mLastLoad; KDateTime mLastSave; QMap mAddedIncidences; QMap mChangedIncidences; QMap mDeletedIncidences; KRES::IdMapper mIdMapper; }; //@endcond ResourceCached::ResourceCached() : ResourceCalendar(), d( new Private ) { connect( &d->mReloadTimer, SIGNAL(timeout()), SLOT(slotReload()) ); connect( &d->mSaveTimer, SIGNAL(timeout()), SLOT(slotSave()) ); } ResourceCached::ResourceCached( const KConfigGroup &group ) : ResourceCalendar( group ), d( new Private ) { connect( &d->mReloadTimer, SIGNAL(timeout()), SLOT(slotReload()) ); connect( &d->mSaveTimer, SIGNAL(timeout()), SLOT(slotSave()) ); } ResourceCached::~ResourceCached() { delete d; } CalendarLocal *ResourceCached::calendar() const { return &d->mCalendar; } bool ResourceCached::defaultReloadInhibited() const { return d->mInhibitReload; } bool ResourceCached::reloaded() const { return d->mReloaded; } void ResourceCached::setReloaded( bool done ) { d->mReloaded = done; } void ResourceCached::setReloadPolicy( int i ) { d->mReloadPolicy = i; setupReloadTimer(); } int ResourceCached::reloadPolicy() const { return d->mReloadPolicy; } void ResourceCached::setReloadInterval( int minutes ) { d->mReloadInterval = minutes; } int ResourceCached::reloadInterval() const { return d->mReloadInterval; } bool ResourceCached::inhibitDefaultReload( bool inhibit ) { if ( inhibit == d->mInhibitReload ) { return false; } d->mInhibitReload = inhibit; return true; } void ResourceCached::setSavePolicy( int i ) { d->mSavePolicy = i; setupSaveTimer(); } int ResourceCached::savePolicy() const { return d->mSavePolicy; } void ResourceCached::setSaveInterval( int minutes ) { d->mSaveInterval = minutes; } int ResourceCached::saveInterval() const { return d->mSaveInterval; } void ResourceCached::readConfig( const KConfigGroup &group ) { d->mReloadPolicy = group.readEntry( "ReloadPolicy", int(ReloadNever) ); d->mReloadInterval = group.readEntry( "ReloadInterval", 10 ); d->mSaveInterval = group.readEntry( "SaveInterval", 10 ); d->mSavePolicy = group.readEntry( "SavePolicy", int(SaveNever) ); QDateTime curDt = QDateTime::currentDateTime(); QDateTime dt = group.readEntry( "LastLoad", curDt ); d->mLastLoad = KDateTime( dt, KDateTime::UTC ); dt = group.readEntry( "LastSave", curDt ); d->mLastSave = KDateTime( dt, KDateTime::UTC ); setupSaveTimer(); setupReloadTimer(); } void ResourceCached::setupSaveTimer() { if ( d->mSavePolicy == SaveInterval ) { kDebug() << "start save timer (interval " << d->mSaveInterval << "mins)"; d->mSaveTimer.start( d->mSaveInterval * 60 * 1000 ); // n minutes } else { d->mSaveTimer.stop(); } } void ResourceCached::setupReloadTimer() { if ( d->mReloadPolicy == ReloadInterval ) { kDebug() << "start reload timer (interval " << d->mReloadInterval << "mins)"; d->mReloadTimer.start( d->mReloadInterval * 60 * 1000 ); // n minutes } else { d->mReloadTimer.stop(); } } void ResourceCached::writeConfig( KConfigGroup &group ) { group.writeEntry( "ReloadPolicy", d->mReloadPolicy ); group.writeEntry( "ReloadInterval", d->mReloadInterval ); group.writeEntry( "SavePolicy", d->mSavePolicy ); group.writeEntry( "SaveInterval", d->mSaveInterval ); group.writeEntry( "LastLoad", d->mLastLoad.toUtc().dateTime() ); group.writeEntry( "LastSave", d->mLastSave.toUtc().dateTime() ); } bool ResourceCached::addEvent( Event *event ) { return d->mCalendar.addEvent( event ); } // probably not really efficient, but...it works for now. bool ResourceCached::deleteEvent( Event *event ) { kDebug(); return d->mCalendar.deleteEvent( event ); } void ResourceCached::deleteAllEvents() { d->mCalendar.deleteAllEvents(); } Event *ResourceCached::event( const QString &uid ) { return d->mCalendar.event( uid ); } Event::List ResourceCached::rawEventsForDate( const QDate &qd, const KDateTime::Spec &timeSpec, EventSortField sortField, SortDirection sortDirection ) { Event::List list = d->mCalendar.rawEventsForDate( qd, timeSpec, sortField, sortDirection ); return list; } Event::List ResourceCached::rawEvents( const QDate &start, const QDate &end, const KDateTime::Spec &timeSpec, bool inclusive ) { return d->mCalendar.rawEvents( start, end, timeSpec, inclusive ); } Event::List ResourceCached::rawEventsForDate( const KDateTime &kdt ) { return d->mCalendar.rawEventsForDate( kdt ); } Event::List ResourceCached::rawEvents( EventSortField sortField, SortDirection sortDirection ) { return d->mCalendar.rawEvents( sortField, sortDirection ); } bool ResourceCached::addTodo( Todo *todo ) { return d->mCalendar.addTodo( todo ); } bool ResourceCached::deleteTodo( Todo *todo ) { return d->mCalendar.deleteTodo( todo ); } void ResourceCached::deleteAllTodos() { d->mCalendar.deleteAllTodos(); } bool ResourceCached::deleteJournal( Journal *journal ) { return d->mCalendar.deleteJournal( journal ); } void ResourceCached::deleteAllJournals() { d->mCalendar.deleteAllJournals(); } Todo::List ResourceCached::rawTodos( TodoSortField sortField, SortDirection sortDirection ) { return d->mCalendar.rawTodos( sortField, sortDirection ); } Todo *ResourceCached::todo( const QString &uid ) { return d->mCalendar.todo( uid ); } Todo::List ResourceCached::rawTodosForDate( const QDate &date ) { return d->mCalendar.rawTodosForDate( date ); } bool ResourceCached::addJournal( Journal *journal ) { return d->mCalendar.addJournal( journal ); } Journal *ResourceCached::journal( const QString &uid ) { return d->mCalendar.journal( uid ); } Journal::List ResourceCached::rawJournals( JournalSortField sortField, SortDirection sortDirection ) { return d->mCalendar.rawJournals( sortField, sortDirection ); } Journal::List ResourceCached::rawJournalsForDate( const QDate &date ) { return d->mCalendar.rawJournalsForDate( date ); } Alarm::List ResourceCached::alarmsTo( const KDateTime &to ) { return d->mCalendar.alarmsTo( to ); } Alarm::List ResourceCached::alarms( const KDateTime &from, const KDateTime &to ) { return d->mCalendar.alarms( from, to ); } void ResourceCached::setTimeSpec( const KDateTime::Spec &timeSpec ) { d->mCalendar.setTimeSpec( timeSpec ); } KDateTime::Spec ResourceCached::timeSpec() const { return d->mCalendar.timeSpec(); } void ResourceCached::setTimeZoneId( const QString &tzid ) { d->mCalendar.setTimeZoneId( tzid ); } QString ResourceCached::timeZoneId() const { return d->mCalendar.timeZoneId(); } void ResourceCached::shiftTimes( const KDateTime::Spec &oldSpec, const KDateTime::Spec &newSpec ) { d->mCalendar.shiftTimes( oldSpec, newSpec ); } void ResourceCached::clearChanges() { d->mAddedIncidences.clear(); d->mChangedIncidences.clear(); d->mDeletedIncidences.clear(); } bool ResourceCached::load( CacheAction action ) { kDebug() << resourceName(); setReceivedLoadError( false ); bool success = true; if ( !isOpen() ) { success = open(); //krazy:exclude=syscalls open is a class method } if ( success ) { bool update = false; switch ( action ) { case DefaultCache: if ( !d->mReloaded && !d->mInhibitReload ) { update = checkForReload(); } break; case NoSyncCache: break; case SyncCache: update = true; break; } success = doLoad( update ); } if ( !success && !receivedLoadError() ) { loadError(); } // If the resource is read-only, we need to set its incidences to read-only, // too. This can't be done at a lower-level, since the read-only setting // happens at this level if ( !noReadOnlyOnLoad() && readOnly() ) { Incidence::List incidences( rawIncidences() ); Incidence::List::Iterator it; for ( it = incidences.begin(); it != incidences.end(); ++it ) { (*it)->setReadOnly( true ); } } kDebug() << "Done loading resource" << resourceName(); if ( success ) { emit resourceLoaded( this ); } return success; } bool ResourceCached::load() { return load( SyncCache ); } bool ResourceCached::loadFromCache() { setIdMapperIdentifier(); d->mIdMapper.load(); if ( !KStandardDirs::exists( cacheFile() ) ) { return false; } d->mCalendar.load( cacheFile() ); if ( !noReadOnlyOnLoad() && readOnly() ) { Incidence::List incidences( rawIncidences() ); Incidence::List::Iterator it; for ( it = incidences.begin(); it != incidences.end(); ++it ) { (*it)->setReadOnly( true ); } } return true; } bool ResourceCached::save( CacheAction action, Incidence *incidence ) { if ( !incidence && ( d->mSavePolicy == SaveAlways || d->mSavePolicy == SaveDelayed ) ) { d->mSaveTimer.stop(); // in case it's called manually while save is pending } d->mSavePending = false; if ( saveInhibited() ) { return true; } if ( !readOnly() ) { kDebug() << "Save resource" << resourceName(); setReceivedSaveError( false ); if ( !isOpen() ) { return true; } bool upload = false; switch ( action ) { case DefaultCache: upload = checkForSave(); break; case NoSyncCache: break; case SyncCache: upload = true; break; } bool success = incidence ? doSave( upload, incidence ) : doSave( upload ); if ( !success && !receivedSaveError() ) { saveError(); } else { emit resourceSaved( this ); } return success; } else { // Read-only, just don't save... kDebug() << "Don't save read-only resource" << resourceName(); return true; } } bool ResourceCached::save( Incidence *incidence ) { return save( SyncCache, incidence ); } bool ResourceCached::doSave( bool syncCache, Incidence *incidence ) { Q_UNUSED( incidence ); return doSave( syncCache ); } void ResourceCached::saveToCache() { kDebug() << cacheFile(); setIdMapperIdentifier(); d->mIdMapper.save(); d->mCalendar.save( cacheFile() ); } void ResourceCached::setIdMapperIdentifier() { d->mIdMapper.setIdentifier( type() + '_' + identifier() ); } void ResourceCached::clearCache() { d->mCalendar.close(); } void ResourceCached::cleanUpEventCache( const Event::List &eventList ) { CalendarLocal calendar ( QLatin1String( "UTC" ) ); if ( KStandardDirs::exists( cacheFile() ) ) { calendar.load( cacheFile() ); } else { return; } Event::List list = calendar.events(); Event::List::ConstIterator cacheIt, it; for ( cacheIt = list.constBegin(); cacheIt != list.constEnd(); ++cacheIt ) { bool found = false; for ( it = eventList.begin(); it != eventList.end(); ++it ) { if ( (*it)->uid() == (*cacheIt)->uid() ) { found = true; break; } } if ( !found ) { d->mIdMapper.removeRemoteId( d->mIdMapper.remoteId( (*cacheIt)->uid() ) ); Event *event = d->mCalendar.event( (*cacheIt)->uid() ); if ( event ) { d->mCalendar.deleteEvent( event ); } } } calendar.close(); } void ResourceCached::cleanUpTodoCache( const Todo::List &todoList ) { CalendarLocal calendar ( QLatin1String( "UTC" ) ); if ( KStandardDirs::exists( cacheFile() ) ) { calendar.load( cacheFile() ); } else { return; } Todo::List list = calendar.todos(); Todo::List::ConstIterator cacheIt, it; for ( cacheIt = list.constBegin(); cacheIt != list.constEnd(); ++cacheIt ) { bool found = false; for ( it = todoList.constBegin(); it != todoList.constEnd(); ++it ) { if ( (*it)->uid() == (*cacheIt)->uid() ) { found = true; } } if ( !found ) { d->mIdMapper.removeRemoteId( d->mIdMapper.remoteId( (*cacheIt)->uid() ) ); Todo *todo = d->mCalendar.todo( (*cacheIt)->uid() ); if ( todo ) { d->mCalendar.deleteTodo( todo ); } } } calendar.close(); } KRES::IdMapper &ResourceCached::idMapper() { return d->mIdMapper; } QString ResourceCached::cacheFile() const { return KStandardDirs::locateLocal( "cache", "kcal/kresources/" + identifier() ); } QString ResourceCached::changesCacheFile( const QString &type ) const { return KStandardDirs::locateLocal( "cache", "kcal/changescache/" + identifier() + '_' + type ); } void ResourceCached::saveChangesCache( const QMap &map, const QString &type ) { CalendarLocal calendar ( QLatin1String( "UTC" ) ); bool isEmpty = true; QMap::ConstIterator it; for ( it = map.begin(); it != map.end(); ++it ) { isEmpty = false; calendar.addIncidence( it.key()->clone() ); } if ( !isEmpty ) { calendar.save( changesCacheFile( type ) ); } else { QFile file( changesCacheFile( type ) ); file.remove(); } calendar.close(); } void ResourceCached::saveChangesCache() { saveChangesCache( d->mAddedIncidences, "added" ); saveChangesCache( d->mDeletedIncidences, "deleted" ); saveChangesCache( d->mChangedIncidences, "changed" ); } void ResourceCached::loadChangesCache( QMap &map, const QString &type ) { CalendarLocal calendar ( QLatin1String( "UTC" ) ); if ( KStandardDirs::exists( changesCacheFile( type ) ) ) { calendar.load( changesCacheFile( type ) ); } else { return; } const Incidence::List list = calendar.incidences(); Incidence::List::ConstIterator it; for ( it = list.begin(); it != list.end(); ++it ) { map.insert( (*it)->clone(), true ); } calendar.close(); } void ResourceCached::loadChangesCache() { loadChangesCache( d->mAddedIncidences, "added" ); loadChangesCache( d->mDeletedIncidences, "deleted" ); loadChangesCache( d->mChangedIncidences, "changed" ); } void ResourceCached::calendarIncidenceAdded( Incidence *i ) { kDebug() << i->uid(); QMap::ConstIterator it; it = d->mAddedIncidences.constFind( i ); if ( it == d->mAddedIncidences.constEnd() ) { d->mAddedIncidences.insert( i, true ); } checkForAutomaticSave(); } void ResourceCached::calendarIncidenceChanged( Incidence *i ) { kDebug() << i->uid(); QMap::ConstIterator it; it = d->mChangedIncidences.constFind( i ); // FIXME: If you modify an added incidence, there's no need to add it to d->mChangedIncidences! if ( it == d->mChangedIncidences.constEnd() ) { d->mChangedIncidences.insert( i, true ); } checkForAutomaticSave(); } void ResourceCached::calendarIncidenceDeleted( Incidence *i ) { kDebug() << i->uid(); QMap::ConstIterator it; it = d->mDeletedIncidences.constFind( i ); if ( it == d->mDeletedIncidences.constEnd() ) { d->mDeletedIncidences.insert( i, true ); } checkForAutomaticSave(); } Incidence::List ResourceCached::addedIncidences() const { Incidence::List added; QMap::ConstIterator it; for ( it = d->mAddedIncidences.constBegin(); it != d->mAddedIncidences.constEnd(); ++it ) { added.append( it.key() ); } return added; } Incidence::List ResourceCached::changedIncidences() const { Incidence::List changed; QMap::ConstIterator it; for ( it = d->mChangedIncidences.constBegin(); it != d->mChangedIncidences.constEnd(); ++it ) { changed.append( it.key() ); } return changed; } Incidence::List ResourceCached::deletedIncidences() const { Incidence::List deleted; QMap::ConstIterator it; for ( it = d->mDeletedIncidences.constBegin(); it != d->mDeletedIncidences.constEnd(); ++it ) { deleted.append( it.key() ); } return deleted; } Incidence::List ResourceCached::allChanges() const { Incidence::List changes; QMap::ConstIterator it; for ( it = d->mAddedIncidences.constBegin(); it != d->mAddedIncidences.constEnd(); ++it ) { changes.append( it.key() ); } for ( it = d->mChangedIncidences.constBegin(); it != d->mChangedIncidences.constEnd(); ++it ) { changes.append( it.key() ); } for ( it = d->mDeletedIncidences.constBegin(); it != d->mDeletedIncidences.constEnd(); ++it ) { changes.append( it.key() ); } return changes; } bool ResourceCached::hasChanges() const { return !( d->mAddedIncidences.isEmpty() && d->mChangedIncidences.isEmpty() && d->mDeletedIncidences.isEmpty() ); } void ResourceCached::clearChange( Incidence *incidence ) { clearChange( incidence->uid() ); } void ResourceCached::clearChange( const QString &uid ) { QMap::Iterator it; for ( it = d->mAddedIncidences.begin(); it != d->mAddedIncidences.end(); ++it ) { if ( it.key()->uid() == uid ) { d->mAddedIncidences.erase( it ); break; } } for ( it = d->mChangedIncidences.begin(); it != d->mChangedIncidences.end(); ++it ) { if ( it.key()->uid() == uid ) { d->mChangedIncidences.erase( it ); break; } } for ( it = d->mDeletedIncidences.begin(); it != d->mDeletedIncidences.end(); ++it ) { if ( it.key()->uid() == uid ) { d->mDeletedIncidences.erase( it ); break; } } } void ResourceCached::enableChangeNotification() { d->mCalendar.registerObserver( this ); } void ResourceCached::disableChangeNotification() { d->mCalendar.unregisterObserver( this ); } void ResourceCached::slotReload() { if ( !isActive() ) { return; } kDebug(); load( SyncCache ); } void ResourceCached::slotSave() { if ( !isActive() ) { return; } kDebug(); save( SyncCache ); } void ResourceCached::checkForAutomaticSave() { if ( d->mSavePolicy == SaveAlways ) { kDebug() << "save now"; d->mSavePending = true; d->mSaveTimer.setSingleShot( true ); d->mSaveTimer.start( 1 * 1000 ); // 1 second } else if ( d->mSavePolicy == SaveDelayed ) { kDebug() << "save delayed"; d->mSavePending = true; d->mSaveTimer.setSingleShot( true ); d->mSaveTimer.start( 15 * 1000 ); // 15 seconds } } bool ResourceCached::checkForReload() { if ( d->mReloadPolicy == ReloadNever ) { return false; } if ( d->mReloadPolicy == ReloadOnStartup ) { return !d->mReloaded; } return true; } bool ResourceCached::checkForSave() { if ( d->mSavePolicy == SaveNever ) { return false; } return true; } void ResourceCached::addInfoText( QString &txt ) const { if ( d->mLastLoad.isValid() ) { txt += "
"; txt += i18n( "Last loaded: %1", KGlobal::locale()->formatDateTime( d->mLastLoad.toUtc().dateTime() ) ); } if ( d->mLastSave.isValid() ) { txt += "
"; txt += i18n( "Last saved: %1", KGlobal::locale()->formatDateTime( d->mLastSave.toUtc().dateTime() ) ); } } void ResourceCached::doClose() { if ( d->mSavePending ) { d->mSaveTimer.stop(); } if ( d->mSavePending || d->mSavePolicy == SaveOnExit || d->mSavePolicy == SaveInterval ) { save( SyncCache ); } d->mCalendar.close(); } bool ResourceCached::doOpen() { kDebug() << "Opening resource" << resourceName(); return true; } void KCal::ResourceCached::setOwner( const Person &owner ) { d->mCalendar.setOwner( owner ); } Person KCal::ResourceCached::owner() const { return d->mCalendar.owner(); }