diff --git a/ChangeLog b/ChangeLog index 3d1eab226..9bd98755b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,17 @@ +2008-07-11 Wolfgang Sourdeau + + * SoObjects/Appointments/SOGoAppointmentFolder.m + ([SOGoAppointmentFolder + -fetchFields:_fieldsfrom:_startDateto:_endDatetitle:titlecomponent:_componentadditionalFilters:filters]): + take exceptional modifications into account. + + * SoObjects/Appointments/iCalToDo+SOGo.m: new class module with + category methods for iCalToDo. + ([iCalToDo -quickRecord]): same as for method below. + + * SoObjects/Appointments/iCalEvent+SOGo.m ([iCalEvent + -quickRecord]): new method taken from OCSiCalFieldExtractor. + 2008-07-10 Wolfgang Sourdeau * SoObjects/SOGo/NSString+DAV.m ([NSString -asWebDAVTuple]): new diff --git a/OGoContentStore/GNUmakefile b/OGoContentStore/GNUmakefile index b697967e1..4de3724b8 100644 --- a/OGoContentStore/GNUmakefile +++ b/OGoContentStore/GNUmakefile @@ -17,9 +17,6 @@ ADDITIONAL_INCLUDE_DIRS += -I../SOPE ADDITIONAL_LIB_DIRS += -L../SOPE/GDLContentStore/obj libOGoContentStore_OBJC_FILES += \ - iCalEntityObject+OCS.m \ - iCalRepeatableEntityObject+OCS.m \ - \ OCSiCalFieldExtractor.m \ OCSContactFieldExtractor.m \ diff --git a/OGoContentStore/OCSiCalFieldExtractor.m b/OGoContentStore/OCSiCalFieldExtractor.m index f68537f9a..3bd604720 100644 --- a/OGoContentStore/OCSiCalFieldExtractor.m +++ b/OGoContentStore/OCSiCalFieldExtractor.m @@ -19,34 +19,21 @@ 02111-1307, USA. */ +#import #import -#import +#import #import -#import #import #import +#import -#import "iCalEntityObject+OCS.h" -#import "iCalRepeatableEntityObject+OCS.h" +#import #import "OCSiCalFieldExtractor.h" @implementation OCSiCalFieldExtractor -static NSCalendarDate *distantFuture = nil; -static NSNumber *distantFutureNumber = nil; - -+ (void) initialize -{ - if (!distantFuture) - { - distantFuture = [[NSCalendarDate distantFuture] retain]; - /* INT_MAX due to Postgres constraint */ - distantFutureNumber = [[NSNumber numberWithUnsignedInt: INT_MAX] retain]; - } -} - + (id) sharedICalFieldExtractor { static OCSiCalFieldExtractor *extractor = nil; @@ -59,287 +46,6 @@ static NSNumber *distantFutureNumber = nil; /* operations */ -- (NSNumber *) numberForDate: (NSCalendarDate *) _date -{ - return ((_date == distantFuture) - ? distantFutureNumber - : [NSNumber numberWithUnsignedInt: [_date timeIntervalSince1970]]); -} - -- (NSMutableDictionary *) extractQuickFieldsFromEvent: (iCalEvent *) _event -{ - NSMutableDictionary *row; - NSCalendarDate *startDate, *endDate; - NSArray *attendees; - NSString *uid, *title, *location, *status; - NSNumber *sequence, *dateNumber; - id organizer; - id participants, partmails; - NSMutableString *partstates; - unsigned int i, count; - BOOL isAllDay; - iCalAccessClass accessClass; - - if (_event == nil) - return nil; - - /* extract values */ - - startDate = [_event startDate]; - endDate = [_event endDate]; - uid = [_event uid]; - title = [_event summary]; - if (![title isNotNull]) - title = @""; - location = [_event location]; - sequence = [_event sequence]; - accessClass = [_event symbolicAccessClass]; - isAllDay = [_event isAllDay]; - status = [[_event status] uppercaseString]; - - attendees = [_event attendees]; - partmails = [attendees valueForKey: @"rfc822Email"]; - partmails = [partmails componentsJoinedByString: @"\n"]; - participants = [attendees valueForKey: @"cn"]; - participants = [participants componentsJoinedByString: @"\n"]; - - /* build row */ - - row = [NSMutableDictionary dictionaryWithCapacity:8]; - - [row setObject: @"vevent" forKey: @"c_component"]; - - if ([uid isNotNull]) - [row setObject:uid forKey: @"c_uid"]; - else - [self logWithFormat: @"WARNING: could not extract a uid from event!"]; - - - [row setObject: [NSNumber numberWithBool: isAllDay] - forKey: @"c_isallday"]; - [row setObject: [NSNumber numberWithBool: [_event isRecurrent]] - forKey: @"c_iscycle"]; - [row setObject: [NSNumber numberWithBool: [_event isOpaque]] - forKey: @"c_isopaque"]; - [row setObject: [NSNumber numberWithInt: [_event priorityNumber]] - forKey: @"c_priority"]; - - [row setObject: title forKey: @"c_title"]; - if ([location isNotNull]) [row setObject: location forKey: @"c_location"]; - if ([sequence isNotNull]) [row setObject: sequence forKey: @"c_sequence"]; - - if ([startDate isNotNull]) - [row setObject: [self numberForDate: startDate] - forKey: @"c_startdate"]; - if ([endDate isNotNull]) - { - if (endDate == distantFuture) - dateNumber = distantFutureNumber; - else - { - if (isAllDay) - i = 1; - else - i = 0; - dateNumber - = [NSNumber numberWithUnsignedInt: - [endDate timeIntervalSince1970] - i]; - } - [row setObject: dateNumber forKey: @"c_enddate"]; - } - - if ([_event isRecurrent]) { - NSCalendarDate *date; - - date = [_event lastPossibleRecurrenceStartDate]; - if (!date) { - /* this could also be *nil*, but in the end it makes the fetchspecs - more complex - thus we set it to a "reasonable" distant future */ - date = distantFuture; - } - [row setObject:[self numberForDate:date] forKey: @"c_cycleenddate"]; - [row setObject:[_event cycleInfo] forKey: @"c_cycleinfo"]; - } - - if ([participants length] > 0) - [row setObject: participants forKey: @"c_participants"]; - if ([partmails length] > 0) - [row setObject: partmails forKey: @"c_partmails"]; - - if ([status isNotNull]) { - int code = 1; - - if ([status isEqualToString: @"TENTATIVE"]) - code = 2; - else if ([status isEqualToString: @"CANCELLED"]) - code = 0; - [row setObject:[NSNumber numberWithInt:code] forKey: @"c_status"]; - } - else { - /* confirmed by default */ - [row setObject: [NSNumber numberWithInt:1] forKey: @"c_status"]; - } - - [row setObject: [NSNumber numberWithUnsignedInt: accessClass] - forKey: @"c_classification"]; - - organizer = [_event organizer]; - if (organizer) { - NSString *email; - - email = [organizer valueForKey: @"rfc822Email"]; - if (email) - [row setObject:email forKey: @"c_orgmail"]; - } - - /* construct partstates */ - count = [attendees count]; - partstates = [[NSMutableString alloc] initWithCapacity:count * 2]; - for ( i = 0; i < count; i++) { - iCalPerson *p; - iCalPersonPartStat stat; - - p = [attendees objectAtIndex:i]; - stat = [p participationStatus]; - if(i != 0) - [partstates appendString: @"\n"]; - [partstates appendFormat: @"%d", stat]; - } - [row setObject:partstates forKey: @"c_partstates"]; - [partstates release]; - return row; -} - -- (NSMutableDictionary *) extractQuickFieldsFromTodo: (iCalToDo *) _task -{ - NSMutableDictionary *row; - NSCalendarDate *startDate, *dueDate; - NSArray *attendees; - NSString *uid, *title, *location, *status; - NSNumber *sequence; - id organizer, date; - id participants, partmails; - NSMutableString *partstates; - unsigned i, count, code; - iCalAccessClass accessClass; - - if (_task == nil) - return nil; - - /* extract values */ - - startDate = [_task startDate]; - dueDate = [_task due]; - uid = [_task uid]; - title = [_task summary]; - if (![title isNotNull]) - title = @""; - location = [_task location]; - sequence = [_task sequence]; - accessClass = [_task symbolicAccessClass]; - status = [[_task status] uppercaseString]; - - attendees = [_task attendees]; - partmails = [attendees valueForKey: @"rfc822Email"]; - partmails = [partmails componentsJoinedByString: @"\n"]; - participants = [attendees valueForKey: @"cn"]; - participants = [participants componentsJoinedByString: @"\n"]; - - /* build row */ - - row = [NSMutableDictionary dictionaryWithCapacity:8]; - - [row setObject: @"vtodo" forKey: @"c_component"]; - - if ([uid isNotNull]) - [row setObject:uid forKey: @"c_uid"]; - else - [self logWithFormat: @"WARNING: could not extract a uid from event!"]; - - [row setObject:[NSNumber numberWithBool:[_task isRecurrent]] - forKey: @"c_iscycle"]; - [row setObject:[NSNumber numberWithInt:[_task priorityNumber]] - forKey: @"c_priority"]; - - [row setObject: [NSNumber numberWithBool: NO] - forKey: @"c_isallday"]; - [row setObject: [NSNumber numberWithBool: NO] - forKey: @"c_isopaque"]; - - [row setObject: title forKey: @"c_title"]; - if ([location isNotNull]) [row setObject: location forKey: @"c_location"]; - if ([sequence isNotNull]) [row setObject: sequence forKey: @"c_sequence"]; - - if ([startDate isNotNull]) - date = [self numberForDate: startDate]; - else - date = [NSNull null]; - [row setObject: date forKey: @"c_startdate"]; - - if ([dueDate isNotNull]) - date = [self numberForDate: dueDate]; - else - date = [NSNull null]; - [row setObject: date forKey: @"c_enddate"]; - - if ([_task isRecurrent]) - { - [row setObject: [self numberForDate: distantFuture] - forKey: @"c_cycleenddate"]; - [row setObject: [_task cycleInfo] - forKey: @"c_cycleinfo"]; - } - - if ([participants length] > 0) - [row setObject:participants forKey: @"c_participants"]; - if ([partmails length] > 0) - [row setObject:partmails forKey: @"c_partmails"]; - - if ([status isNotNull]) { - code = 0; /* NEEDS-ACTION */ - if ([status isEqualToString: @"COMPLETED"]) - code = 1; - else if ([status isEqualToString: @"IN-PROCESS"]) - code = 2; - else if ([status isEqualToString: @"CANCELLED"]) - code = 3; - [row setObject: [NSNumber numberWithInt: code] forKey: @"c_status"]; - } - else { - /* confirmed by default */ - [row setObject:[NSNumber numberWithInt:1] forKey: @"c_status"]; - } - - [row setObject: [NSNumber numberWithUnsignedInt: accessClass] - forKey: @"c_classification"]; - - organizer = [_task organizer]; - if (organizer) { - NSString *email; - - email = [organizer valueForKey: @"rfc822Email"]; - if (email) - [row setObject:email forKey: @"c_orgmail"]; - } - - /* construct partstates */ - count = [attendees count]; - partstates = [[NSMutableString alloc] initWithCapacity:count * 2]; - for ( i = 0; i < count; i++) { - iCalPerson *p; - iCalPersonPartStat stat; - - p = [attendees objectAtIndex:i]; - stat = [p participationStatus]; - if(i != 0) - [partstates appendString: @"\n"]; - [partstates appendFormat: @"%d", stat]; - } - [row setObject:partstates forKey: @"c_partstates"]; - [partstates release]; - return row; -} - - (CardGroup *) firstElementFromCalendar: (iCalCalendar *) ical { NSArray *elements; @@ -349,13 +55,7 @@ static NSNumber *distantFutureNumber = nil; elements = [ical allObjects]; count = [elements count]; if (count) - { - if (count > 1) - [self logWithFormat: - @"WARNING: given calendar contains more than one event: %@", - ical]; - element = [elements objectAtIndex: 0]; - } + element = [elements objectAtIndex: 0]; else { [self logWithFormat: @"ERROR: given calendar contains no elements: %@", ical]; @@ -365,34 +65,32 @@ static NSNumber *distantFutureNumber = nil; return element; } -- (NSMutableDictionary *)extractQuickFieldsFromContent:(NSString *)_content { - NSDictionary *fields; +- (NSMutableDictionary *) extractQuickFieldsFromContent: (NSString *) _content +{ + NSMutableDictionary *fields; id cal; - - if ([_content length] == 0) - return nil; - cal = [iCalCalendar parseSingleFromSource: _content]; - fields = nil; - if (cal) + + if ([_content length]) { - if ([cal isKindOfClass:[iCalCalendar class]]) - cal = [self firstElementFromCalendar: cal]; + cal = [iCalCalendar parseSingleFromSource: _content]; + if (cal) + { + if ([cal isKindOfClass: [iCalCalendar class]]) + cal = [self firstElementFromCalendar: cal]; - if ([cal isKindOfClass:[iCalEvent class]]) - fields = [[self extractQuickFieldsFromEvent:cal] retain]; - else if ([cal isKindOfClass:[iCalToDo class]]) - fields = [[self extractQuickFieldsFromTodo:cal] retain]; - else if ([cal isNotNull]) { - [self logWithFormat: @"ERROR: unexpected iCalendar parse result: %@", - cal]; - } + if ([cal isKindOfClass: [iCalRepeatableEntityObject class]]) + fields = [cal quickRecord]; + else if ([cal isNotNull]) + [self logWithFormat: @"ERROR: unexpected iCalendar parse result: %@", + cal]; + } + else + [self logWithFormat: @"ERROR: parsing source didn't return anything"]; } - else - [self logWithFormat: @"ERROR: parsing source didn't return anything"]; - return [fields autorelease]; + return fields; } @end /* OCSiCalFieldExtractor */ diff --git a/OGoContentStore/iCalEntityObject+OCS.h b/OGoContentStore/iCalEntityObject+OCS.h deleted file mode 100644 index c5eb30da8..000000000 --- a/OGoContentStore/iCalEntityObject+OCS.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - 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. -*/ - -#ifndef __OGoContentStore_iCalEntityObject_OCS_H_ -#define __OGoContentStore_iCalEntityObject_OCS_H_ - -#include - -@interface iCalEntityObject (OCS) - -- (int)priorityNumber; - -@end - -#endif /* __OGoContentStore_iCalEntityObject_OCS_H_ */ diff --git a/OGoContentStore/iCalEntityObject+OCS.m b/OGoContentStore/iCalEntityObject+OCS.m deleted file mode 100644 index e95f8484d..000000000 --- a/OGoContentStore/iCalEntityObject+OCS.m +++ /dev/null @@ -1,42 +0,0 @@ -/* - 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. -*/ - -#import "iCalEntityObject+OCS.h" - -@implementation iCalEntityObject (OCS) - -- (int)priorityNumber { - NSString *prio; - - prio = [self priority]; - if(prio) { - NSRange r; - - r = [prio rangeOfString:@";"]; - if(r.length > 0) { - prio = [prio substringToIndex:r.location]; - } - return [prio intValue]; - } - return 0; -} - -@end diff --git a/OGoContentStore/iCalRepeatableEntityObject+OCS.m b/OGoContentStore/iCalRepeatableEntityObject+OCS.m index 191a44035..6e31876dc 100644 --- a/OGoContentStore/iCalRepeatableEntityObject+OCS.m +++ b/OGoContentStore/iCalRepeatableEntityObject+OCS.m @@ -26,59 +26,4 @@ @implementation iCalRepeatableEntityObject (OCS) -- (NSArray *) _indexedRules: (NSArray *) rules -{ - NSMutableArray *ma; - unsigned int i, count; - NSString *valuesString; - - ma = nil; - count = [rules count]; - if (count > 0) - { - ma = [NSMutableArray arrayWithCapacity:count]; - for (i = 0; i < count; i++) - { - iCalRecurrenceRule *rule; - - rule = [rules objectAtIndex:i]; -#warning we could return an NSArray instead and feed it as such to the iCalRecurrenceRule in SOGoAppointmentFolder... - valuesString = [[rule values] componentsJoinedByString: @";"]; - [ma addObject: valuesString]; - } - } - - return ma; -} - -- (NSString *) cycleInfo -{ - NSArray *rules; - NSString *value; - NSMutableDictionary *cycleInfo; - - if ([self isRecurrent]) - { - cycleInfo = [NSMutableDictionary dictionaryWithCapacity: 3]; - - /* rules */ - rules = [self _indexedRules: [self recurrenceRules]]; - if (rules) - [cycleInfo setObject: rules forKey: @"rules"]; - - rules = [self _indexedRules: [self exceptionRules]]; - if (rules) - [cycleInfo setObject: rules forKey: @"exRules"]; - - rules = [self _indexedRules: [self exceptionDates]]; - if (rules) - [cycleInfo setObject: rules forKey: @"exDates"]; - - value = [cycleInfo description]; - } - else - value = nil; - - return value; -} @end diff --git a/SOPE/NGCards/ChangeLog b/SOPE/NGCards/ChangeLog index d9e4b8afd..0a88ead42 100644 --- a/SOPE/NGCards/ChangeLog +++ b/SOPE/NGCards/ChangeLog @@ -1,3 +1,9 @@ +2008-07-11 Wolfgang Sourdeau + + * iCalEntityObject.m ([iCalEntityObject -setRecurrenceId:newRecId]) + ([iCalEntityObject -recurrenceId]): new accessors to handle the + "RECURRENCE-ID" fields in recurrence exceptions. + 2008-07-07 Wolfgang Sourdeau * CardElement.m ([CardElement -addValue:aValue]) diff --git a/SOPE/NGCards/iCalCalendar.m b/SOPE/NGCards/iCalCalendar.m index 1771c65d3..479569526 100644 --- a/SOPE/NGCards/iCalCalendar.m +++ b/SOPE/NGCards/iCalCalendar.m @@ -190,8 +190,8 @@ - (NSArray *) allObjects { NSMutableArray *ma; - - ma = [NSMutableArray new]; + + ma = [NSMutableArray array]; [ma addObjectsFromArray: [self events]]; [ma addObjectsFromArray: [self todos]]; [ma addObjectsFromArray: [self journals]]; diff --git a/SOPE/NGCards/iCalEntityObject.h b/SOPE/NGCards/iCalEntityObject.h index 516a0d16d..870d9acc2 100644 --- a/SOPE/NGCards/iCalEntityObject.h +++ b/SOPE/NGCards/iCalEntityObject.h @@ -79,6 +79,9 @@ typedef enum - (NSCalendarDate *) startDate; - (BOOL) hasStartDate; +- (void) setRecurrenceId: (NSCalendarDate *) newRecId; +- (NSCalendarDate *) recurrenceId; + - (void) setLastModified: (NSCalendarDate *) _value; - (NSCalendarDate *) lastModified; @@ -110,7 +113,7 @@ typedef enum - (NSArray *) resources; - (BOOL) isParticipant: (id) _email; - (iCalPerson *) findParticipantWithEmail: (id) _email; - + - (void) removeAllAlarms; - (void) addToAlarms: (id) _alarm; - (NSArray *) alarms; diff --git a/SOPE/NGCards/iCalEntityObject.m b/SOPE/NGCards/iCalEntityObject.m index 45ac1f90c..146f2b538 100644 --- a/SOPE/NGCards/iCalEntityObject.m +++ b/SOPE/NGCards/iCalEntityObject.m @@ -60,6 +60,7 @@ tagClass = [CardElement class]; else if ([classTag isEqualToString: @"DTSTAMP"] || [classTag isEqualToString: @"DTSTART"] + || [classTag isEqualToString: @"RECURRENCE-ID"] || [classTag isEqualToString: @"CREATED"] || [classTag isEqualToString: @"LAST-MODIFIED"]) tagClass = [iCalDateTime class]; @@ -257,6 +258,18 @@ dateTime]; } +- (void) setRecurrenceId: (NSCalendarDate *) newRecId +{ + [(iCalDateTime *) [self uniqueChildWithTag: @"recurrence-id"] + setDateTime: newRecId]; +} + +- (NSCalendarDate *) recurrenceId +{ + return [(iCalDateTime *) [self uniqueChildWithTag: @"recurrence-id"] + dateTime]; +} + - (BOOL) hasStartDate { return ([[self childrenWithTag: @"dtstart"] count] > 0); diff --git a/SoObjects/Appointments/GNUmakefile b/SoObjects/Appointments/GNUmakefile index c66531087..858b49b63 100644 --- a/SoObjects/Appointments/GNUmakefile +++ b/SoObjects/Appointments/GNUmakefile @@ -11,9 +11,11 @@ Appointments_OBJC_FILES = \ Product.m \ NSArray+Appointments.m \ iCalEntityObject+SOGo.m \ + iCalRepeatableEntityObject+SOGo.m \ iCalEvent+SOGo.m \ iCalEventChanges+SOGo.m \ iCalPerson+SOGo.m \ + iCalToDo+SOGo.m \ \ SOGoCalendarComponent.m \ SOGoAppointmentObject.m \ diff --git a/SoObjects/Appointments/SOGoAppointmentFolder.m b/SoObjects/Appointments/SOGoAppointmentFolder.m index d2e6754f1..787f1e2ab 100644 --- a/SoObjects/Appointments/SOGoAppointmentFolder.m +++ b/SoObjects/Appointments/SOGoAppointmentFolder.m @@ -59,12 +59,14 @@ #import // #import #import +#import #import #import #import #import -#import +#import +#import "iCalEntityObject+SOGo.h" #import "SOGoAppointmentObject.h" #import "SOGoAppointmentFolders.h" #import "SOGoFreeBusyObject.h" @@ -98,7 +100,10 @@ static Class sogoAppointmentFolderKlass = Nil; didInit = YES; if (!sogoAppointmentFolderKlass) - sogoAppointmentFolderKlass = self; + { + sogoAppointmentFolderKlass = self; + [iCalEntityObject initializeSOGoExtensions]; + } if (!reportQueryFields) reportQueryFields = [[NSArray alloc] initWithObjects: @"c_name", @@ -554,7 +559,6 @@ static Class sogoAppointmentFolderKlass = Nil; } - (NSMutableDictionary *) fixupRecord: (NSDictionary *) _record - fetchRange: (NGCalendarDateRange *) _r { NSMutableDictionary *md; static NSString *fields[] = { @"c_startdate", @"startDate", @@ -598,8 +602,7 @@ static Class sogoAppointmentFolderKlass = Nil; ma = [NSMutableArray arrayWithCapacity: max]; for (count = 0; count < max; count++) { - row = [self fixupRecord: [records objectAtIndex: count] - fetchRange: r]; + row = [self fixupRecord: [records objectAtIndex: count]]; if (row) [ma addObject: row]; } @@ -632,6 +635,117 @@ static Class sogoAppointmentFolderKlass = Nil; return md; } +- (int) _indexOfRecordMatchingDate: (NSCalendarDate *) matchDate + inArray: (NSArray *) recordArray +{ + int count, max, recordIndex; + NSDictionary *currentRecord; + + recordIndex = -1; + + count = 0; + max = [recordArray count]; + while (recordIndex == -1 && count < max) + { + currentRecord = [recordArray objectAtIndex: count]; + if ([[currentRecord objectForKey: @"startDate"] + isEqual: matchDate]) + recordIndex = count; + } + + return recordIndex; +} + +- (void) _fixExceptionRecord: (NSMutableDictionary *) recRecord + fromRow: (NSDictionary *) row +{ + NSArray *objects; + static NSArray *fields = nil; + + if (!fields) + { + fields = [NSArray arrayWithObjects: @"c_name", nil]; + [fields retain]; + } + + objects = [row objectsForKeys: fields notFoundMarker: @""]; + [recRecord setObjects: objects forKeys: fields]; +} + +- (void) _appendCycleException: (iCalRepeatableEntityObject *) component + fromRow: (NSDictionary *) row + forRange: (NGCalendarDateRange *) dateRange + toArray: (NSMutableArray *) ma +{ + NSCalendarDate *startDate, *recurrenceId; + NSMutableDictionary *newRecord; + NGCalendarDateRange *newRecordRange; + int recordIndex; + + newRecord = nil; + + recurrenceId = [component recurrenceId]; + if ([dateRange containsDate: recurrenceId]) + { + recordIndex = [self _indexOfRecordMatchingDate: recurrenceId + inArray: ma]; + if (recordIndex > -1) + { + startDate = [component startDate]; + if ([dateRange containsDate: startDate]) + { + newRecord = [self fixupRecord: [component quickRecord]]; + [ma replaceObjectAtIndex: recordIndex withObject: newRecord]; + } + else + [ma removeObjectAtIndex: recordIndex]; + } + else + [self errorWithFormat: + @"missing exception record for recurrence-id: %@", + recurrenceId]; + } + else + { + newRecord = [self fixupRecord: [component quickRecord]]; + newRecordRange = [NGCalendarDateRange calendarDateRangeWithStartDate: + [newRecord objectForKey: + @"startDate"] + endDate: [newRecord objectForKey: + @"endDate"]]; + if ([dateRange doesIntersectWithDateRange: newRecordRange]) + [ma addObject: newRecord]; + } + + if (newRecord) + [self _fixExceptionRecord: newRecord fromRow: row]; +} + +- (void) _appendCycleExceptionsFromRow: (NSDictionary *) row + forRange: (NGCalendarDateRange *) dateRange + toArray: (NSMutableArray *) ma +{ + NSArray *elements, *components; + unsigned int count, max; + NSString *content; + + content = [row objectForKey: @"c_content"]; + if ([content length]) + { + elements = [iCalCalendar parseFromSource: content]; + if ([elements count]) + { + components = [[elements objectAtIndex: 0] allObjects]; + max = [components count]; + for (count = 1; count < max; count++) + [self _appendCycleException: [components objectAtIndex: count] + fromRow: row + forRange: dateRange + toArray: ma]; + } + } +} + - (void) _flattenCycleRecord: (NSDictionary *) _row forRange: (NGCalendarDateRange *) _r intoArray: (NSMutableArray *) _ma @@ -660,7 +774,7 @@ static Class sogoAppointmentFolderKlass = Nil; return; } - row = [self fixupRecord:_row fetchRange: _r]; + row = [self fixupRecord: _row]; [row removeObjectForKey: @"c_cycleinfo"]; [row setObject: sharedYes forKey: @"isRecurrentEvent"]; @@ -685,6 +799,10 @@ static Class sogoAppointmentFolderKlass = Nil; if (fixedRow) [_ma addObject:fixedRow]; } + + [self _appendCycleExceptionsFromRow: row + forRange: _r + toArray: _ma]; } - (NSArray *) fixupCyclicRecords: (NSArray *) _records @@ -693,16 +811,16 @@ static Class sogoAppointmentFolderKlass = Nil; // TODO: is the result supposed to be sorted by date? NSMutableArray *ma; NSDictionary *row; - unsigned int i, count; + unsigned int count, max; - count = [_records count]; - ma = [NSMutableArray arrayWithCapacity: count]; - if (count > 0) - for (i = 0; i < count; i++) - { - row = [_records objectAtIndex: i]; - [self _flattenCycleRecord: row forRange: _r intoArray: ma]; - } + max = [_records count]; + ma = [NSMutableArray arrayWithCapacity: max]; + + for (count = 0; count < max; count++) + { + row = [_records objectAtIndex: count]; + [self _flattenCycleRecord: row forRange: _r intoArray: ma]; + } return ma; } @@ -844,9 +962,10 @@ static Class sogoAppointmentFolderKlass = Nil; { if (r) records = [self fixupCyclicRecords: records fetchRange: r]; - if (!ma) - ma = [NSMutableArray arrayWithCapacity: [records count]]; - [ma addObjectsFromArray: records]; + if (ma) + [ma addObjectsFromArray: records]; + else + ma = [NSMutableArray arrayWithArray: records]; } else if (!ma) { @@ -891,6 +1010,8 @@ static Class sogoAppointmentFolderKlass = Nil; #warning we should use the EOFetchSpecification for that!!! (see doPROPFIND:) #warning components in calendar-data query are ignored + +#warning the two following methods should be replaced with the new dav rendering mechanism - (NSString *) _nodeTagForProperty: (NSString *) property { NSString *namespace, *nodeName, *nsRep; diff --git a/SoObjects/Appointments/iCalEntityObject+SOGo.h b/SoObjects/Appointments/iCalEntityObject+SOGo.h index f26ae5a1a..bb404d709 100644 --- a/SoObjects/Appointments/iCalEntityObject+SOGo.h +++ b/SoObjects/Appointments/iCalEntityObject+SOGo.h @@ -27,8 +27,13 @@ @class SOGoUser; +NSCalendarDate *iCalDistantFuture; +NSNumber *iCalDistantFutureNumber; + @interface iCalEntityObject (SOGoExtensions) ++ (void) initializeSOGoExtensions; + - (BOOL) userIsParticipant: (SOGoUser *) user; - (BOOL) userIsOrganizer: (SOGoUser *) user; @@ -38,6 +43,10 @@ - (id) itipEntryWithMethod: (NSString *) method; - (NSArray *) attendeesWithoutUser: (SOGoUser *) user; +- (NSMutableDictionary *) quickRecord; + +- (int) priorityNumber; +- (NSNumber *) quickRecordDateAsNumber: (NSCalendarDate *) _date; @end diff --git a/SoObjects/Appointments/iCalEntityObject+SOGo.m b/SoObjects/Appointments/iCalEntityObject+SOGo.m index faa0425fc..cec55d042 100644 --- a/SoObjects/Appointments/iCalEntityObject+SOGo.m +++ b/SoObjects/Appointments/iCalEntityObject+SOGo.m @@ -23,6 +23,7 @@ #import #import #import +#import #import #import @@ -36,6 +37,16 @@ @implementation iCalEntityObject (SOGoExtensions) ++ (void) initializeSOGoExtensions; +{ + if (!iCalDistantFuture) + { + iCalDistantFuture = [[NSCalendarDate distantFuture] retain]; + /* INT_MAX due to Postgres constraint */ + iCalDistantFutureNumber = [[NSNumber numberWithUnsignedInt: INT_MAX] retain]; + } +} + - (BOOL) userIsParticipant: (SOGoUser *) user { NSEnumerator *participants; @@ -128,4 +139,38 @@ return newAttendees; } +- (NSNumber *) quickRecordDateAsNumber: (NSCalendarDate *) _date +{ + return ((_date == iCalDistantFuture) + ? iCalDistantFutureNumber + : [NSNumber numberWithUnsignedInt: [_date timeIntervalSince1970]]); +} + +- (NSMutableDictionary *) quickRecord +{ + [self subclassResponsibility: _cmd]; + + return nil; +} + +- (int) priorityNumber +{ + NSString *prio; + NSRange r; + int priorityNumber; + + prio = [self priority]; + if (prio) + { + r = [prio rangeOfString: @";"]; + if (r.length) + prio = [prio substringToIndex:r.location]; + priorityNumber = [prio intValue]; + } + else + priorityNumber = 0; + + return priorityNumber; +} + @end diff --git a/SoObjects/Appointments/iCalEvent+SOGo.h b/SoObjects/Appointments/iCalEvent+SOGo.h index 8250b23b0..e2d71a8af 100644 --- a/SoObjects/Appointments/iCalEvent+SOGo.h +++ b/SoObjects/Appointments/iCalEvent+SOGo.h @@ -25,9 +25,12 @@ #import +@class NSMutableDictionary; + @interface iCalEvent (SOGoExtensions) - (BOOL) isStillRelevant; +- (NSMutableDictionary *) quickRecord; @end diff --git a/SoObjects/Appointments/iCalEvent+SOGo.m b/SoObjects/Appointments/iCalEvent+SOGo.m index f795d3f78..905f537c1 100644 --- a/SoObjects/Appointments/iCalEvent+SOGo.m +++ b/SoObjects/Appointments/iCalEvent+SOGo.m @@ -20,7 +20,18 @@ * Boston, MA 02111-1307, USA. */ +#import #import +#import +#import + +#import +#import + +#import +#import + +#import "iCalRepeatableEntityObject+SOGo.h" #import "iCalEvent+SOGo.h" @@ -35,4 +46,150 @@ return ([[self endDate] earlierDate: now] == now); } +- (NSMutableDictionary *) quickRecord +{ + NSMutableDictionary *row; + NSCalendarDate *startDate, *endDate; + NSArray *attendees; + NSString *uid, *title, *location, *status; + NSNumber *sequence, *dateNumber; + id organizer; + id participants, partmails; + NSMutableString *partstates; + unsigned int i, count, boolTmp; + BOOL isAllDay; + iCalAccessClass accessClass; + + /* extract values */ + + startDate = [self startDate]; + endDate = [self endDate]; + uid = [self uid]; + title = [self summary]; + if (![title isNotNull]) + title = @""; + location = [self location]; + sequence = [self sequence]; + accessClass = [self symbolicAccessClass]; + isAllDay = [self isAllDay]; + status = [[self status] uppercaseString]; + + attendees = [self attendees]; + partmails = [attendees valueForKey: @"rfc822Email"]; + partmails = [partmails componentsJoinedByString: @"\n"]; + participants = [attendees valueForKey: @"cn"]; + participants = [participants componentsJoinedByString: @"\n"]; + + /* build row */ + + row = [NSMutableDictionary dictionaryWithCapacity:8]; + + [row setObject: @"vevent" forKey: @"c_component"]; + + if ([uid isNotNull]) + [row setObject:uid forKey: @"c_uid"]; + else + [self logWithFormat: @"WARNING: could not extract a uid from event!"]; + + boolTmp = ((isAllDay) ? 1 : 0); + [row setObject: [NSNumber numberWithInt: boolTmp] + forKey: @"c_isallday"]; + boolTmp = (([self isRecurrent]) ? 1 : 0); + [row setObject: [NSNumber numberWithInt: boolTmp] + forKey: @"c_iscycle"]; + boolTmp = (([self isOpaque]) ? 1 : 0); + [row setObject: [NSNumber numberWithInt: boolTmp] + forKey: @"c_isopaque"]; + [row setObject: [NSNumber numberWithInt: [self priorityNumber]] + forKey: @"c_priority"]; + + [row setObject: title forKey: @"c_title"]; + if ([location isNotNull]) [row setObject: location forKey: @"c_location"]; + if ([sequence isNotNull]) [row setObject: sequence forKey: @"c_sequence"]; + + if ([startDate isNotNull]) + [row setObject: [self quickRecordDateAsNumber: startDate] + forKey: @"c_startdate"]; + if ([endDate isNotNull]) + { + if (endDate == iCalDistantFuture) + dateNumber = iCalDistantFutureNumber; + else + { + if (isAllDay) + i = 1; + else + i = 0; + dateNumber + = [NSNumber numberWithUnsignedInt: + [endDate timeIntervalSince1970] - i]; + } + [row setObject: dateNumber forKey: @"c_enddate"]; + } + + if ([self isRecurrent]) { + NSCalendarDate *date; + + date = [self lastPossibleRecurrenceStartDate]; + if (!date) { + /* this could also be *nil*, but in the end it makes the fetchspecs + more complex - thus we set it to a "reasonable" distant future */ + date = iCalDistantFuture; + } + [row setObject:[self quickRecordDateAsNumber:date] forKey: @"c_cycleenddate"]; + [row setObject:[self cycleInfo] forKey: @"c_cycleinfo"]; + } + + if ([participants length] > 0) + [row setObject: participants forKey: @"c_participants"]; + if ([partmails length] > 0) + [row setObject: partmails forKey: @"c_partmails"]; + + if ([status isNotNull]) { + int code = 1; + + if ([status isEqualToString: @"TENTATIVE"]) + code = 2; + else if ([status isEqualToString: @"CANCELLED"]) + code = 0; + [row setObject:[NSNumber numberWithInt:code] forKey: @"c_status"]; + } + else { + /* confirmed by default */ + [row setObject: [NSNumber numberWithInt:1] forKey: @"c_status"]; + } + + [row setObject: [NSNumber numberWithUnsignedInt: accessClass] + forKey: @"c_classification"]; + + organizer = [self organizer]; + if (organizer) + { + NSString *email; + + email = [organizer valueForKey: @"rfc822Email"]; + if (email) + [row setObject:email forKey: @"c_orgmail"]; + } + + /* construct partstates */ + count = [attendees count]; + partstates = [[NSMutableString alloc] initWithCapacity:count * 2]; + for (i = 0; i < count; i++) + { + iCalPerson *p; + iCalPersonPartStat stat; + + p = [attendees objectAtIndex:i]; + stat = [p participationStatus]; + if (i) + [partstates appendString: @"\n"]; + [partstates appendFormat: @"%d", stat]; + } + [row setObject:partstates forKey: @"c_partstates"]; + [partstates release]; + + return row; +} + @end diff --git a/SoObjects/Appointments/iCalRepeatableEntityObject+SOGo.h b/SoObjects/Appointments/iCalRepeatableEntityObject+SOGo.h new file mode 100644 index 000000000..2a54131e1 --- /dev/null +++ b/SoObjects/Appointments/iCalRepeatableEntityObject+SOGo.h @@ -0,0 +1,33 @@ +/* iCalRepeatableEntityObject+SOGo.h - this file is part of SOGo + Copyright (C) 2004-2005 SKYRIX Software AG + Copyright (C) 2008 Inverse groupe conseil + + 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. +*/ + +#import + +#import "iCalEntityObject+SOGo.h" + +@class NSString; + +@interface iCalRepeatableEntityObject (SOGoExtensions) + +- (NSString *) cycleInfo; + +@end diff --git a/SoObjects/Appointments/iCalRepeatableEntityObject+SOGo.m b/SoObjects/Appointments/iCalRepeatableEntityObject+SOGo.m new file mode 100644 index 000000000..ce64f0c45 --- /dev/null +++ b/SoObjects/Appointments/iCalRepeatableEntityObject+SOGo.m @@ -0,0 +1,89 @@ +/* iCalRepeatableEntityObject+SOGo.m - this file is part of SOGo + Copyright (C) 2004-2005 SKYRIX Software AG + Copyright (C) 2008 Inverse groupe conseil + + 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. +*/ + +#import +#import +#import + +#import + +#import "iCalRepeatableEntityObject+SOGo.h" + +@implementation iCalRepeatableEntityObject (SOGoExtensions) + +- (NSArray *) _indexedRules: (NSArray *) rules +{ + NSMutableArray *ma; + unsigned int i, count; + NSString *valuesString; + + ma = nil; + count = [rules count]; + if (count > 0) + { + ma = [NSMutableArray arrayWithCapacity:count]; + for (i = 0; i < count; i++) + { + iCalRecurrenceRule *rule; + + rule = [rules objectAtIndex:i]; +#warning we could return an NSArray instead and feed it as such to the iCalRecurrenceRule in SOGoAppointmentFolder... + valuesString = [[rule values] componentsJoinedByString: @";"]; + [ma addObject: valuesString]; + } + } + + return ma; +} + +- (NSString *) cycleInfo +{ + NSArray *rules; + NSString *value; + NSMutableDictionary *cycleInfo; + + if ([self isRecurrent]) + { + cycleInfo = [NSMutableDictionary dictionaryWithCapacity: 3]; + + /* rules */ + rules = [self _indexedRules: [self recurrenceRules]]; + if (rules) + [cycleInfo setObject: rules forKey: @"rules"]; + + rules = [self _indexedRules: [self exceptionRules]]; + if (rules) + [cycleInfo setObject: rules forKey: @"exRules"]; + + rules = [self _indexedRules: [self exceptionDates]]; + if (rules) + [cycleInfo setObject: rules forKey: @"exDates"]; + + value = [cycleInfo description]; + } + else + value = nil; + + return value; +} + +@end diff --git a/SoObjects/Appointments/iCalToDo+SOGo.h b/SoObjects/Appointments/iCalToDo+SOGo.h new file mode 100644 index 000000000..b02834908 --- /dev/null +++ b/SoObjects/Appointments/iCalToDo+SOGo.h @@ -0,0 +1,36 @@ +/* iCalToDo+SOGo.h - this file is part of SOGo + * + * Copyright (C) 2007 Inverse groupe conseil + * + * Author: Wolfgang Sourdeau + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef ICALTODO_SOGO_H +#define ICALTODO_SOGO_H + +#import + +@class NSMutableArray; + +@interface iCalToDo (SOGoExtensions) + +- (NSMutableDictionary *) quickRecord; + +@end + +#endif /* ICALTODO_SOGO_H */ diff --git a/SoObjects/Appointments/iCalToDo+SOGo.m b/SoObjects/Appointments/iCalToDo+SOGo.m new file mode 100644 index 000000000..ebdddf1b2 --- /dev/null +++ b/SoObjects/Appointments/iCalToDo+SOGo.m @@ -0,0 +1,168 @@ +/* iCalEvent+SOGo.m - this file is part of SOGo + * + * Copyright (C) 2008 Inverse groupe conseil + * Copyright (C) 2004-2005 SKYRIX Software AG + * + * Author: Wolfgang Sourdeau + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#import +#import +#import +#import + +#import +#import + +#import + +#import "iCalRepeatableEntityObject+SOGo.h" + +#import "iCalToDo+SOGo.h" + +@implementation iCalToDo (SOGoExtensions) + +- (NSMutableDictionary *) quickRecord +{ + NSMutableDictionary *row; + NSCalendarDate *startDate, *dueDate; + NSArray *attendees; + NSString *uid, *title, *location, *status; + NSNumber *sequence; + id organizer, date; + id participants, partmails; + NSMutableString *partstates; + unsigned i, count, code; + iCalAccessClass accessClass; + + /* extract values */ + + startDate = [self startDate]; + dueDate = [self due]; + uid = [self uid]; + title = [self summary]; + if (![title isNotNull]) + title = @""; + location = [self location]; + sequence = [self sequence]; + accessClass = [self symbolicAccessClass]; + status = [[self status] uppercaseString]; + + attendees = [self attendees]; + partmails = [attendees valueForKey: @"rfc822Email"]; + partmails = [partmails componentsJoinedByString: @"\n"]; + participants = [attendees valueForKey: @"cn"]; + participants = [participants componentsJoinedByString: @"\n"]; + + /* build row */ + + row = [NSMutableDictionary dictionaryWithCapacity:8]; + + [row setObject: @"vtodo" forKey: @"c_component"]; + + if ([uid isNotNull]) + [row setObject:uid forKey: @"c_uid"]; + else + [self logWithFormat: @"WARNING: could not extract a uid from event!"]; + + [row setObject:[NSNumber numberWithBool:[self isRecurrent]] + forKey: @"c_iscycle"]; + [row setObject:[NSNumber numberWithInt:[self priorityNumber]] + forKey: @"c_priority"]; + + [row setObject: [NSNumber numberWithBool: NO] + forKey: @"c_isallday"]; + [row setObject: [NSNumber numberWithBool: NO] + forKey: @"c_isopaque"]; + + [row setObject: title forKey: @"c_title"]; + if ([location isNotNull]) [row setObject: location forKey: @"c_location"]; + if ([sequence isNotNull]) [row setObject: sequence forKey: @"c_sequence"]; + + if ([startDate isNotNull]) + date = [self quickRecordDateAsNumber: startDate]; + else + date = [NSNull null]; + [row setObject: date forKey: @"c_startdate"]; + + if ([dueDate isNotNull]) + date = [self quickRecordDateAsNumber: dueDate]; + else + date = [NSNull null]; + [row setObject: date forKey: @"c_enddate"]; + + if ([self isRecurrent]) + { + [row setObject: [self quickRecordDateAsNumber: iCalDistantFuture] + forKey: @"c_cycleenddate"]; + [row setObject: [self cycleInfo] + forKey: @"c_cycleinfo"]; + } + + if ([participants length] > 0) + [row setObject:participants forKey: @"c_participants"]; + if ([partmails length] > 0) + [row setObject:partmails forKey: @"c_partmails"]; + + if ([status isNotNull]) { + code = 0; /* NEEDS-ACTION */ + if ([status isEqualToString: @"COMPLETED"]) + code = 1; + else if ([status isEqualToString: @"IN-PROCESS"]) + code = 2; + else if ([status isEqualToString: @"CANCELLED"]) + code = 3; + [row setObject: [NSNumber numberWithInt: code] forKey: @"c_status"]; + } + else { + /* confirmed by default */ + [row setObject:[NSNumber numberWithInt:1] forKey: @"c_status"]; + } + + [row setObject: [NSNumber numberWithUnsignedInt: accessClass] + forKey: @"c_classification"]; + + organizer = [self organizer]; + if (organizer) { + NSString *email; + + email = [organizer valueForKey: @"rfc822Email"]; + if (email) + [row setObject:email forKey: @"c_orgmail"]; + } + + /* construct partstates */ + count = [attendees count]; + partstates = [[NSMutableString alloc] initWithCapacity:count * 2]; + for ( i = 0; i < count; i++) { + iCalPerson *p; + iCalPersonPartStat stat; + + p = [attendees objectAtIndex:i]; + stat = [p participationStatus]; + if(i != 0) + [partstates appendString: @"\n"]; + [partstates appendFormat: @"%d", stat]; + } + [row setObject:partstates forKey: @"c_partstates"]; + [partstates release]; + + return row; +} + +@end diff --git a/SoObjects/SOGo/NSDictionary+Utilities.h b/SoObjects/SOGo/NSDictionary+Utilities.h index 1c5622f13..9427346d7 100644 --- a/SoObjects/SOGo/NSDictionary+Utilities.h +++ b/SoObjects/SOGo/NSDictionary+Utilities.h @@ -25,6 +25,7 @@ #import +@class NSArray; @class NSString; @interface NSDictionary (SOGoDictionaryUtilities) @@ -36,4 +37,11 @@ @end +@interface NSMutableDictionary (SOGoDictionaryUtilities) + +- (void) setObjects: (NSArray *) objects + forKeys: (NSArray *) keys; + +@end + #endif /* NSDICTIONARY_UTILITIES_H */ diff --git a/SoObjects/SOGo/NSDictionary+Utilities.m b/SoObjects/SOGo/NSDictionary+Utilities.m index ea604a8ed..b777b66d2 100644 --- a/SoObjects/SOGo/NSDictionary+Utilities.m +++ b/SoObjects/SOGo/NSDictionary+Utilities.m @@ -23,6 +23,7 @@ #import #import #import +#import #import #import @@ -103,3 +104,23 @@ } @end + +@implementation NSMutableDictionary (SOGoDictionaryUtilities) + +- (void) setObjects: (NSArray *) objects + forKeys: (NSArray *) keys +{ + unsigned int count, max; + + max = [objects count]; + if ([keys count] == max) + for (count = 0; count < max; count++) + [self setObject: [objects objectAtIndex: count] + forKey: [keys objectAtIndex: count]]; + else + [NSException raise: NSInvalidArgumentException + format: @"Number of objects does not match" + @" the number of keys."]; +} + +@end