/* This file is part of the kcal library. Copyright (c) 1998 Preston Brown Copyright (c) 2001,2003,2004 Cornelius Schumacher Copyright (C) 2003-2004 Reinhold Kainhofer 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. */ /** @file This file is part of the API for handling calendar data and defines the CalendarLocal class. @brief This class provides a calendar stored as a local file. @author Preston Brown \ @author Cornelius Schumacher \ */ #include "calendarlocal.h" #include "incidence.h" #include "event.h" #include "todo.h" #include "journal.h" #include "filestorage.h" #include #include #include #include #include #include #include #include using namespace KCal; /** Private class that helps to provide binary compatibility between releases. @internal */ //@cond PRIVATE class KCal::CalendarLocal::Private { public: Private() : mFormat(0) {} QString mFileName; // filename where calendar is stored CalFormat *mFormat; // calendar format QHashmEvents; // hash on uids of all Events QMultiHashmEventsForDate;// on start dates of non-recurring, single-day Events QHashmTodos; // hash on uids of all Todos QMultiHashmTodosForDate;// on due dates for all Todos QHashmJournals; // hash on uids of all Journals QMultiHashmJournalsForDate; // on dates of all Journals void insertEvent( Event *event ); void insertTodo( Todo *todo ); void insertJournal( Journal *journal ); }; // helper namespace { template void removeIncidenceFromMultiHashByUID( QMultiHash< QString, T >& container, const QString &key, const QString &uid ) { const QList values = container.values( key ); QListIterator it(values); while ( it.hasNext() ) { T const inc = it.next(); if ( inc->uid() == uid ) { container.remove( key, inc ); } } } } //@endcond CalendarLocal::CalendarLocal( const KDateTime::Spec &timeSpec ) : Calendar( timeSpec ), d( new KCal::CalendarLocal::Private ) { } CalendarLocal::CalendarLocal( const QString &timeZoneId ) : Calendar( timeZoneId ), d( new KCal::CalendarLocal::Private ) { } CalendarLocal::~CalendarLocal() { close(); delete d; } bool CalendarLocal::load( const QString &fileName, CalFormat *format ) { d->mFileName = fileName; FileStorage storage( this, fileName, format ); return storage.load(); } bool CalendarLocal::reload() { const QString filename = d->mFileName; save(); close(); d->mFileName = filename; FileStorage storage( this, d->mFileName ); return storage.load(); } bool CalendarLocal::save() { if ( d->mFileName.isEmpty() ) { return false; } if ( isModified() ) { FileStorage storage( this, d->mFileName, d->mFormat ); return storage.save(); } else { return true; } } bool CalendarLocal::save( const QString &fileName, CalFormat *format ) { // Save only if the calendar is either modified, or saved to a // different file than it was loaded from if ( d->mFileName != fileName || isModified() ) { FileStorage storage( this, fileName, format ); return storage.save(); } else { return true; } } void CalendarLocal::close() { setObserversEnabled( false ); d->mFileName.clear(); deleteAllEvents(); deleteAllTodos(); deleteAllJournals(); setModified( false ); setObserversEnabled( true ); } bool CalendarLocal::addEvent( Event *event ) { d->insertEvent( event ); event->registerObserver( this ); setModified( true ); notifyIncidenceAdded( event ); return true; } bool CalendarLocal::deleteEvent( Event *event ) { const QString uid = event->uid(); if ( d->mEvents.remove( uid ) ) { setModified( true ); notifyIncidenceDeleted( event ); if ( !event->recurs() ) { removeIncidenceFromMultiHashByUID( d->mEventsForDate, event->dtStart().date().toString(), event->uid() ); } return true; } else { kWarning() << "Event not found."; return false; } } void CalendarLocal::deleteAllEvents() { QHashIteratori( d->mEvents ); while ( i.hasNext() ) { i.next(); notifyIncidenceDeleted( i.value() ); // suppress update notifications for the relation removal triggered // by the following deletions i.value()->startUpdates(); } qDeleteAll( d->mEvents ); d->mEvents.clear(); d->mEventsForDate.clear(); } Event *CalendarLocal::event( const QString &uid ) { return d->mEvents.value( uid ); } bool CalendarLocal::addTodo( Todo *todo ) { d->insertTodo( todo ); todo->registerObserver( this ); // Set up sub-to-do relations setupRelations( todo ); setModified( true ); notifyIncidenceAdded( todo ); return true; } //@cond PRIVATE void CalendarLocal::Private::insertTodo( Todo *todo ) { QString uid = todo->uid(); if ( !mTodos.contains( uid ) ) { mTodos.insert( uid, todo ); if ( todo->hasDueDate() ) { mTodosForDate.insert( todo->dtDue().date().toString(), todo ); } } else { #ifndef NDEBUG // if we already have an to-do with this UID, it must be the same to-do, // otherwise something's really broken Q_ASSERT( mTodos.value( uid ) == todo ); #endif } } //@endcond bool CalendarLocal::deleteTodo( Todo *todo ) { // Handle orphaned children removeRelations( todo ); if ( d->mTodos.remove( todo->uid() ) ) { setModified( true ); notifyIncidenceDeleted( todo ); if ( todo->hasDueDate() ) { removeIncidenceFromMultiHashByUID( d->mTodosForDate, todo->dtDue().date().toString(), todo->uid() ); } return true; } else { kWarning() << "Todo not found."; return false; } } void CalendarLocal::deleteAllTodos() { QHashIteratori( d->mTodos ); while ( i.hasNext() ) { i.next(); notifyIncidenceDeleted( i.value() ); // suppress update notifications for the relation removal triggered // by the following deletions i.value()->startUpdates(); } qDeleteAll( d->mTodos ); d->mTodos.clear(); d->mTodosForDate.clear(); } Todo *CalendarLocal::todo( const QString &uid ) { return d->mTodos.value( uid ); } Todo::List CalendarLocal::rawTodos( TodoSortField sortField, SortDirection sortDirection ) { Todo::List todoList; QHashIteratori( d->mTodos ); while ( i.hasNext() ) { i.next(); todoList.append( i.value() ); } return sortTodos( &todoList, sortField, sortDirection ); } Todo::List CalendarLocal::rawTodosForDate( const QDate &date ) { Todo::List todoList; Todo *t; QString dateStr = date.toString(); QMultiHash::const_iterator it = d->mTodosForDate.constFind( dateStr ); while ( it != d->mTodosForDate.constEnd() && it.key() == dateStr ) { t = it.value(); todoList.append( t ); ++it; } return todoList; } Alarm::List CalendarLocal::alarmsTo( const KDateTime &to ) { return alarms( KDateTime( QDate( 1900, 1, 1 ) ), to ); } Alarm::List CalendarLocal::alarms( const KDateTime &from, const KDateTime &to ) { Alarm::List alarmList; QHashIteratorie( d->mEvents ); Event *e; while ( ie.hasNext() ) { ie.next(); e = ie.value(); if ( e->recurs() ) { appendRecurringAlarms( alarmList, e, from, to ); } else { appendAlarms( alarmList, e, from, to ); } } QHashIteratorit( d->mTodos ); Todo *t; while ( it.hasNext() ) { it.next(); t = it.value(); if ( !t->isCompleted() ) { if ( t->recurs() ) { appendRecurringAlarms( alarmList, t, from, to ); } else { appendAlarms( alarmList, t, from, to ); } } } return alarmList; } //@cond PRIVATE void CalendarLocal::Private::insertEvent( Event *event ) { QString uid = event->uid(); if ( !mEvents.contains( uid ) ) { mEvents.insert( uid, event ); if ( !event->recurs() && !event->isMultiDay() ) { mEventsForDate.insert( event->dtStart().date().toString(), event ); } } else { #ifdef NDEBUG // if we already have an event with this UID, it must be the same event, // otherwise something's really broken Q_ASSERT( mEvents.value( uid ) == event ); #endif } } //@endcond void CalendarLocal::incidenceUpdated( IncidenceBase *incidence ) { KDateTime nowUTC = KDateTime::currentUtcDateTime(); incidence->setLastModified( nowUTC ); // we should probably update the revision number here, // or internally in the Event itself when certain things change. // need to verify with ical documentation. if ( incidence->type() == "Event" ) { Event *event = static_cast( incidence ); removeIncidenceFromMultiHashByUID( d->mEventsForDate, event->dtStart().date().toString(), event->uid() ); if ( !event->recurs() && !event->isMultiDay() ) { d->mEventsForDate.insert( event->dtStart().date().toString(), event ); } } else if ( incidence->type() == "Todo" ) { Todo *todo = static_cast( incidence ); removeIncidenceFromMultiHashByUID( d->mTodosForDate, todo->dtDue().date().toString(), todo->uid() ); if ( todo->hasDueDate() ) { d->mTodosForDate.insert( todo->dtDue().date().toString(), todo ); } } else if ( incidence->type() == "Journal" ) { Journal *journal = static_cast( incidence ); removeIncidenceFromMultiHashByUID( d->mJournalsForDate, journal->dtStart().date().toString(), journal->uid() ); d->mJournalsForDate.insert( journal->dtStart().date().toString(), journal ); } else { Q_ASSERT( false ); } // The static_cast is ok as the CalendarLocal only observes Incidence objects notifyIncidenceChanged( static_cast( incidence ) ); setModified( true ); } Event::List CalendarLocal::rawEventsForDate( const QDate &date, const KDateTime::Spec ×pec, EventSortField sortField, SortDirection sortDirection ) { Event::List eventList; Event *ev; // Find the hash for the specified date QString dateStr = date.toString(); QMultiHash::const_iterator it = d->mEventsForDate.constFind( dateStr ); // Iterate over all non-recurring, single-day events that start on this date KDateTime::Spec ts = timespec.isValid() ? timespec : timeSpec(); KDateTime kdt( date, ts ); while ( it != d->mEventsForDate.constEnd() && it.key() == dateStr ) { ev = it.value(); KDateTime end( ev->dtEnd().toTimeSpec( ev->dtStart() ) ); if ( ev->allDay() ) { end.setDateOnly( true ); } else { end = end.addSecs( -1 ); } if ( end >= kdt ) { eventList.append( ev ); } ++it; } // Iterate over all events. Look for recurring events that occur on this date QHashIteratori( d->mEvents ); while ( i.hasNext() ) { i.next(); ev = i.value(); if ( ev->recurs() ) { if ( ev->isMultiDay() ) { int extraDays = ev->dtStart().date().daysTo( ev->dtEnd().date() ); for ( int i = 0; i <= extraDays; ++i ) { if ( ev->recursOn( date.addDays( -i ), ts ) ) { eventList.append( ev ); break; } } } else { if ( ev->recursOn( date, ts ) ) { eventList.append( ev ); } } } else { if ( ev->isMultiDay() ) { if ( ev->dtStart().date() <= date && ev->dtEnd().date() >= date ) { eventList.append( ev ); } } } } return sortEventsForDate( &eventList, date, timespec, sortField, sortDirection ); } Event::List CalendarLocal::rawEvents( const QDate &start, const QDate &end, const KDateTime::Spec ×pec, bool inclusive ) { Event::List eventList; KDateTime::Spec ts = timespec.isValid() ? timespec : timeSpec(); KDateTime st( start, ts ); KDateTime nd( end, ts ); KDateTime yesterStart = st.addDays( -1 ); // Get non-recurring events QHashIteratori( d->mEvents ); Event *event; while ( i.hasNext() ) { i.next(); event = i.value(); KDateTime rStart = event->dtStart(); if ( nd < rStart ) { continue; } if ( inclusive && rStart < st ) { continue; } if ( !event->recurs() ) { // non-recurring events KDateTime rEnd = event->dtEnd(); if ( rEnd < st ) { continue; } if ( inclusive && nd < rEnd ) { continue; } } else { // recurring events switch( event->recurrence()->duration() ) { case -1: // infinite if ( inclusive ) { continue; } break; case 0: // end date given default: // count given KDateTime rEnd( event->recurrence()->endDate(), ts ); if ( !rEnd.isValid() ) { continue; } if ( rEnd < st ) { continue; } if ( inclusive && nd < rEnd ) { continue; } break; } // switch(duration) } //if(recurs) eventList.append( event ); } return eventList; } Event::List CalendarLocal::rawEventsForDate( const KDateTime &kdt ) { return rawEventsForDate( kdt.date(), kdt.timeSpec() ); } Event::List CalendarLocal::rawEvents( EventSortField sortField, SortDirection sortDirection ) { Event::List eventList; QHashIteratori( d->mEvents ); while ( i.hasNext() ) { i.next(); eventList.append( i.value() ); } return sortEvents( &eventList, sortField, sortDirection ); } bool CalendarLocal::addJournal( Journal *journal ) { d->insertJournal( journal ); journal->registerObserver( this ); setModified( true ); notifyIncidenceAdded( journal ); return true; } //@cond PRIVATE void CalendarLocal::Private::insertJournal( Journal *journal ) { QString uid = journal->uid(); if ( !mJournals.contains( uid ) ) { mJournals.insert( uid, journal ); mJournalsForDate.insert( journal->dtStart().date().toString(), journal ); } else { #ifndef NDEBUG // if we already have an journal with this UID, it must be the same journal, // otherwise something's really broken Q_ASSERT( mJournals.value( uid ) == journal ); #endif } } //@endcond bool CalendarLocal::deleteJournal( Journal *journal ) { if ( d->mJournals.remove( journal->uid() ) ) { setModified( true ); notifyIncidenceDeleted( journal ); removeIncidenceFromMultiHashByUID( d->mJournalsForDate, journal->dtStart().date().toString(), journal->uid() ); return true; } else { kWarning() << "Journal not found."; return false; } } void CalendarLocal::deleteAllJournals() { QHashIteratori( d->mJournals ); while ( i.hasNext() ) { i.next(); notifyIncidenceDeleted( i.value() ); // suppress update notifications for the relation removal triggered // by the following deletions i.value()->startUpdates(); } qDeleteAll( d->mJournals ); d->mJournals.clear(); d->mJournalsForDate.clear(); } Journal *CalendarLocal::journal( const QString &uid ) { return d->mJournals.value( uid ); } Journal::List CalendarLocal::rawJournals( JournalSortField sortField, SortDirection sortDirection ) { Journal::List journalList; QHashIteratori( d->mJournals ); while ( i.hasNext() ) { i.next(); journalList.append( i.value() ); } return sortJournals( &journalList, sortField, sortDirection ); } Journal::List CalendarLocal::rawJournalsForDate( const QDate &date ) { Journal::List journalList; Journal *j; QString dateStr = date.toString(); QMultiHash::const_iterator it = d->mJournalsForDate.constFind( dateStr ); while ( it != d->mJournalsForDate.constEnd() && it.key() == dateStr ) { j = it.value(); journalList.append( j ); ++it; } return journalList; }