]> err.no Git - scalable-opengroupware.org/blob - Protocols/iCalHTTP/SOGoICalFilePublish.m
initial sync
[scalable-opengroupware.org] / Protocols / iCalHTTP / SOGoICalFilePublish.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 OGo; 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 #include <NGObjWeb/WODirectAction.h>
23
24 @interface SOGoICalFilePublish : WODirectAction
25 {
26 }
27
28 @end
29
30 #include "SOGoICalHTTPHandler.h"
31 #include <SoObjects/Appointments/SOGoAppointmentFolder.h>
32 #include <SoObjects/Appointments/SOGoAppointmentObject.h>
33 #include <NGCards/NGCards.h>
34 #include <SaxObjC/SaxObjC.h>
35 #include "common.h"
36
37 @implementation SOGoICalFilePublish
38
39 static BOOL debugOn = YES;
40 static id<NSObject,SaxXMLReader> parser = nil;
41 static SaxObjectDecoder          *sax   = nil;
42
43 + (void)initialize {
44   if (parser == nil) {
45     parser = [[[SaxXMLReaderFactory standardXMLReaderFactory] 
46                 createXMLReaderForMimeType:@"text/calendar"]
47                 retain];
48     if (parser == nil)
49       NSLog(@"ERROR: did not find a parser for text/calendar!");
50   }
51   if (sax == nil) {
52     sax = [[SaxObjectDecoder alloc] initWithMappingNamed:@"NGCards"];
53     if (sax == nil)
54       NSLog(@"ERROR: could not create the iCal SAX handler!");
55   }
56   
57   [parser setContentHandler:sax];
58   [parser setErrorHandler:sax];
59 }
60
61 /* clientObject */
62
63 - (id)clientObject {
64   return [[super clientObject] aptFolderInContext:[self context]];
65 }
66
67 /* change sets */
68
69 - (void)extractNewVEvents:(NSArray **)_new updatedVEvents:(NSArray **)_updated
70   andDeletedUIDs:(NSArray **)_deleted
71   whenComparingOldUIDs:(NSArray *)_old withNewVEvents:(NSArray *)_pub
72 {
73   unsigned i, count;
74   
75   if (_new     != NULL ) *_new     = nil;
76   if (_updated != NULL ) *_updated = nil;
77   if (_deleted != NULL ) *_deleted = nil;
78   
79   /* scan old array for changes */
80   
81   for (i = 0, count = [_old count]; i < count; i++) {
82     id obj;
83     
84     obj = [_old objectAtIndex:i];
85     if (![obj isNotNull]) continue;
86     
87     if ([_pub containsObject:obj]) {
88       /* updated object, in both sets */
89       if (_updated == NULL) continue;
90       if (*_updated == nil) *_updated = [NSMutableArray arrayWithCapacity:16];
91       [(NSMutableArray *)*_updated addObject:obj];
92     }
93     else {
94       /* deleted object, only in old set */
95       if (_deleted == NULL) continue;
96       if (*_deleted == nil) *_deleted = [NSMutableArray arrayWithCapacity:4];
97       [(NSMutableArray *)*_deleted addObject:obj];
98     }
99   }
100
101   /* scan new array for new objects */
102
103   for (i = 0, count = [_pub count]; i < count; i++) {
104     id obj;
105     
106     obj = [_pub objectAtIndex:i];
107     if (![obj isNotNull]) continue;
108     
109     if ([_old containsObject:obj]) /* already processed */
110       continue;
111     
112     if (_new == NULL) continue;
113     if (*_new == nil) *_new = [NSMutableArray arrayWithCapacity:16];
114     [(NSMutableArray *)*_new addObject:obj];
115   }
116 }
117
118 /* operation */
119
120 - (NSException *)publishVToDos:(NSArray *)_todos {
121   // TODO: work on tasks folder?
122   
123   if ([_todos count] == 0)
124     return nil;
125   
126 #if 1
127   return [NSException exceptionWithHTTPStatus:501 /* Not Implemented */
128                       reason:@"server does not support vtodo PUTs!"];
129 #else
130   return nil /* means: OK */;
131 #endif
132 }
133
134 - (NSException *)writeNewVEvents:(NSArray *)_events {
135   SOGoAppointmentFolder *folder;
136   NSException *error;
137   unsigned i, count;
138   
139   if ((folder = [self clientObject]) == nil) {
140     return [NSException exceptionWithHTTPStatus:404 /* Not Found */
141                         reason:@"did not find clientObject?!"];
142   }
143
144   for (i = 0, count = [_events count]; i < count; i++) {
145     SOGoAppointmentObject *object;
146     iCalEvent *event;
147     NSString  *ical;
148     
149     event = [_events objectAtIndex:i];
150     ical  = [event versitString];
151
152     if (![ical isNotNull] && ([ical length] == 0)) {
153       [self logWithFormat:@"ERROR: got no ical representation of event: %@",
154               event];
155       continue;
156     }
157     
158     object = [folder lookupName:[event uid] inContext:[self context]
159                      acquire:NO];
160     if (![object isNotNull]) {
161       // TODO: what to do?
162       [self logWithFormat:@"ERROR: could not lookup event: %@", [event uid]];
163       continue;
164     }
165     
166     if ((error = [object saveContentString:ical]) != nil) /* failed, abort */
167       return error;
168   }
169   return nil; // TODO: fake OK
170 }
171
172 - (NSException *)updateVEvents:(NSArray *)_events {
173   if ([_events count] == 0)
174     return nil;
175   
176   [self logWithFormat:@"TODO: should update: %@", _events];
177   return nil; // TODO: fake OK
178 }
179
180 - (NSException *)deleteUIDs:(NSArray *)_uids {
181   if ([_uids count] == 0)
182     return nil;
183   
184   [self logWithFormat:@"TODO: should delete UIDs: %@", 
185         [_uids componentsJoinedByString:@", "]];
186   return nil; // TODO: fake OK
187 }
188
189 - (NSException *)publishVEvents:(NSArray *)_events {
190   // TODO: extract UIDs and compare sets
191   NSException *ex;
192   NSArray *availUIDs;
193   NSArray *new, *updated, *deleted;
194   
195   [self debugWithFormat:@"publish %d events ...", [_events count]];
196   
197   /* find changeset */
198   
199   availUIDs = [[self clientObject] toOneRelationshipKeys];
200
201   /* build changeset */
202   
203   [self extractNewVEvents:&new updatedVEvents:&updated andDeletedUIDs:&deleted
204         whenComparingOldUIDs:availUIDs
205         withNewVEvents:_events];
206   
207   /* process */
208   
209   if ([new count] > 0) {
210     if ((ex = [self writeNewVEvents:new]) != nil)
211       return ex;
212   }
213   
214   if ([updated count] > 0) {
215     if ((ex = [self updateVEvents:updated]) != nil)
216       return ex;
217   }
218   
219   if ([deleted count] > 0) {
220     if ((ex = [self deleteUIDs:deleted]) != nil)
221       return ex;
222   }
223   
224   return nil /* means: OK */;
225 }
226
227 - (NSException *)publishICalCalendar:(iCalCalendar *)_iCal {
228   NSException *ex;
229   
230   [self debugWithFormat:@"publish iCalCalendar: %@", _iCal];
231
232   if ((ex = [self publishVEvents:[_iCal events]]) != nil)
233     return ex;
234   
235   if ((ex = [self publishVToDos:[_iCal todos]]) != nil)
236     return ex;
237   
238   return nil /* means: OK */;
239 }
240
241 - (NSException *)publishICalendarString:(NSString *)_ical {
242   NSException *ex;
243   id root;
244   
245   [parser parseFromSource:_ical];
246   root = [[sax rootObject] retain]; /* retain to keep it around */
247   [sax reset];
248   
249   if (![root isNotNull]) {
250     [self debugWithFormat:@"invalid iCal input: %@", _ical];
251     return [NSException exceptionWithHTTPStatus:400 /* Bad Request */
252                         reason:@"could not parse iCal input?!"];
253   }
254   
255   if ([root isKindOfClass:[NSException class]])
256     return [root autorelease];
257   
258   if ([root isKindOfClass:[iCalCalendar class]]) {
259     ex = [self publishICalCalendar:root];
260   }
261   else {
262     ex = [NSException exceptionWithHTTPStatus:501 /* Not Implemented */
263                       reason:@"cannot deal with input"];
264   }
265   
266   [root release]; root = nil;
267   return ex /* means: OK */;
268 }
269
270 /* responses */
271
272 - (WOResponse *)publishOkResponse {
273   WOResponse *r;
274   
275   r = [[self context] response];
276   [r setStatus:200 /* OK */];
277   return r;
278 }
279
280 /* actions */
281
282 - (id)defaultAction {
283   /*
284     Note: Apple iCal.app submits no content-type!
285   */
286   NSString *s;
287   
288   s = [[[self context] request] contentAsString];
289   if ([s length] == 0) {
290     [self debugWithFormat:@"missing content!"];
291     return [NSException exceptionWithHTTPStatus:400 /* Bad Request */
292                         reason:@"missing iCalendar content"];
293   }
294   
295   if ([s hasPrefix:@"BEGIN:VCALENDAR"]) {
296     NSException *e;
297     
298     if ((e = [self publishICalendarString:s]) == nil)
299       return [self publishOkResponse];
300     
301     return e;
302   }
303   
304   [self debugWithFormat:@"ERROR: cannot process input: %@", s];
305   
306   /* Fake successful publish ... */
307   return [NSException exceptionWithHTTPStatus:400 /* Bad Request */
308                       reason:@"invalid input format"];
309 }
310
311 /* debugging */
312
313 - (BOOL)isDebuggingEnabled {
314   return debugOn;
315 }
316
317 @end /* SOGoICalFilePublish */