// $Id$
#include "SOGoAppointmentObject.h"
+#include <SOGoLogic/AgenorUserManager.h>
+#include <NGiCal/NGiCal.h>
#include "common.h"
@implementation SOGoAppointmentObject
return [self contentAsString];
}
+/* 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] email];
+ if ([email isNotNull]) {
+ uid = [um getUIDForEmail:email];
+ if ([uid isNotNull]) {
+ if ([uid hasPrefix:@"mailto:"])
+ uid = [uid substringFromIndex:7];
+ [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 email];
+ if (![email isNotNull]) continue;
+
+ uid = [um getUIDForEmail:email];
+ if (![uid isNotNull]) {
+ [self logWithFormat:@"Note: got no uid for email: '%@'", email];
+ continue;
+ }
+ if ([uid hasPrefix:@"mailto:"])
+ uid = [uid substringFromIndex:7];
+
+ if (![uids containsObject:uid])
+ [uids addObject:uid];
+ }
+
+ return uids;
+}
+
/* raw saving */
- (NSException *)primarySaveContentString:(NSString *)_iCalString {
return [super delete];
}
+/* folder management */
+
+- (id)_primaryLookupFolderForUID:(NSString *)_uid inContext:(id)_ctx {
+ // TODO: DUP to SOGoGroupFolder
+ NSException *error = nil;
+ NSArray *path;
+ id ctx, result;
+
+ if (_ctx == nil) _ctx = [[WOApplication application] context];
+
+ /* create subcontext, so that we don't destroy our environment */
+
+ if ((ctx = [_ctx createSubContext]) == nil) {
+ [self logWithFormat:@"ERROR: could not create SOPE subcontext!"];
+ return nil;
+ }
+
+ /* build path */
+
+ path = _uid != nil ? [NSArray arrayWithObjects:&_uid count:1] : nil;
+
+ /* traverse path */
+
+ result = [[ctx application] traversePathArray:path inContext:ctx
+ error:&error acquire:NO];
+ if (error != nil) {
+ [self logWithFormat:@"ERROR: folder lookup failed (uid=%@): %@",
+ _uid, error];
+ return nil;
+ }
+
+ [self debugWithFormat:@"Note: got folder for uid %@ path %@: %@",
+ _uid, [path componentsJoinedByString:@"=>"], result];
+ return result;
+}
+- (NSArray *)lookupCalendarFoldersForUIDs:(NSArray *)_uids inContext:(id)_ctx {
+ NSMutableArray *folders;
+ NSEnumerator *e;
+ NSString *uid;
+
+ if ([_uids count] == 0) return nil;
+ folders = [NSMutableArray arrayWithCapacity:16];
+ e = [_uids objectEnumerator];
+ while ((uid = [e nextObject])) {
+ id folder;
+
+ folder = [self _primaryLookupFolderForUID:uid inContext:nil];
+ folder = [folder lookupName:@"Calendar" inContext:nil acquire:NO];
+ if (![folder isNotNull]) {
+ [self logWithFormat:@"Note: did not find folder for uid: '%@'", uid];
+ continue;
+ }
+
+ [folders addObject:folder];
+ }
+ return folders;
+}
+
+/* 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])) {
+ 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 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 {
- delete in removed folders
- send iMIP mail for all folders not found
*/
-#warning TODO: implement proper "multi-saves"
- return [self primarySaveContentString:_iCal];
+ SOGoAppointment *oldApt, *newApt;
+ NSString *oldContent;
+ NSArray *oldUIDs, *newUIDs;
+ NSMutableArray *storeUIDs, *removedUIDs;
+ unsigned i, count;
+ NSException *storeError, *delError;
+
+ /* handle old content */
+
+ oldContent = [self iCalString]; /* if nil, this is a new appointment */
+ oldApt =
+ [[[SOGoAppointment alloc] initWithICalString:oldContent] autorelease];
+
+ /* compare sequence if requested */
+
+ if (_v != 0) {
+ // TODO
+ }
+
+ oldUIDs = [self attendeeUIDsFromAppointment:oldApt];
+
+ /* handle new content */
+
+ newApt = [[[SOGoAppointment alloc] initWithICalString:_iCal] autorelease];
+ newUIDs = [self attendeeUIDsFromAppointment: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:@"store: %@", storeUIDs];
+ [self debugWithFormat:@"remove: %@", removedUIDs];
+
+ /* perform */
+
+ storeError = [self saveContentString:_iCal inUIDs:storeUIDs];
+ delError = [self deleteInUIDs:removedUIDs];
+
+ // TODO: make compound
+ if (storeError) return storeError;
+ if (delError) return delError;
+
+ return nil;
}
- (NSException *)deleteWithBaseSequence:(int)_v {
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
*/
-#warning TODO: implement proper "multi-saves"
- return [self primaryDelete];
+ 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 {