]> err.no Git - scalable-opengroupware.org/blob - OGoContentStore/OCSiCalFieldExtractor.m
git-svn-id: http://svn.opengroupware.org/SOGo/inverse/trunk@1235 d1b88da0-ebda-0310...
[scalable-opengroupware.org] / OGoContentStore / OCSiCalFieldExtractor.m
1 /*
2   Copyright (C) 2004 SKYRIX Software AG
3
4   This file is part of OpenGroupware.org.
5
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
9   later version.
10
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.
15
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
19   02111-1307, USA.
20 */
21
22 #import <Foundation/NSCalendarDate.h>
23 #import <Foundation/NSNull.h>
24 #import <Foundation/NSString.h>
25 #import <Foundation/NSValue.h>
26
27 #import <NGExtensions/NSNull+misc.h>
28 #import <NGExtensions/NSObject+Logs.h>
29
30 #import "iCalEntityObject+OCS.h"
31 #import "iCalRepeatableEntityObject+OCS.h"
32
33 #import "OCSiCalFieldExtractor.h"
34
35 @implementation OCSiCalFieldExtractor
36
37 static NSCalendarDate *distantFuture = nil;
38 static NSNumber *distantFutureNumber = nil;
39
40 + (void) initialize
41 {
42   if (!distantFuture)
43     {
44       distantFuture = [[NSCalendarDate distantFuture] retain];
45       /* INT_MAX due to Postgres constraint */
46       distantFutureNumber = [[NSNumber numberWithUnsignedInt: INT_MAX] retain];
47     }
48 }
49
50 + (id) sharedICalFieldExtractor
51 {
52   static OCSiCalFieldExtractor *extractor = nil;
53
54   if (!extractor)
55     extractor = [self new];
56
57   return extractor;
58 }
59
60 /* operations */
61
62 - (NSNumber *) numberForDate: (NSCalendarDate *) _date
63 {
64   return ((_date == distantFuture)
65           ? distantFutureNumber
66           : [NSNumber numberWithUnsignedInt: [_date timeIntervalSince1970]]);
67 }
68
69 - (NSMutableDictionary *) extractQuickFieldsFromEvent: (iCalEvent *) _event
70 {
71   NSMutableDictionary *row;
72   NSCalendarDate *startDate, *endDate;
73   NSArray *attendees;
74   NSString *uid, *title, *location, *status;
75   NSNumber *sequence, *dateNumber;
76   id organizer;
77   id participants, partmails;
78   NSMutableString *partstates;
79   unsigned int i, count;
80   BOOL isAllDay;
81   iCalAccessClass accessClass;
82
83   if (_event == nil)
84     return nil;
85
86   /* extract values */
87  
88   startDate = [_event startDate];
89   endDate = [_event endDate];
90   uid = [_event uid];
91   title = [_event summary];
92   if (![title isNotNull])
93     title = @"";
94   location = [_event location];
95   sequence = [_event sequence];
96   accessClass = [_event symbolicAccessClass];
97   isAllDay = [_event isAllDay];
98   status = [[_event status] uppercaseString];
99
100   attendees = [_event attendees];
101   partmails = [attendees valueForKey: @"rfc822Email"];
102   partmails = [partmails componentsJoinedByString: @"\n"];
103   participants = [attendees valueForKey: @"cn"];
104   participants = [participants componentsJoinedByString: @"\n"];
105
106   /* build row */
107
108   row = [NSMutableDictionary dictionaryWithCapacity:8];
109
110   [row setObject: @"vevent" forKey: @"c_component"];
111  
112   if ([uid isNotNull]) 
113     [row setObject:uid forKey: @"c_uid"];
114   else
115     [self logWithFormat: @"WARNING: could not extract a uid from event!"];
116
117
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"];
126
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"];
130
131   if ([startDate isNotNull])
132     [row setObject: [self numberForDate: startDate]
133          forKey: @"c_startdate"];
134   if ([endDate isNotNull])
135     {
136       if (endDate == distantFuture)
137         dateNumber = distantFutureNumber;
138       else
139         {
140           if (isAllDay)
141             i = 1;
142           else
143             i = 0;
144           dateNumber
145             = [NSNumber numberWithUnsignedInt:
146                           [endDate timeIntervalSince1970] - i];
147         }
148       [row setObject: dateNumber forKey: @"c_enddate"];
149     }
150
151   if ([_event isRecurrent]) {
152     NSCalendarDate *date;
153  
154     date = [_event lastPossibleRecurrenceStartDate];
155     if (!date) {
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;
159     }
160     [row setObject:[self numberForDate:date] forKey: @"c_cycleenddate"];
161     [row setObject:[_event cycleInfo] forKey: @"c_cycleinfo"];
162   }
163
164   if ([participants length] > 0)
165     [row setObject: participants forKey: @"c_participants"];
166   if ([partmails length] > 0)
167     [row setObject: partmails forKey: @"c_partmails"];
168
169   if ([status isNotNull]) {
170     int code = 1;
171  
172     if ([status isEqualToString: @"TENTATIVE"])
173       code = 2;
174     else if ([status isEqualToString: @"CANCELLED"])
175       code = 0;
176     [row setObject:[NSNumber numberWithInt:code] forKey: @"c_status"];
177   }
178   else {
179     /* confirmed by default */
180     [row setObject: [NSNumber numberWithInt:1] forKey: @"c_status"];
181   }
182
183   [row setObject: [NSNumber numberWithUnsignedInt: accessClass]
184        forKey: @"c_classification"];
185
186   organizer = [_event organizer];
187   if (organizer) {
188     NSString *email;
189  
190     email = [organizer valueForKey: @"rfc822Email"];
191     if (email)
192       [row setObject:email forKey: @"c_orgmail"];
193   }
194  
195   /* construct partstates */
196   count = [attendees count];
197   partstates = [[NSMutableString alloc] initWithCapacity:count * 2];
198   for ( i = 0; i < count; i++) {
199     iCalPerson *p;
200     iCalPersonPartStat stat;
201  
202     p = [attendees objectAtIndex:i];
203     stat = [p participationStatus];
204     if(i != 0)
205       [partstates appendString: @"\n"];
206     [partstates appendFormat: @"%d", stat];
207   }
208   [row setObject:partstates forKey: @"c_partstates"];
209   [partstates release];
210   return row;
211 }
212
213 - (NSMutableDictionary *) extractQuickFieldsFromTodo: (iCalToDo *) _task
214 {
215   NSMutableDictionary *row;
216   NSCalendarDate *startDate, *dueDate;
217   NSArray *attendees;
218   NSString *uid, *title, *location, *status;
219   NSNumber *sequence;
220   id organizer, date;
221   id participants, partmails;
222   NSMutableString *partstates;
223   unsigned i, count, code;
224   iCalAccessClass accessClass;
225
226   if (_task == nil)
227     return nil;
228
229   /* extract values */
230  
231   startDate = [_task startDate];
232   dueDate = [_task due];
233   uid = [_task uid];
234   title = [_task summary];
235   if (![title isNotNull])
236     title = @"";
237   location = [_task location];
238   sequence = [_task sequence];
239   accessClass = [_task symbolicAccessClass];
240   status = [[_task status] uppercaseString];
241
242   attendees = [_task attendees];
243   partmails = [attendees valueForKey: @"rfc822Email"];
244   partmails = [partmails componentsJoinedByString: @"\n"];
245   participants = [attendees valueForKey: @"cn"];
246   participants = [participants componentsJoinedByString: @"\n"];
247
248   /* build row */
249
250   row = [NSMutableDictionary dictionaryWithCapacity:8];
251
252   [row setObject: @"vtodo" forKey: @"c_component"];
253
254   if ([uid isNotNull]) 
255     [row setObject:uid forKey: @"c_uid"];
256   else
257     [self logWithFormat: @"WARNING: could not extract a uid from event!"];
258
259   [row setObject:[NSNumber numberWithBool:[_task isRecurrent]]
260        forKey: @"c_iscycle"];
261   [row setObject:[NSNumber numberWithInt:[_task priorityNumber]]
262        forKey: @"c_priority"];
263
264   [row setObject: [NSNumber numberWithBool: NO]
265        forKey: @"c_isallday"];
266   [row setObject: [NSNumber numberWithBool: NO]
267        forKey: @"c_isopaque"];
268
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"];
272  
273   if ([startDate isNotNull])
274     date = [self numberForDate: startDate];
275   else
276     date = [NSNull null];
277   [row setObject: date forKey: @"c_startdate"];
278
279   if ([dueDate isNotNull]) 
280     date = [self numberForDate: dueDate];
281   else
282     date = [NSNull null];
283   [row setObject: date forKey: @"c_enddate"];
284
285   if ([participants length] > 0)
286     [row setObject:participants forKey: @"c_participants"];
287   if ([partmails length] > 0)
288     [row setObject:partmails forKey: @"c_partmails"];
289
290   if ([status isNotNull]) {
291     code = 0; /* NEEDS-ACTION */
292     if ([status isEqualToString: @"COMPLETED"])
293       code = 1;
294     else if ([status isEqualToString: @"IN-PROCESS"])
295       code = 2;
296     else if ([status isEqualToString: @"CANCELLED"])
297       code = 3;
298     [row setObject: [NSNumber numberWithInt: code] forKey: @"c_status"];
299   }
300   else {
301     /* confirmed by default */
302     [row setObject:[NSNumber numberWithInt:1] forKey: @"c_status"];
303   }
304
305   [row setObject: [NSNumber numberWithUnsignedInt: accessClass]
306        forKey: @"c_classification"];
307
308   organizer = [_task organizer];
309   if (organizer) {
310     NSString *email;
311  
312     email = [organizer valueForKey: @"rfc822Email"];
313     if (email)
314       [row setObject:email forKey: @"c_orgmail"];
315   }
316  
317   /* construct partstates */
318   count = [attendees count];
319   partstates = [[NSMutableString alloc] initWithCapacity:count * 2];
320   for ( i = 0; i < count; i++) {
321     iCalPerson *p;
322     iCalPersonPartStat stat;
323  
324     p = [attendees objectAtIndex:i];
325     stat = [p participationStatus];
326     if(i != 0)
327       [partstates appendString: @"\n"];
328     [partstates appendFormat: @"%d", stat];
329   }
330   [row setObject:partstates forKey: @"c_partstates"];
331   [partstates release];
332   return row;
333 }
334
335 - (CardGroup *) firstElementFromCalendar: (iCalCalendar *) ical
336 {
337   NSArray *elements;
338   CardGroup *element;
339   unsigned int count;
340  
341   elements = [ical allObjects];
342   count = [elements count];
343   if (count)
344     {
345       if (count > 1)
346         [self logWithFormat:
347                 @"WARNING: given calendar contains more than one event: %@",
348               ical];
349       element = [elements objectAtIndex: 0];
350     }
351   else
352     {
353       [self logWithFormat: @"ERROR: given calendar contains no elements: %@", ical];
354       element = nil;
355     }
356
357   return element;
358 }
359
360 - (NSMutableDictionary *)extractQuickFieldsFromContent:(NSString *)_content {
361   NSDictionary *fields;
362   id cal;
363  
364   if ([_content length] == 0)
365     return nil;
366
367   cal = [iCalCalendar parseSingleFromSource: _content];
368  
369   fields = nil;
370   if (cal)
371     {
372       if ([cal isKindOfClass:[iCalCalendar class]])
373         cal = [self firstElementFromCalendar: cal];
374
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: %@",
381               cal];
382       }
383     }
384   else
385     [self logWithFormat: @"ERROR: parsing source didn't return anything"];
386
387   return [fields autorelease];
388 }
389
390 @end /* OCSiCalFieldExtractor */