2 Copyright (C) 2004-2005 SKYRIX Software AG
4 This file is part of OpenGroupware.org.
6 OGo is free software; you can redistribute it and/or modify it under
7 the terms of the GNU Lesser General Public License as published by the
8 Free Software Foundation; either version 2, or (at your option) any
11 OGo is distributed in the hope that it will be useful, but WITHOUT ANY
12 WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
14 License for more details.
16 You should have received a copy of the GNU Lesser General Public
17 License along with OGo; see the file COPYING. If not, write to the
18 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
22 #import <GDLContentStore/GCSFolder.h>
23 #import <SaxObjC/SaxObjC.h>
24 #import <NGCards/NGCards.h>
25 #import <NGObjWeb/SoObject+SoDAV.h>
26 #import <NGObjWeb/WOContext.h>
27 #import <NGObjWeb/WOMessage.h>
28 #import <NGExtensions/NGCalendarDateRange.h>
30 // #import <NGObjWeb/SoClassSecurityInfo.h>
31 #import <SOGo/SOGoCustomGroupFolder.h>
32 #import <SOGo/AgenorUserManager.h>
33 #import <SOGo/SOGoPermissions.h>
34 #import <SOGo/NSString+Utilities.h>
38 #import "SOGoAppointmentObject.h"
39 #import "SOGoTaskObject.h"
41 #import "SOGoAppointmentFolder.h"
43 #if APPLE_Foundation_LIBRARY || NeXT_Foundation_LIBRARY
44 @interface NSDate(UsedPrivates)
45 - (id)initWithTimeIntervalSince1970:(NSTimeInterval)_interval;
49 @implementation SOGoAppointmentFolder
51 static NGLogger *logger = nil;
52 static NSNumber *sharedYes = nil;
56 return [super version] + 1 /* v1 */;
62 static BOOL didInit = NO;
63 // SoClassSecurityInfo *securityInfo;
68 NSAssert2([super version] == 0,
69 @"invalid superclass (%@) version %i !",
70 NSStringFromClass([self superclass]), [super version]);
72 lm = [NGLoggerManager defaultLoggerManager];
73 logger = [lm loggerForDefaultKey:@"SOGoAppointmentFolderDebugEnabled"];
75 // securityInfo = [self soClassSecurityInfo];
76 // [securityInfo declareRole: SOGoRole_Delegate
77 // asDefaultForPermission: SoPerm_AddDocumentsImagesAndFiles];
78 // [securityInfo declareRole: SOGoRole_Delegate
79 // asDefaultForPermission: SoPerm_ChangeImagesAndFiles];
80 // [securityInfo declareRoles: [NSArray arrayWithObjects:
82 // SOGoRole_Assistant, nil]
83 // asDefaultForPermission: SoPerm_View];
85 sharedYes = [[NSNumber numberWithBool:YES] retain];
90 [self->uidToFilename release];
103 - (NSArray *) calendarUIDs
105 /* this is used for group calendars (this folder just returns itself) */
108 s = [[self container] nameInContainer];
109 return [s isNotNull] ? [NSArray arrayWithObjects:&s count:1] : nil;
114 - (BOOL) isValidAppointmentName: (NSString *)_key
116 return ([_key length] != 0);
119 - (id) lookupActionForCalDAVMethod: (NSString *)_key
121 SoSelectorInvocation *invocation;
124 name = [NSString stringWithFormat: @"%@:", [_key davMethodToObjC]];
126 invocation = [[SoSelectorInvocation alloc]
127 initWithSelectorNamed: name
128 addContextParameter: YES];
129 [invocation autorelease];
134 - (void) appendObject: (NSDictionary *) object
135 withBaseURL: (NSString *) baseURL
136 toREPORTResponse: (WOResponse *) r
138 SOGoContentObject *ocsObject;
139 NSString *c_name, *etagLine, *calString;
141 c_name = [object objectForKey: @"c_name"];
143 ocsObject = [SOGoContentObject objectWithName: c_name
146 [r appendContentString: @" <D:response>\r\n"];
147 [r appendContentString: @" <D:href>"];
148 [r appendContentString: baseURL];
149 if (![baseURL hasSuffix: @"/"])
150 [r appendContentString: @"/"];
151 [r appendContentString: c_name];
152 [r appendContentString: @"</D:href>\r\n"];
154 [r appendContentString: @" <D:propstat>\r\n"];
155 [r appendContentString: @" <D:prop>\r\n"];
156 etagLine = [NSString stringWithFormat: @" <D:getetag>%@</D:getetag>\r\n",
157 [ocsObject davEntityTag]];
158 [r appendContentString: etagLine];
159 [r appendContentString: @" </D:prop>\r\n"];
160 [r appendContentString: @" <D:status>HTTP/1.1 200 OK</D:status>\r\n"];
161 [r appendContentString: @" </D:propstat>\r\n"];
162 [r appendContentString: @" <C:calendar-data>"];
163 calString = [[ocsObject contentAsString] stringByEscapingXMLString];
164 [r appendContentString: calString];
165 [r appendContentString: @"</C:calendar-data>\r\n"];
166 [r appendContentString: @" </D:response>\r\n"];
169 - (void) _appendTimeRange: (id <DOMElement>) timeRangeElement
170 toFilter: (NSMutableDictionary *) filter
172 NSCalendarDate *parsedDate;
174 parsedDate = [[timeRangeElement attribute: @"start"] asCalendarDate];
175 [filter setObject: parsedDate forKey: @"start"];
176 parsedDate = [[timeRangeElement attribute: @"end"] asCalendarDate];
177 [filter setObject: parsedDate forKey: @"end"];
180 - (NSDictionary *) _parseCalendarFilter: (id <DOMElement>) filterElement
182 NSMutableDictionary *filterData;
183 id <DOMNode> parentNode;
184 id <DOMNodeList> ranges;
185 NSString *componentName;
187 parentNode = [filterElement parentNode];
188 if ([[parentNode tagName] isEqualToString: @"comp-filter"]
189 && [[parentNode attribute: @"name"] isEqualToString: @"VCALENDAR"])
191 componentName = [[filterElement attribute: @"name"] lowercaseString];
192 filterData = [NSMutableDictionary new];
193 [filterData autorelease];
194 [filterData setObject: componentName forKey: @"name"];
195 ranges = [filterElement getElementsByTagName: @"time-range"];
197 [self _appendTimeRange: [ranges objectAtIndex: 0]
198 toFilter: filterData];
206 - (NSArray *) _parseCalendarFilters: (id <DOMElement>) parentNode
208 NSEnumerator *children;
210 NSMutableArray *filters;
211 NSDictionary *filter;
213 filters = [NSMutableArray new];
215 children = [[parentNode getElementsByTagName: @"comp-filter"] objectEnumerator];
216 node = [children nextObject];
219 filter = [self _parseCalendarFilter: node];
221 [filters addObject: filter];
222 node = [children nextObject];
228 - (void) _appendComponentsMatchingFilters: (NSArray *) filters
229 toResponse: (WOResponse *) response
230 inContext: (WOContext *) context
233 unsigned int count, max;
234 NSDictionary *currentFilter, *appointment;
235 NSEnumerator *appointments;
238 baseURL = [self baseURLInContext: context];
240 max = [filters count];
241 for (count = 0; count < max; count++)
243 currentFilter = [filters objectAtIndex: 0];
244 apts = [self fetchCoreInfosFrom: [currentFilter objectForKey: @"start"]
245 to: [currentFilter objectForKey: @"end"]
246 component: [currentFilter objectForKey: @"name"]];
247 appointments = [apts objectEnumerator];
248 appointment = [appointments nextObject];
251 [self appendObject: appointment
253 toREPORTResponse: response];
254 appointment = [appointments nextObject];
259 - (id) davCalendarQuery: (id) context
263 id <DOMDocument> document;
265 r = [context response];
267 [r setContentEncoding: NSUTF8StringEncoding];
268 [r setHeader: @"text/xml; charset=\"utf-8\"" forKey: @"content-type"];
269 [r setHeader: @"no-cache" forKey: @"pragma"];
270 [r setHeader: @"no-cache" forKey: @"cache-control"];
271 [r appendContentString:@"<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n"];
272 [r appendContentString: @"<D:multistatus xmlns:D=\"DAV:\""
273 @" xmlns:C=\"urn:ietf:params:xml:ns:caldav\">\r\n"];
275 document = [[context request] contentAsDOMDocument];
276 filters = [self _parseCalendarFilters: [document documentElement]];
277 [self _appendComponentsMatchingFilters: filters
280 [r appendContentString:@"</D:multistatus>\r\n"];
285 - (Class) objectClassForContent: (NSString *) content
287 iCalCalendar *calendar;
294 calendar = [iCalCalendar parseSingleFromSource: content];
297 elements = [calendar allObjects];
298 if ([elements count])
300 firstTag = [[[elements objectAtIndex: 0] tag] uppercaseString];
301 if ([firstTag isEqualToString: @"VEVENT"])
302 objectClass = [SOGoAppointmentObject class];
303 else if ([firstTag isEqualToString: @"VTODO"])
304 objectClass = [SOGoTaskObject class];
311 - (id) deduceObjectForName: (NSString *)_key
319 request = [_ctx request];
320 method = [request method];
321 if ([method isEqualToString: @"PUT"])
322 objectClass = [self objectClassForContent: [request contentAsString]];
324 objectClass = [self objectClassForResourceNamed: _key];
327 obj = [objectClass objectWithName: _key inContainer: self];
334 - (BOOL) requestNamedIsHandledLater: (NSString *) name
335 inContext: (WOContext *) context
337 return [name isEqualToString: @"OPTIONS"];
340 - (id) lookupName: (NSString *)_key
348 /* first check attributes directly bound to the application */
349 handledLater = [self requestNamedIsHandledLater: _key inContext: _ctx];
354 obj = [super lookupName:_key inContext:_ctx acquire:NO];
357 if ([_key hasPrefix: @"{urn:ietf:params:xml:ns:caldav}"])
359 = [self lookupActionForCalDAVMethod: [_key substringFromIndex: 31]];
360 else if ([self isValidAppointmentName:_key])
362 url = [[[_ctx request] uri] urlWithoutParameters];
363 if ([url hasSuffix: @"AsTask"])
364 obj = [SOGoTaskObject objectWithName: _key
366 else if ([url hasSuffix: @"AsAppointment"])
367 obj = [SOGoAppointmentObject objectWithName: _key
370 obj = [self deduceObjectForName: _key
375 obj = [NSException exceptionWithHTTPStatus:404 /* Not Found */];
381 - (NSArray *) davComplianceClassesInContext: (id)_ctx
383 NSMutableArray *classes;
384 NSArray *primaryClasses;
386 classes = [NSMutableArray new];
387 [classes autorelease];
389 primaryClasses = [super davComplianceClassesInContext: _ctx];
391 [classes addObjectsFromArray: primaryClasses];
392 [classes addObject: @"access-control"];
393 [classes addObject: @"calendar-access"];
398 - (NSString *) groupDavResourceType
400 return @"vevent-collection";
403 /* vevent UID handling */
405 - (NSString *) resourceNameForEventUID: (NSString *)_u
406 inFolder: (GCSFolder *)_f
408 static NSArray *nameFields = nil;
409 EOQualifier *qualifier;
412 if (![_u isNotNull]) return nil;
414 [self errorWithFormat:@"(%s): missing folder for fetch!",
415 __PRETTY_FUNCTION__];
419 if (nameFields == nil)
420 nameFields = [[NSArray alloc] initWithObjects:@"c_name", nil];
422 qualifier = [EOQualifier qualifierWithQualifierFormat:@"uid = %@", _u];
423 records = [_f fetchFields: nameFields matchingQualifier: qualifier];
425 if ([records count] == 1)
426 return [[records objectAtIndex:0] valueForKey:@"c_name"];
427 if ([records count] == 0)
430 [self errorWithFormat:
431 @"The storage contains more than file with the same UID!"];
432 return [[records objectAtIndex:0] valueForKey:@"c_name"];
435 - (NSString *) resourceNameForEventUID: (NSString *) _uid
441 if (![_uid isNotNull])
443 if ((rname = [self->uidToFilename objectForKey:_uid]) != nil)
444 return [rname isNotNull] ? rname : nil;
446 if ((folder = [self ocsFolder]) == nil) {
447 [self errorWithFormat:@"(%s): missing folder for fetch!",
448 __PRETTY_FUNCTION__];
452 if (self->uidToFilename == nil)
453 self->uidToFilename = [[NSMutableDictionary alloc] initWithCapacity:16];
455 if ((rname = [self resourceNameForEventUID:_uid inFolder:folder]) == nil)
456 [self->uidToFilename setObject:[NSNull null] forKey:_uid];
458 [self->uidToFilename setObject:rname forKey:_uid];
463 - (Class) objectClassForResourceNamed: (NSString *) c_name
465 EOQualifier *qualifier;
470 qualifier = [EOQualifier qualifierWithQualifierFormat:@"c_name = %@", c_name];
471 records = [[self ocsFolder] fetchFields: [NSArray arrayWithObject: @"component"]
472 matchingQualifier: qualifier];
476 component = [[records objectAtIndex:0] valueForKey: @"component"];
477 if ([component isEqualToString: @"vevent"])
478 objectClass = [SOGoAppointmentObject class];
479 else if ([component isEqualToString: @"vtodo"])
480 objectClass = [SOGoTaskObject class];
492 - (NSMutableDictionary *) fixupRecord: (NSDictionary *) _record
493 fetchRange: (NGCalendarDateRange *) _r
495 NSMutableDictionary *md;
498 md = [[_record mutableCopy] autorelease];
500 if ((tmp = [_record objectForKey:@"startdate"])) {
501 tmp = [[NSCalendarDate alloc] initWithTimeIntervalSince1970:
502 (NSTimeInterval)[tmp unsignedIntValue]];
503 [tmp setTimeZone: [self userTimeZone]];
504 if (tmp) [md setObject:tmp forKey:@"startDate"];
508 [self logWithFormat:@"missing 'startdate' in record?"];
510 if ((tmp = [_record objectForKey:@"enddate"])) {
511 tmp = [[NSCalendarDate alloc] initWithTimeIntervalSince1970:
512 (NSTimeInterval)[tmp unsignedIntValue]];
513 [tmp setTimeZone: [self userTimeZone]];
514 if (tmp) [md setObject:tmp forKey:@"endDate"];
518 [self logWithFormat:@"missing 'enddate' in record?"];
523 - (NSMutableDictionary *) fixupCycleRecord: (NSDictionary *) _record
524 cycleRange: (NGCalendarDateRange *) _r
526 NSMutableDictionary *md;
529 md = [[_record mutableCopy] autorelease];
532 tmp = [_r startDate];
533 [tmp setTimeZone:[self userTimeZone]];
534 [md setObject:tmp forKey:@"startDate"];
536 [tmp setTimeZone:[self userTimeZone]];
537 [md setObject:tmp forKey:@"endDate"];
542 - (void) _flattenCycleRecord: (NSDictionary *) _row
543 forRange: (NGCalendarDateRange *) _r
544 intoArray: (NSMutableArray *) _ma
546 NSMutableDictionary *row;
547 NSDictionary *cycleinfo;
548 NSCalendarDate *startDate, *endDate;
549 NGCalendarDateRange *fir;
550 NSArray *rules, *exRules, *exDates, *ranges;
553 cycleinfo = [[_row objectForKey:@"cycleinfo"] propertyList];
554 if (cycleinfo == nil) {
555 [self errorWithFormat:@"cyclic record doesn't have cycleinfo -> %@", _row];
559 row = [self fixupRecord:_row fetchRange: _r];
560 [row removeObjectForKey:@"cycleinfo"];
561 [row setObject:sharedYes forKey:@"isRecurrentEvent"];
563 startDate = [row objectForKey:@"startDate"];
564 endDate = [row objectForKey:@"endDate"];
565 fir = [NGCalendarDateRange calendarDateRangeWithStartDate:startDate
567 rules = [cycleinfo objectForKey:@"rules"];
568 exRules = [cycleinfo objectForKey:@"exRules"];
569 exDates = [cycleinfo objectForKey:@"exDates"];
571 ranges = [iCalRecurrenceCalculator recurrenceRangesWithinCalendarDateRange:_r
572 firstInstanceCalendarDateRange:fir
573 recurrenceRules:rules
574 exceptionRules:exRules
575 exceptionDates:exDates];
576 count = [ranges count];
577 for (i = 0; i < count; i++) {
578 NGCalendarDateRange *rRange;
581 rRange = [ranges objectAtIndex:i];
582 fixedRow = [self fixupCycleRecord:row cycleRange:rRange];
583 if (fixedRow != nil) [_ma addObject:fixedRow];
587 - (NSArray *) fixupRecords: (NSArray *) _records
588 fetchRange: (NGCalendarDateRange *) _r
590 // TODO: is the result supposed to be sorted by date?
594 if (_records == nil) return nil;
595 if ((count = [_records count]) == 0)
598 ma = [NSMutableArray arrayWithCapacity:count];
599 for (i = 0; i < count; i++) {
600 id row; // TODO: what is the type of the record?
602 row = [_records objectAtIndex:i];
603 row = [self fixupRecord:row fetchRange:_r];
604 if (row != nil) [ma addObject:row];
609 - (NSArray *) fixupCyclicRecords: (NSArray *) _records
610 fetchRange: (NGCalendarDateRange *) _r
612 // TODO: is the result supposed to be sorted by date?
616 if (_records == nil) return nil;
617 if ((count = [_records count]) == 0)
620 ma = [NSMutableArray arrayWithCapacity:count];
621 for (i = 0; i < count; i++) {
622 id row; // TODO: what is the type of the record?
624 row = [_records objectAtIndex:i];
625 [self _flattenCycleRecord:row forRange:_r intoArray:ma];
630 - (NSString *) _sqlStringForComponent: (id) _component
637 if ([_component isKindOfClass: [NSArray class]])
638 components = _component;
640 components = [NSArray arrayWithObject: _component];
643 = [NSString stringWithFormat: @" AND (component = '%@')",
644 [components componentsJoinedByString: @"' OR component = '"]];
652 - (NSString *) _sqlStringRangeFrom: (NSCalendarDate *) _startDate
653 to: (NSCalendarDate *) _endDate
655 unsigned int start, end;
657 start = (unsigned int) [_startDate timeIntervalSince1970];
658 end = (unsigned int) [_endDate timeIntervalSince1970];
660 return [NSString stringWithFormat:
661 @" AND (startdate <= %d) AND (enddate >= %d)",
665 - (NSArray *) fetchFields: (NSArray *) _fields
666 fromFolder: (GCSFolder *) _folder
667 from: (NSCalendarDate *) _startDate
668 to: (NSCalendarDate *) _endDate
669 component: (id) _component
671 EOQualifier *qualifier;
672 NSMutableArray *fields, *ma = nil;
674 NSString *sql, *dateSqlString, *componentSqlString; /* , *owner; */
675 NGCalendarDateRange *r;
677 if (_folder == nil) {
678 [self errorWithFormat:@"(%s): missing folder for fetch!",
679 __PRETTY_FUNCTION__];
683 if (_startDate && _endDate)
685 r = [NGCalendarDateRange calendarDateRangeWithStartDate: _startDate
687 dateSqlString = [self _sqlStringRangeFrom: _startDate to: _endDate];
695 componentSqlString = [self _sqlStringForComponent: _component];
697 /* prepare mandatory fields */
699 fields = [NSMutableArray arrayWithArray: _fields];
700 [fields addObject: @"uid"];
701 [fields addObject: @"startdate"];
702 [fields addObject: @"enddate"];
705 [self debugWithFormat:@"should fetch (%@=>%@) ...", _startDate, _endDate];
707 sql = [NSString stringWithFormat: @"(iscycle = 0)%@%@",
708 dateSqlString, componentSqlString];
710 /* fetch non-recurrent apts first */
711 qualifier = [EOQualifier qualifierWithQualifierFormat: sql];
713 records = [_folder fetchFields: fields matchingQualifier: qualifier];
717 records = [self fixupRecords: records fetchRange: r];
719 [self debugWithFormat: @"fetched %i records: %@",
720 [records count], records];
721 ma = [NSMutableArray arrayWithArray: records];
724 /* fetch recurrent apts now */
725 sql = [NSString stringWithFormat: @"(iscycle = 1)%@%@",
726 dateSqlString, componentSqlString];
727 qualifier = [EOQualifier qualifierWithQualifierFormat: sql];
729 [fields addObject: @"cycleinfo"];
731 records = [_folder fetchFields: fields matchingQualifier: qualifier];
735 [self debugWithFormat: @"fetched %i cyclic records: %@",
736 [records count], records];
738 records = [self fixupCyclicRecords: records fetchRange: r];
740 ma = [NSMutableArray arrayWithCapacity: [records count]];
742 // owner = [self ownerInContext: nil];
743 [ma addObjectsFromArray: records];
747 [self errorWithFormat: @"(%s): fetch failed!", __PRETTY_FUNCTION__];
751 /* NOTE: why do we sort here?
752 This probably belongs to UI but cannot be achieved as fast there as
753 we can do it here because we're operating on a mutable array -
754 having the apts sorted is never a bad idea, though
756 [ma sortUsingSelector: @selector (compareAptsAscending:)];
758 [self debugWithFormat:@"returning %i records", [ma count]];
760 // [ma makeObjectsPerform: @selector (setObject:forKey:)
762 // withObject: @"owner"];
767 /* override this in subclasses */
768 - (NSArray *) fetchFields: (NSArray *) _fields
769 from: (NSCalendarDate *) _startDate
770 to: (NSCalendarDate *) _endDate
771 component: (id) _component
775 if ((folder = [self ocsFolder]) == nil) {
776 [self errorWithFormat:@"(%s): missing folder for fetch!",
777 __PRETTY_FUNCTION__];
781 return [self fetchFields: _fields fromFolder: folder
782 from: _startDate to: _endDate
783 component: _component];
787 - (NSArray *) fetchFreeBusyInfosFrom: (NSCalendarDate *) _startDate
788 to: (NSCalendarDate *) _endDate
790 static NSArray *infos = nil; // TODO: move to a plist file
793 infos = [[NSArray alloc] initWithObjects: @"partmails", @"partstates",
794 @"isopaque", @"status", nil];
796 return [self fetchFields: infos from: _startDate to: _endDate
797 component: @"vevent"];
800 - (NSArray *) fetchCoreInfosFrom: (NSCalendarDate *) _startDate
801 to: (NSCalendarDate *) _endDate
802 component: (id) _component
804 static NSArray *infos = nil; // TODO: move to a plist file
807 infos = [[NSArray alloc] initWithObjects:
808 @"c_name", @"component",
809 @"title", @"location", @"orgmail",
810 @"status", @"ispublic",
811 @"isallday", @"isopaque",
812 @"participants", @"partmails",
813 @"partstates", @"sequence", @"priority", nil];
815 return [self fetchFields: infos from: _startDate to: _endDate component: _component];
818 - (void) deleteEntriesWithIds: (NSArray *) ids
821 unsigned int count, max;
822 NSString *currentId, *currentUser;
826 context = [[WOApplication application] context];
827 currentUser = [[context activeUser] login];
830 for (count = 0; count < max; count++)
832 currentId = [ids objectAtIndex: count];
834 = [self objectClassForResourceNamed: currentId];
835 deleteObject = [objectClass objectWithName: currentId
837 if ([currentUser isEqualToString: [deleteObject ownerInContext: nil]])
839 [deleteObject delete];
840 [deleteObject primaryDelete];
847 - (NSString *) baseURLForAptWithUID: (NSString *)_uid
850 // TODO: who calls this?
853 if ([_uid length] == 0)
856 url = [self baseURLInContext:_ctx];
857 if (![url hasSuffix:@"/"])
858 url = [url stringByAppendingString:@"/"];
860 // TODO: this should run a query to determine the uid!
861 return [url stringByAppendingString:_uid];
864 /* folder management */
866 - (id) lookupHomeFolderForUID: (NSString *) _uid
869 // TODO: DUP to SOGoGroupFolder
870 NSException *error = nil;
874 if (![_uid isNotNull])
877 if (_ctx == nil) _ctx = [[WOApplication application] context];
879 /* create subcontext, so that we don't destroy our environment */
881 if ((ctx = [_ctx createSubContext]) == nil) {
882 [self errorWithFormat:@"could not create SOPE subcontext!"];
888 path = _uid != nil ? [NSArray arrayWithObjects:&_uid count:1] : nil;
892 result = [[ctx application] traversePathArray:path inContext:ctx
893 error:&error acquire:NO];
895 [self errorWithFormat:@"folder lookup failed (uid=%@): %@",
900 [self debugWithFormat:@"Note: got folder for uid %@ path %@: %@",
901 _uid, [path componentsJoinedByString:@"=>"], result];
905 - (SOGoAppointmentFolder *) lookupCalendarFolderForUID: (NSString *) uid
907 SOGoFolder *upperContainer;
908 SOGoUserFolder *userFolder;
909 SOGoAppointmentFolder *calendarFolder;
911 upperContainer = [[self container] container];
912 userFolder = [SOGoUserFolder objectWithName: uid
913 inContainer: upperContainer];
914 calendarFolder = [SOGoAppointmentFolder objectWithName: @"Calendar"
915 inContainer: userFolder];
917 setOCSPath: [NSString stringWithFormat: @"/Users/%@/Calendar", uid]];
918 [calendarFolder setOwner: uid];
920 return calendarFolder;
923 - (NSArray *) lookupCalendarFoldersForUIDs: (NSArray *) _uids
926 /* Note: can return NSNull objects in the array! */
927 NSMutableArray *folders;
931 if ([_uids count] == 0) return nil;
932 folders = [NSMutableArray arrayWithCapacity:16];
933 e = [_uids objectEnumerator];
934 while ((uid = [e nextObject])) {
937 folder = [self lookupCalendarFolderForUID: uid];
938 if (![folder isNotNull])
939 [self logWithFormat:@"Note: did not find folder for uid: '%@'", uid];
941 /* Note: intentionally add 'null' folders to allow a mapping */
942 [folders addObject:folder ? folder : [NSNull null]];
947 - (NSArray *) lookupFreeBusyObjectsForUIDs: (NSArray *) _uids
950 /* Note: can return NSNull objects in the array! */
951 NSMutableArray *objs;
955 if ([_uids count] == 0) return nil;
956 objs = [NSMutableArray arrayWithCapacity:16];
957 e = [_uids objectEnumerator];
958 while ((uid = [e nextObject])) {
961 obj = [self lookupHomeFolderForUID:uid inContext:nil];
962 if ([obj isNotNull]) {
963 obj = [obj lookupName:@"freebusy.ifb" inContext:nil acquire:NO];
964 if ([obj isKindOfClass:[NSException class]])
967 if (![obj isNotNull])
968 [self logWithFormat:@"Note: did not find freebusy.ifb for uid: '%@'", uid];
970 /* Note: intentionally add 'null' folders to allow a mapping */
971 [objs addObject:obj ? obj : [NSNull null]];
976 - (NSArray *) uidsFromICalPersons: (NSArray *) _persons
978 /* Note: can return NSNull objects in the array! */
979 NSMutableArray *uids;
980 AgenorUserManager *um;
986 count = [_persons count];
987 uids = [NSMutableArray arrayWithCapacity:count + 1];
988 um = [AgenorUserManager sharedUserManager];
990 for (i = 0; i < count; i++) {
995 person = [_persons objectAtIndex:i];
996 email = [person rfc822Email];
997 if ([email isNotNull]) {
998 uid = [um getUIDForEmail:email];
1003 [uids addObject:(uid != nil ? uid : (id)[NSNull null])];
1008 - (NSArray *)lookupCalendarFoldersForICalPerson: (NSArray *) _persons
1009 inContext: (id) _ctx
1011 /* Note: can return NSNull objects in the array! */
1014 if ((uids = [self uidsFromICalPersons:_persons]) == nil)
1017 return [self lookupCalendarFoldersForUIDs:uids inContext:_ctx];
1020 - (id) lookupGroupFolderForUIDs: (NSArray *) _uids
1023 SOGoCustomGroupFolder *folder;
1028 folder = [[SOGoCustomGroupFolder alloc] initWithUIDs:_uids inContainer:self];
1029 return [folder autorelease];
1032 - (id) lookupGroupCalendarFolderForUIDs: (NSArray *) _uids
1033 inContext: (id) _ctx
1035 SOGoCustomGroupFolder *folder;
1037 if ((folder = [self lookupGroupFolderForUIDs:_uids inContext:_ctx]) == nil)
1040 folder = [folder lookupName:@"Calendar" inContext:_ctx acquire:NO];
1041 if (![folder isNotNull])
1043 if ([folder isKindOfClass:[NSException class]]) {
1044 [self debugWithFormat:@"Note: could not lookup 'Calendar' in folder: %@",
1054 - (NSArray *) fetchAllSOGoAppointments
1057 Note: very expensive method, do not use unless absolutely required.
1058 returns an array of SOGoAppointment objects.
1060 Note that we can leave out the filenames, supposed to be stored
1061 in the 'uid' field of the iCalendar object!
1063 NSMutableArray *events;
1064 NSDictionary *files;
1065 NSEnumerator *contents;
1068 /* fetch all raw contents */
1070 files = [self fetchContentStringsAndNamesOfAllObjects];
1071 if (![files isNotNull]) return nil;
1072 if ([files isKindOfClass:[NSException class]]) return (id)files;
1074 /* transform to SOGo appointments */
1076 events = [NSMutableArray arrayWithCapacity:[files count]];
1077 contents = [files objectEnumerator];
1078 while ((content = [contents nextObject]) != nil)
1079 [events addObject: [iCalCalendar parseSingleFromSource: content]];
1086 - (NSString *) folderType
1088 return @"Appointment";
1091 - (NSString *) outlookFolderClass
1093 return @"IPF.Appointment";
1096 @end /* SOGoAppointmentFolder */