/* Copyright (C) 2004-2005 SKYRIX Software AG This file is part of OpenGroupware.org. OGo is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. OGo 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with OGo; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "SOGoAppointmentObject.h" #include #include #include #include #include "common.h" @implementation SOGoAppointmentObject static id parser = nil; static SaxObjectDecoder *sax = nil; static NGLogger *logger = nil; + (void)initialize { NGLoggerManager *lm; SaxXMLReaderFactory *factory; static BOOL didInit = NO; if (didInit) return; didInit = YES; lm = [NGLoggerManager defaultLoggerManager]; logger = [lm loggerForClass:self]; factory = [SaxXMLReaderFactory standardXMLReaderFactory]; parser = [[factory createXMLReaderForMimeType:@"text/calendar"] retain]; if (parser == nil) [logger fatalWithFormat:@"did not find a parser for text/calendar!"]; sax = [[SaxObjectDecoder alloc] initWithMappingNamed:@"NGiCal"]; if (sax == nil) [logger fatalWithFormat:@"could not create the iCal SAX handler!"]; [parser setContentHandler:sax]; [parser setErrorHandler:sax]; } - (void)dealloc { [super dealloc]; } /* accessors */ - (NSString *)iCalString { // for UI-X appointment viewer return [self contentAsString]; } - (iCalEvent *)event { NSString *iCalString; iCalEvent *event; iCalString = [self iCalString]; if ([iCalString length] > 0) { iCalCalendar *cal; [parser parseFromSource:iCalString]; cal = [sax rootObject]; [sax reset]; event = [[cal events] lastObject]; return event; } return nil; } /* iCal handling */ - (NSArray *)attendeeUIDsFromAppointment:(SOGoAppointment *)_apt { AgenorUserManager *um; NSMutableArray *uids; NSArray *attendees; unsigned i, count; NSString *email, *uid; if (![_apt isNotNull]) return nil; if ((attendees = [_apt attendees]) == nil) return nil; count = [attendees count]; uids = [NSMutableArray arrayWithCapacity:count + 1]; um = [AgenorUserManager sharedUserManager]; /* add organizer */ email = [[_apt organizer] rfc822Email]; if ([email isNotNull]) { uid = [um getUIDForEmail:email]; if ([uid isNotNull]) { [uids addObject:uid]; } else [self logWithFormat:@"Note: got no uid for organizer: '%@'", email]; } /* add attendees */ for (i = 0; i < count; i++) { iCalPerson *person; person = [attendees objectAtIndex:i]; email = [person rfc822Email]; if (![email isNotNull]) continue; uid = [um getUIDForEmail:email]; if (![uid isNotNull]) { [self logWithFormat:@"Note: got no uid for email: '%@'", email]; continue; } if (![uids containsObject:uid]) [uids addObject:uid]; } return uids; } /* raw saving */ - (NSException *)primarySaveContentString:(NSString *)_iCalString { return [super saveContentString:_iCalString]; } - (NSException *)primaryDelete { return [super delete]; } /* folder management */ - (id)lookupHomeFolderForUID:(NSString *)_uid inContext:(id)_ctx { // TODO: what does this do? lookup the home of the organizer? return [[self container] lookupHomeFolderForUID:_uid inContext:_ctx]; } - (NSArray *)lookupCalendarFoldersForUIDs:(NSArray *)_uids inContext:(id)_ctx { return [[self container] lookupCalendarFoldersForUIDs:_uids inContext:_ctx]; } /* store in all the other folders */ - (NSException *)saveContentString:(NSString *)_iCal inUIDs:(NSArray *)_uids { NSEnumerator *e; id folder; NSException *allErrors = nil; id ctx; ctx = [[WOApplication application] context]; e = [[self lookupCalendarFoldersForUIDs:_uids inContext:ctx] objectEnumerator]; while ((folder = [e nextObject]) != nil) { NSException *error; SOGoAppointmentObject *apt; if (![folder isNotNull]) /* no folder was found for given UID */ continue; apt = [folder lookupName:[self nameInContainer] inContext:ctx acquire:NO]; if (![apt isNotNull]) { [self logWithFormat:@"Note: did not find '%@' in folder: %@", [self nameInContainer], folder]; continue; } if ((error = [apt primarySaveContentString:_iCal]) != nil) { [self logWithFormat:@"Note: failed to save iCal in folder: %@", folder]; // TODO: make compound allErrors = error; } } return allErrors; } - (NSException *)deleteInUIDs:(NSArray *)_uids { NSEnumerator *e; id folder; NSException *allErrors = nil; id ctx; ctx = [[WOApplication application] context]; e = [[self lookupCalendarFoldersForUIDs:_uids inContext:ctx] objectEnumerator]; while ((folder = [e nextObject])) { NSException *error; SOGoAppointmentObject *apt; apt = [folder lookupName:[self nameInContainer] inContext:ctx acquire:NO]; if (![apt isNotNull]) { [self logWithFormat:@"Note: did not find '%@' in folder: %@", [self nameInContainer], folder]; continue; } if ((error = [apt primaryDelete]) != nil) { [self logWithFormat:@"Note: failed to delete in folder: %@", folder]; // TODO: make compound allErrors = error; } } return allErrors; } /* "iCal multifolder saves" */ - (NSException *)saveContentString:(NSString *)_iCal baseSequence:(int)_v { /* Note: we need to delete in all participants folders and send iMIP messages for all external accounts. Steps: - fetch stored content - parse old content - check if sequence matches (or if 0=ignore) - extract old attendee list + organizer (make unique) - parse new content (ensure that sequence is increased!) - extract new attendee list + organizer (make unique) - make a diff => new, same, removed - write to new, same - delete in removed folders - send iMIP mail for all folders not found */ SOGoAppointment *oldApt, *newApt; NSString *oldContent; NSArray *oldUIDs, *newUIDs; NSMutableArray *storeUIDs, *removedUIDs; unsigned i, count; NSException *storeError, *delError; if ([_iCal length] == 0) { return [NSException exceptionWithHTTPStatus:400 /* Bad Request */ reason:@"got no iCalendar content to store!"]; } /* handle old content */ oldContent = [self iCalString]; /* if nil, this is a new appointment */ if ([oldContent length] == 0) { /* new appointment */ [self debugWithFormat:@"saving new appointment: %@", _iCal]; oldApt = nil; } else { oldApt = [[[SOGoAppointment alloc] initWithICalString:oldContent] autorelease]; } /* compare sequence if requested */ if (_v != 0) { // TODO } oldUIDs = [oldApt isNotNull] ? [self attendeeUIDsFromAppointment:oldApt] : nil; /* handle new content */ newApt = [[[SOGoAppointment alloc] initWithICalString:_iCal] autorelease]; if (newApt == nil) { return [NSException exceptionWithHTTPStatus:400 /* Bad Request */ reason:@"could not parse iCalendar content!"]; } if ((newUIDs = [self attendeeUIDsFromAppointment:newApt]) == nil) [self debugWithFormat:@"got no UIDs from appointment: %@", newApt]; /* diff */ count = [oldUIDs count]; removedUIDs = [NSMutableArray arrayWithCapacity:count]; storeUIDs = [NSMutableArray arrayWithCapacity:count]; for (i = 0; i < count; i++) { NSString *uid; uid = [oldUIDs objectAtIndex:i]; if ([newUIDs containsObject:uid]) [storeUIDs addObject:uid]; /* old ID is still available */ else [removedUIDs addObject:uid]; /* old ID is not available anymore */ } count = [newUIDs count]; for (i = 0; i < count; i++) { NSString *uid; uid = [newUIDs objectAtIndex:i]; if ([storeUIDs containsObject:uid]) /* old ID is still available */ continue; if ([removedUIDs containsObject:uid]) /* old ID is not available anymore */ continue; /* new ID which is not part of the old set => store a new */ [storeUIDs addObject:uid]; } [self debugWithFormat: @"UID ops:\n new: %@\n old: %@\n store: %@\n remove: %@", newUIDs, oldUIDs, storeUIDs, removedUIDs]; /* perform */ storeError = [self saveContentString:_iCal inUIDs:storeUIDs]; delError = [self deleteInUIDs:removedUIDs]; // TODO: make compound if (storeError != nil) return storeError; if (delError != nil) return delError; return nil; } - (NSException *)deleteWithBaseSequence:(int)_v { /* Note: We need to delete in all participants folders and send iMIP messages for all external accounts. Delete is basically identical to save with all attendees and the organizer being deleted. Steps: - fetch stored content - parse old content - check if sequence matches (or if 0=ignore) - extract old attendee list + organizer (make unique) - delete in removed folders - send iMIP mail for all folders not found */ SOGoAppointment *apt; NSString *econtent; NSArray *removedUIDs; /* load existing content */ econtent = [self iCalString]; /* if nil, this is a new appointment */ apt = [[[SOGoAppointment alloc] initWithICalString:econtent] autorelease]; /* compare sequence if requested */ if (_v != 0) { // TODO } removedUIDs = [self attendeeUIDsFromAppointment:apt]; /* perform */ return [self deleteInUIDs:removedUIDs]; } - (NSException *)saveContentString:(NSString *)_iCalString { return [self saveContentString:_iCalString baseSequence:0]; } - (NSException *)delete { return [self deleteWithBaseSequence:0]; } /* message type */ - (NSString *)outlookMessageClass { return @"IPM.Appointment"; } @end /* SOGoAppointmentObject */