2 Copyright (C) 2004 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 SOGo; see the file COPYING. If not, write to the
18 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
22 #import <Foundation/NSCalendarDate.h>
23 #import <Foundation/NSNull.h>
24 #import <Foundation/NSString.h>
25 #import <Foundation/NSValue.h>
27 #import <NGExtensions/NSNull+misc.h>
28 #import <NGExtensions/NSObject+Logs.h>
30 #import "iCalEntityObject+OCS.h"
31 #import "iCalRepeatableEntityObject+OCS.h"
33 #import "OCSiCalFieldExtractor.h"
35 @implementation OCSiCalFieldExtractor
37 static NSCalendarDate *distantFuture = nil;
38 static NSNumber *distantFutureNumber = nil;
44 distantFuture = [[NSCalendarDate distantFuture] retain];
45 /* INT_MAX due to Postgres constraint */
46 distantFutureNumber = [[NSNumber numberWithUnsignedInt: INT_MAX] retain];
50 + (id) sharedICalFieldExtractor
52 static OCSiCalFieldExtractor *extractor = nil;
55 extractor = [self new];
62 - (NSNumber *) numberForDate: (NSCalendarDate *) _date
64 return ((_date == distantFuture)
66 : [NSNumber numberWithUnsignedInt: [_date timeIntervalSince1970]]);
69 - (NSMutableDictionary *) extractQuickFieldsFromEvent: (iCalEvent *) _event
71 NSMutableDictionary *row;
72 NSCalendarDate *startDate, *endDate;
74 NSString *uid, *title, *location, *status;
75 NSNumber *sequence, *dateNumber;
77 id participants, partmails;
78 NSMutableString *partstates;
79 unsigned int i, count;
81 iCalAccessClass accessClass;
88 startDate = [_event startDate];
89 endDate = [_event endDate];
91 title = [_event summary];
92 if (![title isNotNull])
94 location = [_event location];
95 sequence = [_event sequence];
96 accessClass = [_event symbolicAccessClass];
97 isAllDay = [_event isAllDay];
98 status = [[_event status] uppercaseString];
100 attendees = [_event attendees];
101 partmails = [attendees valueForKey: @"rfc822Email"];
102 partmails = [partmails componentsJoinedByString: @"\n"];
103 participants = [attendees valueForKey: @"cn"];
104 participants = [participants componentsJoinedByString: @"\n"];
108 row = [NSMutableDictionary dictionaryWithCapacity:8];
110 [row setObject: @"vevent" forKey: @"c_component"];
113 [row setObject:uid forKey: @"c_uid"];
115 [self logWithFormat: @"WARNING: could not extract a uid from event!"];
118 [row setObject: [NSNumber numberWithBool: isAllDay]
119 forKey: @"c_isallday"];
120 [row setObject: [NSNumber numberWithBool: [_event isRecurrent]]
121 forKey: @"c_iscycle"];
122 [row setObject: [NSNumber numberWithBool: [_event isOpaque]]
123 forKey: @"c_isopaque"];
124 [row setObject: [NSNumber numberWithInt: [_event priorityNumber]]
125 forKey: @"c_priority"];
127 [row setObject: title forKey: @"c_title"];
128 if ([location isNotNull]) [row setObject: location forKey: @"c_location"];
129 if ([sequence isNotNull]) [row setObject: sequence forKey: @"c_sequence"];
131 if ([startDate isNotNull])
132 [row setObject: [self numberForDate: startDate]
133 forKey: @"c_startdate"];
134 if ([endDate isNotNull])
136 if (endDate == distantFuture)
137 dateNumber = distantFutureNumber;
145 = [NSNumber numberWithUnsignedInt:
146 [endDate timeIntervalSince1970] - i];
148 [row setObject: dateNumber forKey: @"c_enddate"];
151 if ([_event isRecurrent]) {
152 NSCalendarDate *date;
154 date = [_event lastPossibleRecurrenceStartDate];
156 /* this could also be *nil*, but in the end it makes the fetchspecs
157 more complex - thus we set it to a "reasonable" distant future */
158 date = distantFuture;
160 [row setObject:[self numberForDate:date] forKey: @"c_cycleenddate"];
161 [row setObject:[_event cycleInfo] forKey: @"c_cycleinfo"];
164 if ([participants length] > 0)
165 [row setObject: participants forKey: @"c_participants"];
166 if ([partmails length] > 0)
167 [row setObject: partmails forKey: @"c_partmails"];
169 if ([status isNotNull]) {
172 if ([status isEqualToString: @"TENTATIVE"])
174 else if ([status isEqualToString: @"CANCELLED"])
176 [row setObject:[NSNumber numberWithInt:code] forKey: @"c_status"];
179 /* confirmed by default */
180 [row setObject: [NSNumber numberWithInt:1] forKey: @"c_status"];
183 [row setObject: [NSNumber numberWithUnsignedInt: accessClass]
184 forKey: @"c_classification"];
186 organizer = [_event organizer];
190 email = [organizer valueForKey: @"rfc822Email"];
192 [row setObject:email forKey: @"c_orgmail"];
195 /* construct partstates */
196 count = [attendees count];
197 partstates = [[NSMutableString alloc] initWithCapacity:count * 2];
198 for ( i = 0; i < count; i++) {
200 iCalPersonPartStat stat;
202 p = [attendees objectAtIndex:i];
203 stat = [p participationStatus];
205 [partstates appendString: @"\n"];
206 [partstates appendFormat: @"%d", stat];
208 [row setObject:partstates forKey: @"c_partstates"];
209 [partstates release];
213 - (NSMutableDictionary *) extractQuickFieldsFromTodo: (iCalToDo *) _task
215 NSMutableDictionary *row;
216 NSCalendarDate *startDate, *dueDate;
218 NSString *uid, *title, *location, *status;
221 id participants, partmails;
222 NSMutableString *partstates;
223 unsigned i, count, code;
224 iCalAccessClass accessClass;
231 startDate = [_task startDate];
232 dueDate = [_task due];
234 title = [_task summary];
235 if (![title isNotNull])
237 location = [_task location];
238 sequence = [_task sequence];
239 accessClass = [_task symbolicAccessClass];
240 status = [[_task status] uppercaseString];
242 attendees = [_task attendees];
243 partmails = [attendees valueForKey: @"rfc822Email"];
244 partmails = [partmails componentsJoinedByString: @"\n"];
245 participants = [attendees valueForKey: @"cn"];
246 participants = [participants componentsJoinedByString: @"\n"];
250 row = [NSMutableDictionary dictionaryWithCapacity:8];
252 [row setObject: @"vtodo" forKey: @"c_component"];
255 [row setObject:uid forKey: @"c_uid"];
257 [self logWithFormat: @"WARNING: could not extract a uid from event!"];
259 [row setObject:[NSNumber numberWithBool:[_task isRecurrent]]
260 forKey: @"c_iscycle"];
261 [row setObject:[NSNumber numberWithInt:[_task priorityNumber]]
262 forKey: @"c_priority"];
264 [row setObject: [NSNumber numberWithBool: NO]
265 forKey: @"c_isallday"];
266 [row setObject: [NSNumber numberWithBool: NO]
267 forKey: @"c_isopaque"];
269 [row setObject: title forKey: @"c_title"];
270 if ([location isNotNull]) [row setObject: location forKey: @"c_location"];
271 if ([sequence isNotNull]) [row setObject: sequence forKey: @"c_sequence"];
273 if ([startDate isNotNull])
274 date = [self numberForDate: startDate];
276 date = [NSNull null];
277 [row setObject: date forKey: @"c_startdate"];
279 if ([dueDate isNotNull])
280 date = [self numberForDate: dueDate];
282 date = [NSNull null];
283 [row setObject: date forKey: @"c_enddate"];
285 if ([participants length] > 0)
286 [row setObject:participants forKey: @"c_participants"];
287 if ([partmails length] > 0)
288 [row setObject:partmails forKey: @"c_partmails"];
290 if ([status isNotNull]) {
291 code = 0; /* NEEDS-ACTION */
292 if ([status isEqualToString: @"COMPLETED"])
294 else if ([status isEqualToString: @"IN-PROCESS"])
296 else if ([status isEqualToString: @"CANCELLED"])
298 [row setObject: [NSNumber numberWithInt: code] forKey: @"c_status"];
301 /* confirmed by default */
302 [row setObject:[NSNumber numberWithInt:1] forKey: @"c_status"];
305 [row setObject: [NSNumber numberWithUnsignedInt: accessClass]
306 forKey: @"c_classification"];
308 organizer = [_task organizer];
312 email = [organizer valueForKey: @"rfc822Email"];
314 [row setObject:email forKey: @"c_orgmail"];
317 /* construct partstates */
318 count = [attendees count];
319 partstates = [[NSMutableString alloc] initWithCapacity:count * 2];
320 for ( i = 0; i < count; i++) {
322 iCalPersonPartStat stat;
324 p = [attendees objectAtIndex:i];
325 stat = [p participationStatus];
327 [partstates appendString: @"\n"];
328 [partstates appendFormat: @"%d", stat];
330 [row setObject:partstates forKey: @"c_partstates"];
331 [partstates release];
335 - (CardGroup *) firstElementFromCalendar: (iCalCalendar *) ical
341 elements = [ical allObjects];
342 count = [elements count];
347 @"WARNING: given calendar contains more than one event: %@",
349 element = [elements objectAtIndex: 0];
353 [self logWithFormat: @"ERROR: given calendar contains no elements: %@", ical];
360 - (NSMutableDictionary *)extractQuickFieldsFromContent:(NSString *)_content {
361 NSDictionary *fields;
364 if ([_content length] == 0)
367 cal = [iCalCalendar parseSingleFromSource: _content];
372 if ([cal isKindOfClass:[iCalCalendar class]])
373 cal = [self firstElementFromCalendar: cal];
375 if ([cal isKindOfClass:[iCalEvent class]])
376 fields = [[self extractQuickFieldsFromEvent:cal] retain];
377 else if ([cal isKindOfClass:[iCalToDo class]])
378 fields = [[self extractQuickFieldsFromTodo:cal] retain];
379 else if ([cal isNotNull]) {
380 [self logWithFormat: @"ERROR: unexpected iCalendar parse result: %@",
385 [self logWithFormat: @"ERROR: parsing source didn't return anything"];
387 return [fields autorelease];
390 @end /* OCSiCalFieldExtractor */