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 <NGExtensions/NGCalendarDateRange.h>
28 #import <NGObjWeb/SoClassSecurityInfo.h>
29 #import <SOGo/SOGoCustomGroupFolder.h>
30 #import <SOGo/AgenorUserManager.h>
31 #import <SOGo/SOGoPermissions.h>
32 #import <SOGo/NSString+Utilities.h>
36 #import "SOGoAppointmentObject.h"
37 #import "SOGoTaskObject.h"
39 #import "SOGoAppointmentFolder.h"
41 #if APPLE_Foundation_LIBRARY || NeXT_Foundation_LIBRARY
42 @interface NSDate(UsedPrivates)
43 - (id)initWithTimeIntervalSince1970:(NSTimeInterval)_interval;
47 @implementation SOGoAppointmentFolder
49 static NGLogger *logger = nil;
50 static NSNumber *sharedYes = nil;
54 return [super version] + 1 /* v1 */;
60 static BOOL didInit = NO;
61 SoClassSecurityInfo *securityInfo;
66 NSAssert2([super version] == 0,
67 @"invalid superclass (%@) version %i !",
68 NSStringFromClass([self superclass]), [super version]);
70 lm = [NGLoggerManager defaultLoggerManager];
71 logger = [lm loggerForDefaultKey:@"SOGoAppointmentFolderDebugEnabled"];
73 // securityInfo = [self soClassSecurityInfo];
74 // [securityInfo declareRole: SOGoRole_Delegate
75 // asDefaultForPermission: SoPerm_AddDocumentsImagesAndFiles];
76 // [securityInfo declareRole: SOGoRole_Delegate
77 // asDefaultForPermission: SoPerm_ChangeImagesAndFiles];
78 // [securityInfo declareRoles: [NSArray arrayWithObjects:
80 // SOGoRole_Assistant, nil]
81 // asDefaultForPermission: SoPerm_View];
83 sharedYes = [[NSNumber numberWithBool:YES] retain];
88 [self->uidToFilename release];
101 - (NSArray *) calendarUIDs
103 /* this is used for group calendars (this folder just returns itself) */
106 s = [[self container] nameInContainer];
107 return [s isNotNull] ? [NSArray arrayWithObjects:&s count:1] : nil;
112 - (BOOL) isValidAppointmentName: (NSString *)_key
114 return ([_key length] != 0);
117 - (id) lookupActionForCalDAVMethod: (NSString *)_key
119 SoSelectorInvocation *invocation;
122 name = [NSString stringWithFormat: @"%@:", [_key davMethodToObjC]];
124 invocation = [[SoSelectorInvocation alloc]
125 initWithSelectorNamed: name
126 addContextParameter: YES];
127 [invocation autorelease];
132 - (void) appendObject: (NSDictionary *) object
133 withBaseURL: (NSString *) baseURL
134 toREPORTResponse: (WOResponse *) r
136 SOGoContentObject *ocsObject;
137 NSString *c_name, *etagLine, *calString;
139 c_name = [object objectForKey: @"c_name"];
141 ocsObject = [SOGoContentObject objectWithName: c_name
144 [r appendContentString: @" <D:response>\r\n"];
145 [r appendContentString: @" <D:href>"];
146 [r appendContentString: baseURL];
147 if (![baseURL hasSuffix: @"/"])
148 [r appendContentString: @"/"];
149 [r appendContentString: c_name];
150 [r appendContentString: @"</D:href>\r\n"];
152 [r appendContentString: @" <D:propstat>\r\n"];
153 [r appendContentString: @" <D:prop>\r\n"];
154 etagLine = [NSString stringWithFormat: @" <D:getetag>%@</D:getetag>\r\n",
155 [ocsObject davEntityTag]];
156 [r appendContentString: etagLine];
157 [r appendContentString: @" </D:prop>\r\n"];
158 [r appendContentString: @" <D:status>HTTP/1.1 200 OK</D:status>\r\n"];
159 [r appendContentString: @" </D:propstat>\r\n"];
160 [r appendContentString: @" <C:calendar-data>"];
161 calString = [[ocsObject contentAsString] stringByEscapingXMLString];
162 [r appendContentString: calString];
163 [r appendContentString: @"</C:calendar-data>\r\n"];
164 [r appendContentString: @" </D:response>\r\n"];
167 - (void) _appendTimeRange: (id <DOMElement>) timeRangeElement
168 toFilter: (NSMutableDictionary *) filter
170 NSCalendarDate *parsedDate;
172 parsedDate = [[timeRangeElement attribute: @"start"] asCalendarDate];
173 [filter setObject: parsedDate forKey: @"start"];
174 parsedDate = [[timeRangeElement attribute: @"end"] asCalendarDate];
175 [filter setObject: parsedDate forKey: @"end"];
178 - (NSDictionary *) _parseCalendarFilter: (id <DOMElement>) filterElement
180 NSMutableDictionary *filterData;
181 id <DOMNode> parentNode;
182 id <DOMNodeList> ranges;
183 NSString *componentName;
185 parentNode = [filterElement parentNode];
186 if ([[parentNode tagName] isEqualToString: @"comp-filter"]
187 && [[parentNode attribute: @"name"] isEqualToString: @"VCALENDAR"])
189 componentName = [[filterElement attribute: @"name"] lowercaseString];
190 filterData = [NSMutableDictionary new];
191 [filterData autorelease];
192 [filterData setObject: componentName forKey: @"name"];
193 ranges = [filterElement getElementsByTagName: @"time-range"];
195 [self _appendTimeRange: [ranges objectAtIndex: 0]
196 toFilter: filterData];
204 - (NSArray *) _parseCalendarFilters: (id <DOMElement>) parentNode
206 NSEnumerator *children;
208 NSMutableArray *filters;
209 NSDictionary *filter;
211 filters = [NSMutableArray new];
213 children = [[parentNode getElementsByTagName: @"comp-filter"] objectEnumerator];
214 node = [children nextObject];
217 filter = [self _parseCalendarFilter: node];
219 [filters addObject: filter];
220 node = [children nextObject];
226 - (void) _appendComponentsMatchingFilters: (NSArray *) filters
227 toResponse: (WOResponse *) response
228 inContext: (WOContext *) context
231 unsigned int count, max;
232 NSDictionary *currentFilter, *appointment;
233 NSEnumerator *appointments;
236 baseURL = [self baseURLInContext: context];
238 max = [filters count];
239 for (count = 0; count < max; count++)
241 currentFilter = [filters objectAtIndex: 0];
242 apts = [self fetchCoreInfosFrom: [currentFilter objectForKey: @"start"]
243 to: [currentFilter objectForKey: @"end"]
244 component: [currentFilter objectForKey: @"name"]];
245 appointments = [apts objectEnumerator];
246 appointment = [appointments nextObject];
249 [self appendObject: appointment
251 toREPORTResponse: response];
252 appointment = [appointments nextObject];
257 - (id) davCalendarQuery: (id) context
261 id <DOMDocument> document;
263 r = [context response];
265 [r setContentEncoding: NSUTF8StringEncoding];
266 [r setHeader: @"text/xml; charset=\"utf-8\"" forKey: @"content-type"];
267 [r setHeader: @"no-cache" forKey: @"pragma"];
268 [r setHeader: @"no-cache" forKey: @"cache-control"];
269 [r appendContentString:@"<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n"];
270 [r appendContentString: @"<D:multistatus xmlns:D=\"DAV:\""
271 @" xmlns:C=\"urn:ietf:params:xml:ns:caldav\">\r\n"];
273 document = [[context request] contentAsDOMDocument];
274 filters = [self _parseCalendarFilters: [document documentElement]];
275 [self _appendComponentsMatchingFilters: filters
278 [r appendContentString:@"</D:multistatus>\r\n"];
283 - (Class) objectClassForContent: (NSString *) content
285 iCalCalendar *calendar;
292 calendar = [iCalCalendar parseSingleFromSource: content];
295 elements = [calendar allObjects];
296 if ([elements count])
298 firstTag = [[[elements objectAtIndex: 0] tag] uppercaseString];
299 if ([firstTag isEqualToString: @"VEVENT"])
300 objectClass = [SOGoAppointmentObject class];
301 else if ([firstTag isEqualToString: @"VTODO"])
302 objectClass = [SOGoTaskObject class];
309 - (id) deduceObjectForName: (NSString *)_key
317 request = [_ctx request];
318 method = [request method];
319 if ([method isEqualToString: @"PUT"])
320 objectClass = [self objectClassForContent: [request contentAsString]];
322 objectClass = [self objectClassForResourceNamed: _key];
325 obj = [objectClass objectWithName: _key inContainer: self];
332 - (BOOL) requestNamedIsHandledLater: (NSString *) name
333 inContext: (WOContext *) context
335 return [name isEqualToString: @"OPTIONS"];
338 - (id) lookupName: (NSString *)_key
346 /* first check attributes directly bound to the application */
347 handledLater = [self requestNamedIsHandledLater: _key inContext: _ctx];
352 obj = [super lookupName:_key inContext:_ctx acquire:NO];
355 if ([_key hasPrefix: @"{urn:ietf:params:xml:ns:caldav}"])
357 = [self lookupActionForCalDAVMethod: [_key substringFromIndex: 31]];
358 else if ([self isValidAppointmentName:_key])
360 url = [[[_ctx request] uri] urlWithoutParameters];
361 if ([url hasSuffix: @"AsTask"])
362 obj = [SOGoTaskObject objectWithName: _key
364 else if ([url hasSuffix: @"AsAppointment"])
365 obj = [SOGoAppointmentObject objectWithName: _key
368 obj = [self deduceObjectForName: _key
373 obj = [NSException exceptionWithHTTPStatus:404 /* Not Found */];
379 - (NSArray *) davComplianceClassesInContext: (id)_ctx
381 NSMutableArray *classes;
382 NSArray *primaryClasses;
384 classes = [NSMutableArray new];
385 [classes autorelease];
387 primaryClasses = [super davComplianceClassesInContext: _ctx];
389 [classes addObjectsFromArray: primaryClasses];
390 [classes addObject: @"access-control"];
391 [classes addObject: @"calendar-access"];
396 - (NSString *) groupDavResourceType
398 return @"vevent-collection";
401 /* vevent UID handling */
403 - (NSString *) resourceNameForEventUID: (NSString *)_u
404 inFolder: (GCSFolder *)_f
406 static NSArray *nameFields = nil;
407 EOQualifier *qualifier;
410 if (![_u isNotNull]) return nil;
412 [self errorWithFormat:@"(%s): missing folder for fetch!",
413 __PRETTY_FUNCTION__];
417 if (nameFields == nil)
418 nameFields = [[NSArray alloc] initWithObjects:@"c_name", nil];
420 qualifier = [EOQualifier qualifierWithQualifierFormat:@"uid = %@", _u];
421 records = [_f fetchFields: nameFields matchingQualifier: qualifier];
423 if ([records count] == 1)
424 return [[records objectAtIndex:0] valueForKey:@"c_name"];
425 if ([records count] == 0)
428 [self errorWithFormat:
429 @"The storage contains more than file with the same UID!"];
430 return [[records objectAtIndex:0] valueForKey:@"c_name"];
433 - (NSString *) resourceNameForEventUID: (NSString *) _uid
439 if (![_uid isNotNull])
441 if ((rname = [self->uidToFilename objectForKey:_uid]) != nil)
442 return [rname isNotNull] ? rname : nil;
444 if ((folder = [self ocsFolder]) == nil) {
445 [self errorWithFormat:@"(%s): missing folder for fetch!",
446 __PRETTY_FUNCTION__];
450 if (self->uidToFilename == nil)
451 self->uidToFilename = [[NSMutableDictionary alloc] initWithCapacity:16];
453 if ((rname = [self resourceNameForEventUID:_uid inFolder:folder]) == nil)
454 [self->uidToFilename setObject:[NSNull null] forKey:_uid];
456 [self->uidToFilename setObject:rname forKey:_uid];
461 - (Class) objectClassForResourceNamed: (NSString *) c_name
463 EOQualifier *qualifier;
468 qualifier = [EOQualifier qualifierWithQualifierFormat:@"c_name = %@", c_name];
469 records = [[self ocsFolder] fetchFields: [NSArray arrayWithObject: @"component"]
470 matchingQualifier: qualifier];
474 component = [[records objectAtIndex:0] valueForKey: @"component"];
475 if ([component isEqualToString: @"vevent"])
476 objectClass = [SOGoAppointmentObject class];
477 else if ([component isEqualToString: @"vtodo"])
478 objectClass = [SOGoTaskObject class];
490 - (NSMutableDictionary *) fixupRecord: (NSDictionary *) _record
491 fetchRange: (NGCalendarDateRange *) _r
493 NSMutableDictionary *md;
496 md = [[_record mutableCopy] autorelease];
498 if ((tmp = [_record objectForKey:@"startdate"])) {
499 tmp = [[NSCalendarDate alloc] initWithTimeIntervalSince1970:
500 (NSTimeInterval)[tmp unsignedIntValue]];
501 [tmp setTimeZone: [self userTimeZone]];
502 if (tmp) [md setObject:tmp forKey:@"startDate"];
506 [self logWithFormat:@"missing 'startdate' in record?"];
508 if ((tmp = [_record objectForKey:@"enddate"])) {
509 tmp = [[NSCalendarDate alloc] initWithTimeIntervalSince1970:
510 (NSTimeInterval)[tmp unsignedIntValue]];
511 [tmp setTimeZone: [self userTimeZone]];
512 if (tmp) [md setObject:tmp forKey:@"endDate"];
516 [self logWithFormat:@"missing 'enddate' in record?"];
521 - (NSMutableDictionary *) fixupCycleRecord: (NSDictionary *) _record
522 cycleRange: (NGCalendarDateRange *) _r
524 NSMutableDictionary *md;
527 md = [[_record mutableCopy] autorelease];
530 tmp = [_r startDate];
531 [tmp setTimeZone:[self userTimeZone]];
532 [md setObject:tmp forKey:@"startDate"];
534 [tmp setTimeZone:[self userTimeZone]];
535 [md setObject:tmp forKey:@"endDate"];
540 - (void) _flattenCycleRecord: (NSDictionary *) _row
541 forRange: (NGCalendarDateRange *) _r
542 intoArray: (NSMutableArray *) _ma
544 NSMutableDictionary *row;
545 NSDictionary *cycleinfo;
546 NSCalendarDate *startDate, *endDate;
547 NGCalendarDateRange *fir;
548 NSArray *rules, *exRules, *exDates, *ranges;
551 cycleinfo = [[_row objectForKey:@"cycleinfo"] propertyList];
552 if (cycleinfo == nil) {
553 [self errorWithFormat:@"cyclic record doesn't have cycleinfo -> %@", _row];
557 row = [self fixupRecord:_row fetchRange: _r];
558 [row removeObjectForKey:@"cycleinfo"];
559 [row setObject:sharedYes forKey:@"isRecurrentEvent"];
561 startDate = [row objectForKey:@"startDate"];
562 endDate = [row objectForKey:@"endDate"];
563 fir = [NGCalendarDateRange calendarDateRangeWithStartDate:startDate
565 rules = [cycleinfo objectForKey:@"rules"];
566 exRules = [cycleinfo objectForKey:@"exRules"];
567 exDates = [cycleinfo objectForKey:@"exDates"];
569 ranges = [iCalRecurrenceCalculator recurrenceRangesWithinCalendarDateRange:_r
570 firstInstanceCalendarDateRange:fir
571 recurrenceRules:rules
572 exceptionRules:exRules
573 exceptionDates:exDates];
574 count = [ranges count];
575 for (i = 0; i < count; i++) {
576 NGCalendarDateRange *rRange;
579 rRange = [ranges objectAtIndex:i];
580 fixedRow = [self fixupCycleRecord:row cycleRange:rRange];
581 if (fixedRow != nil) [_ma addObject:fixedRow];
585 - (NSArray *) fixupRecords: (NSArray *) _records
586 fetchRange: (NGCalendarDateRange *) _r
588 // TODO: is the result supposed to be sorted by date?
592 if (_records == nil) return nil;
593 if ((count = [_records count]) == 0)
596 ma = [NSMutableArray arrayWithCapacity:count];
597 for (i = 0; i < count; i++) {
598 id row; // TODO: what is the type of the record?
600 row = [_records objectAtIndex:i];
601 row = [self fixupRecord:row fetchRange:_r];
602 if (row != nil) [ma addObject:row];
607 - (NSArray *) fixupCyclicRecords: (NSArray *) _records
608 fetchRange: (NGCalendarDateRange *) _r
610 // TODO: is the result supposed to be sorted by date?
614 if (_records == nil) return nil;
615 if ((count = [_records count]) == 0)
618 ma = [NSMutableArray arrayWithCapacity:count];
619 for (i = 0; i < count; i++) {
620 id row; // TODO: what is the type of the record?
622 row = [_records objectAtIndex:i];
623 [self _flattenCycleRecord:row forRange:_r intoArray:ma];
628 - (NSString *) _sqlStringForComponent: (id) _component
635 if ([_component isKindOfClass: [NSArray class]])
636 components = _component;
638 components = [NSArray arrayWithObject: _component];
641 = [NSString stringWithFormat: @" AND (component = '%@')",
642 [components componentsJoinedByString: @"' OR component = '"]];
650 - (NSString *) _sqlStringRangeFrom: (NSCalendarDate *) _startDate
651 to: (NSCalendarDate *) _endDate
653 unsigned int start, end;
655 start = (unsigned int) [_startDate timeIntervalSince1970];
656 end = (unsigned int) [_endDate timeIntervalSince1970];
658 return [NSString stringWithFormat:
659 @" AND (startdate <= %d) AND (enddate >= %d)",
663 - (NSArray *) fetchFields: (NSArray *) _fields
664 fromFolder: (GCSFolder *) _folder
665 from: (NSCalendarDate *) _startDate
666 to: (NSCalendarDate *) _endDate
667 component: (id) _component
669 EOQualifier *qualifier;
670 NSMutableArray *fields, *ma = nil;
672 NSString *sql, *dateSqlString, *componentSqlString; /* , *owner; */
673 NGCalendarDateRange *r;
675 if (_folder == nil) {
676 [self errorWithFormat:@"(%s): missing folder for fetch!",
677 __PRETTY_FUNCTION__];
681 if (_startDate && _endDate)
683 r = [NGCalendarDateRange calendarDateRangeWithStartDate: _startDate
685 dateSqlString = [self _sqlStringRangeFrom: _startDate to: _endDate];
693 componentSqlString = [self _sqlStringForComponent: _component];
695 /* prepare mandatory fields */
697 fields = [NSMutableArray arrayWithArray: _fields];
698 [fields addObject: @"uid"];
699 [fields addObject: @"startdate"];
700 [fields addObject: @"enddate"];
703 [self debugWithFormat:@"should fetch (%@=>%@) ...", _startDate, _endDate];
705 sql = [NSString stringWithFormat: @"(iscycle = 0)%@%@",
706 dateSqlString, componentSqlString];
708 /* fetch non-recurrent apts first */
709 qualifier = [EOQualifier qualifierWithQualifierFormat: sql];
711 records = [_folder fetchFields: fields matchingQualifier: qualifier];
715 records = [self fixupRecords: records fetchRange: r];
717 [self debugWithFormat: @"fetched %i records: %@",
718 [records count], records];
719 ma = [NSMutableArray arrayWithArray: records];
722 /* fetch recurrent apts now */
723 sql = [NSString stringWithFormat: @"(iscycle = 1)%@%@",
724 dateSqlString, componentSqlString];
725 qualifier = [EOQualifier qualifierWithQualifierFormat: sql];
727 [fields addObject: @"cycleinfo"];
729 records = [_folder fetchFields: fields matchingQualifier: qualifier];
733 [self debugWithFormat: @"fetched %i cyclic records: %@",
734 [records count], records];
736 records = [self fixupCyclicRecords: records fetchRange: r];
738 ma = [NSMutableArray arrayWithCapacity: [records count]];
740 // owner = [self ownerInContext: nil];
741 [ma addObjectsFromArray: records];
745 [self errorWithFormat: @"(%s): fetch failed!", __PRETTY_FUNCTION__];
749 /* NOTE: why do we sort here?
750 This probably belongs to UI but cannot be achieved as fast there as
751 we can do it here because we're operating on a mutable array -
752 having the apts sorted is never a bad idea, though
754 [ma sortUsingSelector: @selector (compareAptsAscending:)];
756 [self debugWithFormat:@"returning %i records", [ma count]];
758 // [ma makeObjectsPerform: @selector (setObject:forKey:)
760 // withObject: @"owner"];
765 /* override this in subclasses */
766 - (NSArray *) fetchFields: (NSArray *) _fields
767 from: (NSCalendarDate *) _startDate
768 to: (NSCalendarDate *) _endDate
769 component: (id) _component
773 if ((folder = [self ocsFolder]) == nil) {
774 [self errorWithFormat:@"(%s): missing folder for fetch!",
775 __PRETTY_FUNCTION__];
779 return [self fetchFields: _fields fromFolder: folder
780 from: _startDate to: _endDate
781 component: _component];
785 - (NSArray *) fetchFreeBusyInfosFrom: (NSCalendarDate *) _startDate
786 to: (NSCalendarDate *) _endDate
788 static NSArray *infos = nil; // TODO: move to a plist file
791 infos = [[NSArray alloc] initWithObjects: @"partmails", @"partstates",
792 @"isopaque", @"status", nil];
794 return [self fetchFields: infos from: _startDate to: _endDate
795 component: @"vevent"];
798 - (NSArray *) fetchCoreInfosFrom: (NSCalendarDate *) _startDate
799 to: (NSCalendarDate *) _endDate
800 component: (id) _component
802 static NSArray *infos = nil; // TODO: move to a plist file
805 infos = [[NSArray alloc] initWithObjects:
806 @"c_name", @"component",
807 @"title", @"location", @"orgmail",
808 @"status", @"ispublic",
809 @"isallday", @"isopaque",
810 @"participants", @"partmails",
811 @"partstates", @"sequence", @"priority", nil];
813 return [self fetchFields: infos from: _startDate to: _endDate component: _component];
816 - (void) deleteEntriesWithIds: (NSArray *) ids
819 unsigned int count, max;
820 NSString *currentId, *currentUser;
824 context = [[WOApplication application] context];
825 currentUser = [[context activeUser] login];
828 for (count = 0; count < max; count++)
830 currentId = [ids objectAtIndex: count];
832 = [self objectClassForResourceNamed: currentId];
833 deleteObject = [objectClass objectWithName: currentId
835 if ([currentUser isEqualToString: [deleteObject ownerInContext: nil]])
837 [deleteObject delete];
838 [deleteObject primaryDelete];
845 - (NSString *) baseURLForAptWithUID: (NSString *)_uid
848 // TODO: who calls this?
851 if ([_uid length] == 0)
854 url = [self baseURLInContext:_ctx];
855 if (![url hasSuffix:@"/"])
856 url = [url stringByAppendingString:@"/"];
858 // TODO: this should run a query to determine the uid!
859 return [url stringByAppendingString:_uid];
862 /* folder management */
864 - (id) lookupHomeFolderForUID: (NSString *) _uid
867 // TODO: DUP to SOGoGroupFolder
868 NSException *error = nil;
872 if (![_uid isNotNull])
875 if (_ctx == nil) _ctx = [[WOApplication application] context];
877 /* create subcontext, so that we don't destroy our environment */
879 if ((ctx = [_ctx createSubContext]) == nil) {
880 [self errorWithFormat:@"could not create SOPE subcontext!"];
886 path = _uid != nil ? [NSArray arrayWithObjects:&_uid count:1] : nil;
890 result = [[ctx application] traversePathArray:path inContext:ctx
891 error:&error acquire:NO];
893 [self errorWithFormat:@"folder lookup failed (uid=%@): %@",
898 [self debugWithFormat:@"Note: got folder for uid %@ path %@: %@",
899 _uid, [path componentsJoinedByString:@"=>"], result];
903 - (SOGoAppointmentFolder *) lookupCalendarFolderForUID: (NSString *) uid
905 SOGoFolder *upperContainer;
906 SOGoUserFolder *userFolder;
907 SOGoAppointmentFolder *calendarFolder;
909 upperContainer = [[self container] container];
910 userFolder = [SOGoUserFolder objectWithName: uid
911 inContainer: upperContainer];
912 calendarFolder = [SOGoAppointmentFolder objectWithName: @"Calendar"
913 inContainer: userFolder];
915 setOCSPath: [NSString stringWithFormat: @"/Users/%@/Calendar", uid]];
916 [calendarFolder setOwner: uid];
918 return calendarFolder;
921 - (NSArray *) lookupCalendarFoldersForUIDs: (NSArray *) _uids
924 /* Note: can return NSNull objects in the array! */
925 NSMutableArray *folders;
929 if ([_uids count] == 0) return nil;
930 folders = [NSMutableArray arrayWithCapacity:16];
931 e = [_uids objectEnumerator];
932 while ((uid = [e nextObject])) {
935 folder = [self lookupCalendarFolderForUID: uid];
936 if (![folder isNotNull])
937 [self logWithFormat:@"Note: did not find folder for uid: '%@'", uid];
939 /* Note: intentionally add 'null' folders to allow a mapping */
940 [folders addObject:folder ? folder : [NSNull null]];
945 - (NSArray *) lookupFreeBusyObjectsForUIDs: (NSArray *) _uids
948 /* Note: can return NSNull objects in the array! */
949 NSMutableArray *objs;
953 if ([_uids count] == 0) return nil;
954 objs = [NSMutableArray arrayWithCapacity:16];
955 e = [_uids objectEnumerator];
956 while ((uid = [e nextObject])) {
959 obj = [self lookupHomeFolderForUID:uid inContext:nil];
960 if ([obj isNotNull]) {
961 obj = [obj lookupName:@"freebusy.ifb" inContext:nil acquire:NO];
962 if ([obj isKindOfClass:[NSException class]])
965 if (![obj isNotNull])
966 [self logWithFormat:@"Note: did not find freebusy.ifb for uid: '%@'", uid];
968 /* Note: intentionally add 'null' folders to allow a mapping */
969 [objs addObject:obj ? obj : [NSNull null]];
974 - (NSArray *) uidsFromICalPersons: (NSArray *) _persons
976 /* Note: can return NSNull objects in the array! */
977 NSMutableArray *uids;
978 AgenorUserManager *um;
984 count = [_persons count];
985 uids = [NSMutableArray arrayWithCapacity:count + 1];
986 um = [AgenorUserManager sharedUserManager];
988 for (i = 0; i < count; i++) {
993 person = [_persons objectAtIndex:i];
994 email = [person rfc822Email];
995 if ([email isNotNull]) {
996 uid = [um getUIDForEmail:email];
1001 [uids addObject:(uid != nil ? uid : (id)[NSNull null])];
1006 - (NSArray *)lookupCalendarFoldersForICalPerson: (NSArray *) _persons
1007 inContext: (id) _ctx
1009 /* Note: can return NSNull objects in the array! */
1012 if ((uids = [self uidsFromICalPersons:_persons]) == nil)
1015 return [self lookupCalendarFoldersForUIDs:uids inContext:_ctx];
1018 - (id) lookupGroupFolderForUIDs: (NSArray *) _uids
1021 SOGoCustomGroupFolder *folder;
1026 folder = [[SOGoCustomGroupFolder alloc] initWithUIDs:_uids inContainer:self];
1027 return [folder autorelease];
1030 - (id) lookupGroupCalendarFolderForUIDs: (NSArray *) _uids
1031 inContext: (id) _ctx
1033 SOGoCustomGroupFolder *folder;
1035 if ((folder = [self lookupGroupFolderForUIDs:_uids inContext:_ctx]) == nil)
1038 folder = [folder lookupName:@"Calendar" inContext:_ctx acquire:NO];
1039 if (![folder isNotNull])
1041 if ([folder isKindOfClass:[NSException class]]) {
1042 [self debugWithFormat:@"Note: could not lookup 'Calendar' in folder: %@",
1052 - (NSArray *) fetchAllSOGoAppointments
1055 Note: very expensive method, do not use unless absolutely required.
1056 returns an array of SOGoAppointment objects.
1058 Note that we can leave out the filenames, supposed to be stored
1059 in the 'uid' field of the iCalendar object!
1061 NSMutableArray *events;
1062 NSDictionary *files;
1063 NSEnumerator *contents;
1066 /* fetch all raw contents */
1068 files = [self fetchContentStringsAndNamesOfAllObjects];
1069 if (![files isNotNull]) return nil;
1070 if ([files isKindOfClass:[NSException class]]) return (id)files;
1072 /* transform to SOGo appointments */
1074 events = [NSMutableArray arrayWithCapacity:[files count]];
1075 contents = [files objectEnumerator];
1076 while ((content = [contents nextObject]) != nil)
1077 [events addObject: [iCalCalendar parseSingleFromSource: content]];
1084 - (NSString *) outlookFolderClass
1086 return @"IPF.Appointment";
1089 @end /* SOGoAppointmentFolder */