From cfa6b4114cea52b167caaaeb417f98f83edd690f Mon Sep 17 00:00:00 2001 From: tpearson Date: Fri, 27 Aug 2010 23:01:53 +0000 Subject: Initial (i.e. read only) support for RECURRENCE-ID modified incidence series. Write support requires further debugging and/or compliance checks with respect to Zimbra; there is no obvious reason why it should not be working but Zimbra fails with 409 when saving. User interface support is mostly complete, with event links being tracked across deletes. git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/kdepim@1168937 283d02a7-25f6-0310-bc7c-ecb5cbfe19da --- libkcal/calendar.cpp | 19 +++++++ libkcal/calendarlocal.cpp | 61 ++++++++++++++++++++- libkcal/calendarlocal.h | 12 +++++ libkcal/icalformat.cpp | 44 ++++++++++++++++ libkcal/icalformat.h | 7 +++ libkcal/icalformatimpl.cpp | 128 ++++++++++++++++++++++++++++++++++++++------- libkcal/incidence.cpp | 106 +++++++++++++++++++++++++++++++++++-- libkcal/incidence.h | 76 +++++++++++++++++++++++++++ 8 files changed, 429 insertions(+), 24 deletions(-) (limited to 'libkcal') diff --git a/libkcal/calendar.cpp b/libkcal/calendar.cpp index 3d57e754..0be3dffc 100644 --- a/libkcal/calendar.cpp +++ b/libkcal/calendar.cpp @@ -290,6 +290,25 @@ bool Calendar::addIncidence( Incidence *incidence ) bool Calendar::deleteIncidence( Incidence *incidence ) { if ( beginChange( incidence ) ) { + if (incidence->hasRecurrenceID()) { + // Delete this event's UID from the parent's list of children + Incidence *parentIncidence; + IncidenceList il = incidence->childIncidences(); + IncidenceListIterator it; + it = il.begin(); + parentIncidence = this->incidence(*it); + parentIncidence->deleteChildIncidence(incidence->uid()); + } + else { + // Delete all children as well + IncidenceList il = incidence->childIncidences(); + IncidenceListIterator it; + for ( it = il.begin(); it != il.end(); ++it ) { + deleteIncidence( this->incidence(*it) ); + // Avoid a crash, reset the iterator every time the list is modified + it = il.begin(); + } + } Incidence::DeleteVisitor v( this ); bool result = incidence->accept( v ); endChange( incidence ); diff --git a/libkcal/calendarlocal.cpp b/libkcal/calendarlocal.cpp index 39c48ae4..716fa5e9 100644 --- a/libkcal/calendarlocal.cpp +++ b/libkcal/calendarlocal.cpp @@ -126,6 +126,10 @@ bool CalendarLocal::deleteEvent( Event *event ) setModified( true ); notifyIncidenceDeleted( event ); mDeletedIncidences.append( event ); + // Delete child events + if (!event->hasRecurrenceID()) { + deleteChildEvents(event); + } return true; } else { kdWarning() << "CalendarLocal::deleteEvent(): Event not found." << endl; @@ -133,6 +137,21 @@ bool CalendarLocal::deleteEvent( Event *event ) } } +bool CalendarLocal::deleteChildEvents( Event *event ) +{ + EventDictIterator it( mEvents ); + for( ; it.current(); ++it ) { + Event *e = *it; + if (e->uid() == event->uid()) { + if ( e->hasRecurrenceID() ) { + deleteEvent(( e )); + } + } + } + + return true; +} + void CalendarLocal::deleteAllEvents() { // kdDebug(5800) << "CalendarLocal::deleteAllEvents" << endl; @@ -178,6 +197,10 @@ bool CalendarLocal::deleteTodo( Todo *todo ) setModified( true ); notifyIncidenceDeleted( todo ); mDeletedIncidences.append( todo ); + // Delete child todos + if (!todo->hasRecurrenceID()) { + deleteChildTodos(todo); + } return true; } else { kdWarning() << "CalendarLocal::deleteTodo(): Todo not found." << endl; @@ -185,6 +208,21 @@ bool CalendarLocal::deleteTodo( Todo *todo ) } } +bool CalendarLocal::deleteChildTodos( Todo *todo ) +{ + Todo::List::ConstIterator it; + for( it = mTodoList.begin(); it != mTodoList.end(); ++it ) { + Todo *t = *it; + if (t->uid() == todo->uid()) { + if ( t->hasRecurrenceID() ) { + deleteTodo(( t )); + } + } + } + + return true; +} + void CalendarLocal::deleteAllTodos() { // kdDebug(5800) << "CalendarLocal::deleteAllTodos()\n"; @@ -392,13 +430,13 @@ Event::List CalendarLocal::rawEventsForDate( const TQDate &qd, int extraDays = event->dtStart().date().daysTo( event->dtEnd().date() ); int i; for ( i = 0; i <= extraDays; i++ ) { - if ( event->recursOn( qd.addDays( -i ) ) ) { + if ( event->recursOn( qd.addDays( -i ), this ) ) { eventList.append( event ); break; } } } else { - if ( event->recursOn( qd ) ) + if ( event->recursOn( qd, this ) ) eventList.append( event ); } } else { @@ -527,6 +565,10 @@ bool CalendarLocal::deleteJournal( Journal *journal ) setModified( true ); notifyIncidenceDeleted( journal ); mDeletedIncidences.append( journal ); + // Delete child journals + if (!journal->hasRecurrenceID()) { + deleteChildJournals(journal); + } return true; } else { kdWarning() << "CalendarLocal::deleteJournal(): Journal not found." << endl; @@ -534,6 +576,21 @@ bool CalendarLocal::deleteJournal( Journal *journal ) } } +bool CalendarLocal::deleteChildJournals( Journal *journal ) +{ + Journal::List::ConstIterator it; + for( it = mJournalList.begin(); it != mJournalList.end(); ++it ) { + Journal *j = *it; + if (j->uid() == journal->uid()) { + if ( j->hasRecurrenceID() ) { + deleteJournal(( j )); + } + } + } + + return true; +} + void CalendarLocal::deleteAllJournals() { Journal::List::ConstIterator it; diff --git a/libkcal/calendarlocal.h b/libkcal/calendarlocal.h index b76eb5ab..7326a234 100644 --- a/libkcal/calendarlocal.h +++ b/libkcal/calendarlocal.h @@ -88,6 +88,10 @@ class LIBKCAL_EXPORT CalendarLocal : public Calendar Deletes an event from this calendar. */ bool deleteEvent( Event *event ); + /** + Deletes a child event from this calendar. + */ + bool deleteChildEvents( Event *event ); /** Deletes all events from this calendar. */ @@ -110,6 +114,10 @@ class LIBKCAL_EXPORT CalendarLocal : public Calendar Remove a todo from the todolist. */ bool deleteTodo( Todo * ); + /** + Deletes a child todo from this calendar. + */ + bool deleteChildTodos( Todo *todo ); /** Deletes all todos from this calendar. */ @@ -136,6 +144,10 @@ class LIBKCAL_EXPORT CalendarLocal : public Calendar Remove a Journal from the calendar. */ bool deleteJournal( Journal * ); + /** + Delete a child journal from this calendar. + */ + bool deleteChildJournals( Journal *journal ); /** Deletes all journals from this calendar. */ diff --git a/libkcal/icalformat.cpp b/libkcal/icalformat.cpp index 4fae0eaf..c6a245c8 100644 --- a/libkcal/icalformat.cpp +++ b/libkcal/icalformat.cpp @@ -293,6 +293,50 @@ TQString ICalFormat::toString( Incidence *incidence ) return text; } +TQString ICalFormat::toString( Incidence *incidence, Calendar *calendar ) +{ + icalcomponent *component; + TQString text = ""; + + // See if there are any parent or child events that must be added to the string + if ( incidence->hasRecurrenceID() ) { + // Get the parent + IncidenceList il = incidence->childIncidences(); + IncidenceListIterator it; + it = il.begin(); + Incidence *parentIncidence; + parentIncidence = calendar->incidence(*it); + il = parentIncidence->childIncidences(); + if (il.count() > 0) { + for ( it = il.begin(); it != il.end(); ++it ) { + component = mImpl->writeIncidence( calendar->incidence(*it) ); + text = text + TQString::fromUtf8( icalcomponent_as_ical_string( component ) ); + icalcomponent_free( component ); + } + } + component = mImpl->writeIncidence( parentIncidence ); + text = text + TQString::fromUtf8( icalcomponent_as_ical_string( component ) ); + icalcomponent_free( component ); + } + else { + // This incidence is a potential parent + IncidenceList il = incidence->childIncidences(); + if (il.count() > 0) { + IncidenceListIterator it; + for ( it = il.begin(); it != il.end(); ++it ) { + component = mImpl->writeIncidence( calendar->incidence(*it) ); + text = text + TQString::fromUtf8( icalcomponent_as_ical_string( component ) ); + icalcomponent_free( component ); + } + } + component = mImpl->writeIncidence( incidence ); + text = text + TQString::fromUtf8( icalcomponent_as_ical_string( component ) ); + icalcomponent_free( component ); + } + + return text; +} + TQString ICalFormat::toString( RecurrenceRule *recurrence ) { icalproperty *property; diff --git a/libkcal/icalformat.h b/libkcal/icalformat.h index c35da5eb..425e80d5 100644 --- a/libkcal/icalformat.h +++ b/libkcal/icalformat.h @@ -86,6 +86,13 @@ class LIBKCAL_EXPORT ICalFormat : public CalFormat Return incidence as iCalendar formatted text. */ TQString toString( Incidence * ); + /** + Return incidence as iCalendar formatted text. + This function includes all RECURRENCE-ID related incidences. + @return TQString of iCalendar formatted text. + @since 3.5.12 + */ + TQString toString( Incidence *, Calendar * ); /** Return recurrence rule as iCalendar formatted text. */ diff --git a/libkcal/icalformatimpl.cpp b/libkcal/icalformatimpl.cpp index a52ec304..a1655409 100644 --- a/libkcal/icalformatimpl.cpp +++ b/libkcal/icalformatimpl.cpp @@ -314,10 +314,24 @@ void ICalFormatImpl::writeIncidence(icalcomponent *parent,Incidence *incidence) icalcomponent_add_property(parent,p); } - if ( incidence->schedulingID() != incidence->uid() ) + TQString modifiedUid; + if ( incidence->hasRecurrenceID() ) { + // Recurring incidences are special; they must match their parent's UID + // Each child has the parent set as the first item in the list + // So, get and set the UID... + IncidenceList il = incidence->childIncidences(); + IncidenceListIterator it; + it = il.begin(); + modifiedUid = (*it); + } + else { + modifiedUid = incidence->uid(); + } + + if ( incidence->schedulingID() != modifiedUid ) // We need to store the UID in here. The rawSchedulingID will // go into the iCal UID component - incidence->setCustomProperty( "LIBKCAL", "ID", incidence->uid() ); + incidence->setCustomProperty( "LIBKCAL", "ID", modifiedUid ); else incidence->removeCustomProperty( "LIBKCAL", "ID" ); @@ -330,9 +344,15 @@ void ICalFormatImpl::writeIncidence(icalcomponent *parent,Incidence *incidence) // unique id // If the scheduling ID is different from the real UID, the real // one is stored on X-REALID above - if ( !incidence->schedulingID().isEmpty() ) { - icalcomponent_add_property(parent,icalproperty_new_uid( - incidence->schedulingID().utf8())); + if ( incidence->hasRecurrenceID() ) { + // Recurring incidences are special; they must match their parent's UID + icalcomponent_add_property(parent,icalproperty_new_uid(modifiedUid.utf8())); + } + else { + if ( !incidence->schedulingID().isEmpty() ) { + icalcomponent_add_property(parent,icalproperty_new_uid( + incidence->schedulingID().utf8())); + } } // revision @@ -426,6 +446,11 @@ void ICalFormatImpl::writeIncidence(icalcomponent *parent,Incidence *incidence) incidence->relatedToUid().utf8())); } + // recurrenceid + if ( incidence->hasRecurrenceID() ) { + icalcomponent_add_property(parent, icalproperty_new_recurrenceid( writeICalDateTime( incidence->recurrenceID() ) )); + } + // kdDebug(5800) << "Write recurrence for '" << incidence->summary() << "' (" << incidence->uid() // << ")" << endl; @@ -1359,10 +1384,21 @@ void ICalFormatImpl::readIncidence(icalcomponent *parent, icaltimezone *tz, Inci categories.append(TQString::fromUtf8(text)); break; + case ICAL_RECURRENCEID_PROPERTY: // recurrenceID + icaltime = icalproperty_get_recurrenceid(p); + incidence->setRecurrenceID( readICalDateTime( p, icaltime ) ); + incidence->setHasRecurrenceID( true ); + break; + case ICAL_RRULE_PROPERTY: readRecurrenceRule( p, incidence ); break; +// case ICAL_CONTACT_PROPERTY: +// incidenceBase->addContact( +// QString::fromUtf8( icalproperty_get_contact( p ) ) ); +// break; + case ICAL_RDATE_PROPERTY: { icaldatetimeperiodtype rd = icalproperty_get_rdate( p ); if ( icaltime_is_valid_time( rd.time ) ) { @@ -2031,11 +2067,29 @@ bool ICalFormatImpl::populate( Calendar *cal, icalcomponent *calendar) // kdDebug(5800) << "----Todo found" << endl; Todo *todo = readTodo(c); if (todo) { - if (!cal->todo(todo->uid())) { - cal->addTodo(todo); - } else { - delete todo; - mTodosRelate.remove( todo ); + if (todo->hasRecurrenceID()) { + TQString originalUid = todo->uid(); + todo->setUid(originalUid + QString("-recur-%1").arg(todo->recurrenceID().toTime_t())); + if (!cal->todo(todo->uid())) { + cal->addTodo(todo); + if (!cal->event(originalUid)) { + printf("FIXME! [WARNING] Parent for child event does not yet exist!\n\r"); + } + else { + // Add this todo to its parent + cal->todo(originalUid)->addChildIncidence(todo->uid()); + // And the parent to the child + todo->addChildIncidence(cal->todo(originalUid)->uid()); + } + } + } + else { + if (!cal->todo(todo->uid())) { + cal->addTodo(todo); + } else { + delete todo; + mTodosRelate.remove( todo ); + } } } c = icalcomponent_get_next_component(calendar,ICAL_VTODO_COMPONENT); @@ -2047,11 +2101,29 @@ bool ICalFormatImpl::populate( Calendar *cal, icalcomponent *calendar) // kdDebug(5800) << "----Event found" << endl; Event *event = readEvent(c, ctz); if (event) { - if (!cal->event(event->uid())) { - cal->addEvent(event); - } else { - delete event; - mEventsRelate.remove( event ); + if (event->hasRecurrenceID()) { + TQString originalUid = event->uid(); + event->setUid(originalUid + QString("-recur-%1").arg(event->recurrenceID().toTime_t())); + if (!cal->event(event->uid())) { + cal->addEvent(event); + if (!cal->event(originalUid)) { + printf("FIXME! [WARNING] Parent for child event does not yet exist!\n\r"); + } + else { + // Add this event to its parent + cal->event(originalUid)->addChildIncidence(event->uid()); + // And the parent to the child + event->addChildIncidence(cal->event(originalUid)->uid()); + } + } + } + else { + if (!cal->event(event->uid())) { + cal->addEvent(event); + } else { + delete event; + mEventsRelate.remove( event ); + } } } c = icalcomponent_get_next_component(calendar,ICAL_VEVENT_COMPONENT); @@ -2063,10 +2135,28 @@ bool ICalFormatImpl::populate( Calendar *cal, icalcomponent *calendar) // kdDebug(5800) << "----Journal found" << endl; Journal *journal = readJournal(c); if (journal) { - if (!cal->journal(journal->uid())) { - cal->addJournal(journal); - } else { - delete journal; + if (journal->hasRecurrenceID()) { + TQString originalUid = journal->uid(); + journal->setUid(originalUid + QString("-recur-%1").arg(journal->recurrenceID().toTime_t())); + if (!cal->journal(journal->uid())) { + cal->addJournal(journal); + if (!cal->event(originalUid)) { + printf("FIXME! [WARNING] Parent for child event does not yet exist!\n\r"); + } + else { + // Add this journal to its parent + cal->journal(originalUid)->addChildIncidence(journal->uid()); + // And the parent to the child + journal->addChildIncidence(cal->journal(originalUid)->uid()); + } + } + } + else { + if (!cal->journal(journal->uid())) { + cal->addJournal(journal); + } else { + delete journal; + } } } c = icalcomponent_get_next_component(calendar,ICAL_VJOURNAL_COMPONENT); diff --git a/libkcal/incidence.cpp b/libkcal/incidence.cpp index 94995b12..79de6723 100644 --- a/libkcal/incidence.cpp +++ b/libkcal/incidence.cpp @@ -27,13 +27,15 @@ #include "calformat.h" #include "incidence.h" +#include "calendar.h" using namespace KCal; Incidence::Incidence() : IncidenceBase(), mRelatedTo(0), mStatus(StatusNone), mSecrecy(SecrecyPublic), - mPriority(0), mRecurrence(0) + mPriority(0), mRecurrence(0), + mHasRecurrenceID( false ), mChildRecurrenceEvents() { recreate(); @@ -59,6 +61,9 @@ Incidence::Incidence( const Incidence &i ) : IncidenceBase( i ),Recurrence::Obse mSecrecy = i.mSecrecy; mPriority = i.mPriority; mLocation = i.mLocation; + mRecurrenceID = i.mRecurrenceID; + mHasRecurrenceID = i.mHasRecurrenceID; + mChildRecurrenceEvents = i.mChildRecurrenceEvents; // Alarms and Attachments are stored in ListBase<...>, which is a TQValueList<...*>. // We need to really duplicate the objects stored therein, otherwise deleting @@ -124,6 +129,9 @@ Incidence& Incidence::operator=( const Incidence &i ) mSecrecy = i.mSecrecy; mPriority = i.mPriority; mLocation = i.mLocation; + mRecurrenceID = i.mRecurrenceID; + mHasRecurrenceID = i.mHasRecurrenceID; + mChildRecurrenceEvents = i.mChildRecurrenceEvents; mAlarms.clearAll(); Alarm::List::ConstIterator it; @@ -413,12 +421,58 @@ bool Incidence::doesRecur() const bool Incidence::recursOn(const TQDate &qd) const { - return ( mRecurrence && mRecurrence->recursOn(qd) ); + bool doesRecur = false; + doesRecur = mRecurrence && mRecurrence->recursOn(qd); + + return doesRecur; } bool Incidence::recursAt(const TQDateTime &qdt) const { - return ( mRecurrence && mRecurrence->recursAt(qdt) ); + bool doesRecur = false; + doesRecur = mRecurrence && mRecurrence->recursAt(qdt); + + return doesRecur; +} + +bool Incidence::recursOn(const TQDate &qd, Calendar *cal) const +{ + bool doesRecur = false; + doesRecur = mRecurrence && mRecurrence->recursOn(qd); + + // Make sure that this instance has not been moved through a RECURRENCE-ID statement + if (hasRecurrenceID() == false) { + IncidenceList il = childIncidences(); + IncidenceListIterator it; + for ( it = il.begin(); it != il.end(); ++it ) { + QDateTime modifiedDt = cal->incidence(*it)->recurrenceID(); + modifiedDt.setTime(QTime()); + if (QDateTime(qd) == modifiedDt) { + doesRecur = false; + } + } + } + + return doesRecur; +} + +bool Incidence::recursAt(const TQDateTime &qdt, Calendar *cal) const +{ + bool doesRecur = false; + doesRecur = mRecurrence && mRecurrence->recursAt(qdt); + + // Make sure that this instance has not been moved through a RECURRENCE-ID statement + if (hasRecurrenceID() == false) { + IncidenceList il = childIncidences(); + IncidenceListIterator it; + for ( it = il.begin(); it != il.end(); ++it ) { + if (qdt == cal->incidence(*it)->recurrenceID()) { + doesRecur = false; + } + } + } + + return doesRecur; } /** @@ -836,6 +890,52 @@ TQString Incidence::schedulingID() const return mSchedulingID; } +bool Incidence::hasRecurrenceID() const +{ + return mHasRecurrenceID; +} + +void Incidence::setHasRecurrenceID( bool hasRecurrenceID ) +{ + if ( mReadOnly ) { + return; + } + + mHasRecurrenceID = hasRecurrenceID; + updated(); +} + +TQDateTime Incidence::recurrenceID() const +{ + return mRecurrenceID; +} + +void Incidence::setRecurrenceID( const TQDateTime &recurrenceID ) +{ + if ( mReadOnly ) { + return; + } + +// update(); + mRecurrenceID = recurrenceID; + updated(); +} + +void Incidence::addChildIncidence( TQString childIncidence ) +{ + mChildRecurrenceEvents.append(childIncidence); +} + +void Incidence::deleteChildIncidence( TQString childIncidence ) +{ + mChildRecurrenceEvents.remove(childIncidence); +} + +IncidenceList Incidence::childIncidences() const +{ + return mChildRecurrenceEvents; +} + /** Observer interface for the recurrence class. If the recurrence is changed, this method will be called for the incidence the recurrence object belongs to. */ diff --git a/libkcal/incidence.h b/libkcal/incidence.h index 268b2ced..477b758c 100644 --- a/libkcal/incidence.h +++ b/libkcal/incidence.h @@ -36,6 +36,10 @@ namespace KCal { +class Calendar; + +typedef QStringList IncidenceList; +typedef QStringList::iterator IncidenceListIterator; /** This class provides the base class common to all calendar components. @@ -248,6 +252,21 @@ class LIBKCAL_EXPORT Incidence : public IncidenceBase, public Recurrence::Observ */ bool recursAt( const TQDateTime &qdt ) const; + /** + Returns true if the date specified is one on which the incidence will + recur. + This function takes RECURRENCE-ID parameters into account + @param cal the calendar owning the incidence + */ + virtual bool recursOn( const TQDate &qd, Calendar *cal ) const; + /** + Returns true if the date/time specified is one on which the incidence will + recur. + This function takes RECURRENCE-ID parameters into account + @param cal the calendar owning the incidence + */ + bool recursAt( const TQDateTime &qdt, Calendar *cal ) const; + /** Calculates the start date/time for all recurrences that happen at some time on the given date (might start before that date, but end on or after the @@ -378,6 +397,58 @@ class LIBKCAL_EXPORT Incidence : public IncidenceBase, public Recurrence::Observ */ int priority() const; + /** + Returns true if the incidence has recurrenceID, otherwise return false. + @see setHasRecurrenceID(), setRecurrenceID(TQDateTime) + @since 3.5.12 + */ + bool hasRecurrenceID() const; + + /** + Sets if the incidence has recurrenceID. + @param hasRecurrenceID true if incidence has recurrenceID, otherwise false + @see hasRecurrenceID(), recurrenceID() + @since 3.5.12 + */ + void setHasRecurrenceID( bool hasRecurrenceID ); + + /** + Set the incidences recurrenceID. + @param recurrenceID is the incidence recurrenceID to set + @see recurrenceID(). + @since 3.5.12 + */ + void setRecurrenceID( const TQDateTime &recurrenceID ); + + /** + Returns the incidence recurrenceID. + @return incidences recurrenceID value + @see setRecurrenceID(). + @since 3.5.12 + */ + TQDateTime recurrenceID() const; + + /** + Attach a child incidence to a parent incidence. + @param childIncidence is the child incidence to add + @since 3.5.12 + */ + void addChildIncidence( TQString childIncidence ); + + /** + Detach a child incidence from its parent incidence. + @param childIncidence is the child incidence to remove + @since 3.5.12 + */ + void deleteChildIncidence( TQString childIncidence ); + + /** + Returns an EventList of all child incidences. + @return EventList of all child incidences. + @since 3.5.12 + */ + IncidenceList childIncidences() const; + // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% // %%%%% Alarm-related methods @@ -477,6 +548,11 @@ class LIBKCAL_EXPORT Incidence : public IncidenceBase, public Recurrence::Observ // Scheduling ID - used only to identify between scheduling mails TQString mSchedulingID; + TQDateTime mRecurrenceID; // recurrenceID + bool mHasRecurrenceID; // if incidence has recurrenceID + + IncidenceList mChildRecurrenceEvents; + class Private; Private *d; }; -- cgit v1.2.3