From: helge Date: Tue, 19 Oct 2004 22:45:25 +0000 (+0000) Subject: more work on iCal publish X-Git-Url: https://err.no/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=cfdcfcf0befb8ee9180e5c158ddca66535c783c8;p=scalable-opengroupware.org more work on iCal publish git-svn-id: http://svn.opengroupware.org/SOGo/trunk@413 d1b88da0-ebda-0310-925b-ed51d893ca5b --- diff --git a/SOGo/Protocols/iCalHTTP/ChangeLog b/SOGo/Protocols/iCalHTTP/ChangeLog index 5f48bd54..7108d6a6 100644 --- a/SOGo/Protocols/iCalHTTP/ChangeLog +++ b/SOGo/Protocols/iCalHTTP/ChangeLog @@ -1,5 +1,7 @@ 2004-10-19 Helge Hess + * started SOGoICalFilePublish.m for allowing iCal PUTs + * moved fetch method to a direct action SoMethod (v0.9.3) * SOGoICalHTTPHandler.m: some work on assembly support (v0.9.2) diff --git a/SOGo/Protocols/iCalHTTP/GNUmakefile b/SOGo/Protocols/iCalHTTP/GNUmakefile index 119e77a9..7b280b50 100644 --- a/SOGo/Protocols/iCalHTTP/GNUmakefile +++ b/SOGo/Protocols/iCalHTTP/GNUmakefile @@ -12,6 +12,7 @@ iCalHTTP_OBJC_FILES = \ iCalHTTPProduct.m \ SOGoICalHTTPHandler.m \ SOGoICalFileFetch.m \ + SOGoICalFilePublish.m \ iCalHTTP_RESOURCE_FILES += \ Version \ diff --git a/SOGo/Protocols/iCalHTTP/SOGoICalFileFetch.m b/SOGo/Protocols/iCalHTTP/SOGoICalFileFetch.m index 2ec3f462..75943557 100644 --- a/SOGo/Protocols/iCalHTTP/SOGoICalFileFetch.m +++ b/SOGo/Protocols/iCalHTTP/SOGoICalFileFetch.m @@ -34,18 +34,46 @@ @implementation SOGoICalFileFetch +/* clientObject */ + - (id)clientObject { return [[super clientObject] aptFolderInContext:[self context]]; } +/* actions */ + - (id)defaultAction { - NSArray *events; + NSAutoreleasePool *pool; + WOResponse *response; + SOGoAppointment *event; + NSEnumerator *e; + NSArray *events; + + response = [[self context] response]; + [response setHeader:@"text/plain" forKey:@"content-type"]; + + pool = [[NSAutoreleasePool alloc] init]; + + /* vcal preamble */ + + [response appendContentString:@"BEGIN:VCALENDAR\r\nMETHOD:REQUEST\r\n"]; + [response appendContentString:@"PRODID:SOGo/0.9\r\n"]; + [response appendContentString:@"VERSION:2.0\r\n"]; + + /* add events */ - [self logWithFormat:@"assemble iCal events ..."]; events = [[self clientObject] fetchAllSOGoAppointments]; - [self logWithFormat:@" events: %@", events]; + [self debugWithFormat:@"generate %d appointments ...", [events count]]; + e = [events objectEnumerator]; + while ((event = [e nextObject]) != nil) { + [response appendContentString:[event vEventString]]; + } + + /* vcal postamble */ + [response appendContentString:@"END:VCALENDAR\r\n"]; - return nil; + [pool release]; + return response; } @end /* SOGoICalFileFetch */ diff --git a/SOGo/Protocols/iCalHTTP/SOGoICalFilePublish.m b/SOGo/Protocols/iCalHTTP/SOGoICalFilePublish.m new file mode 100644 index 00000000..4be72e2e --- /dev/null +++ b/SOGo/Protocols/iCalHTTP/SOGoICalFilePublish.m @@ -0,0 +1,322 @@ +/* + Copyright (C) 2004 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. +*/ + +#include + +@interface SOGoICalFilePublish : WODirectAction +{ +} + +@end + +#include "SOGoICalHTTPHandler.h" +#include +#include +#include +#include +#include +#include +#include "common.h" + +@implementation SOGoICalFilePublish + +static BOOL debugOn = YES; +static id parser = nil; +static SaxObjectDecoder *sax = nil; + ++ (void)initialize { + if (parser == nil) { + parser = [[[SaxXMLReaderFactory standardXMLReaderFactory] + createXMLReaderForMimeType:@"text/calendar"] + retain]; + if (parser == nil) + NSLog(@"ERROR: did not find a parser for text/calendar!"); + } + if (sax == nil) { + sax = [[SaxObjectDecoder alloc] initWithMappingNamed:@"NGiCal"]; + if (sax == nil) + NSLog(@"ERROR: could not create the iCal SAX handler!"); + } + + [parser setContentHandler:sax]; + [parser setErrorHandler:sax]; +} + +/* clientObject */ + +- (id)clientObject { + return [[super clientObject] aptFolderInContext:[self context]]; +} + +/* change sets */ + +- (void)extractNewVEvents:(NSArray **)_new updatedVEvents:(NSArray **)_updated + andDeletedUIDs:(NSArray **)_deleted + whenComparingOldUIDs:(NSArray *)_old withNewVEvents:(NSArray *)_pub +{ + unsigned i, count; + + if (_new != NULL ) *_new = nil; + if (_updated != NULL ) *_updated = nil; + if (_deleted != NULL ) *_deleted = nil; + + /* scan old array for changes */ + + for (i = 0, count = [_old count]; i < count; i++) { + id obj; + + obj = [_old objectAtIndex:i]; + if (![obj isNotNull]) continue; + + if ([_pub containsObject:obj]) { + /* updated object, in both sets */ + if (_updated == NULL) continue; + if (*_updated == nil) *_updated = [NSMutableArray arrayWithCapacity:16]; + [(NSMutableArray *)*_updated addObject:obj]; + } + else { + /* deleted object, only in old set */ + if (_deleted == NULL) continue; + if (*_deleted == nil) *_deleted = [NSMutableArray arrayWithCapacity:4]; + [(NSMutableArray *)*_deleted addObject:obj]; + } + } + + /* scan new array for new objects */ + + for (i = 0, count = [_pub count]; i < count; i++) { + id obj; + + obj = [_pub objectAtIndex:i]; + if (![obj isNotNull]) continue; + + if ([_old containsObject:obj]) /* already processed */ + continue; + + if (_new == NULL) continue; + if (*_new == nil) *_new = [NSMutableArray arrayWithCapacity:16]; + [(NSMutableArray *)*_new addObject:obj]; + } +} + +/* operation */ + +- (NSException *)publishVToDos:(NSArray *)_todos { + // TODO: work on tasks folder? + + if ([_todos count] == 0) + return nil; + +#if 1 + return [NSException exceptionWithHTTPStatus:501 /* Not Implemented */ + reason:@"server does not support vtodo PUTs!"]; +#else + return nil /* means: OK */; +#endif +} + +- (NSException *)writeNewVEvents:(NSArray *)_events { + SOGoAppointmentFolder *folder; + iCalRenderer *renderer; + NSException *error; + unsigned i, count; + + if ((folder = [self clientObject]) == nil) { + return [NSException exceptionWithHTTPStatus:404 /* Not Found */ + reason:@"did not find clientObject?!"]; + } + + renderer = [iCalRenderer sharedICalendarRenderer]; + + for (i = 0, count = [_events count]; i < count; i++) { + SOGoAppointmentObject *object; + iCalEvent *event; + NSString *ical; + + event = [_events objectAtIndex:i]; + ical = [renderer iCalendarStringForEvent:event]; + + if (![ical isNotNull] && ([ical length] == 0)) { + [self logWithFormat:@"ERROR: got no ical representation of event: %@", + event]; + continue; + } + + object = [folder lookupName:[event uid] inContext:[self context] + acquire:NO]; + if (![object isNotNull]) { + // TODO: what to do? + [self logWithFormat:@"ERROR: could not lookup event: %@", [event uid]]; + continue; + } + + if ((error = [object saveContentString:ical]) != nil) /* failed, abort */ + return error; + } + return nil; // TODO: fake OK +} + +- (NSException *)updateVEvents:(NSArray *)_events { + if ([_events count] == 0) + return nil; + + [self logWithFormat:@"TODO: should update: %@", _events]; + return nil; // TODO: fake OK +} + +- (NSException *)deleteUIDs:(NSArray *)_uids { + if ([_uids count] == 0) + return nil; + + [self logWithFormat:@"TODO: should delete UIDs: %@", + [_uids componentsJoinedByString:@", "]]; + return nil; // TODO: fake OK +} + +- (NSException *)publishVEvents:(NSArray *)_events { + // TODO: extract UIDs and compare sets + NSException *ex; + NSArray *availUIDs; + NSArray *new, *updated, *deleted; + + [self debugWithFormat:@"publish %d events ...", [_events count]]; + + /* find changeset */ + + availUIDs = [[self clientObject] toOneRelationshipKeys]; + + /* build changeset */ + + [self extractNewVEvents:&new updatedVEvents:&updated andDeletedUIDs:&deleted + whenComparingOldUIDs:availUIDs + withNewVEvents:_events]; + + /* process */ + + if ([new count] > 0) { + if ((ex = [self writeNewVEvents:new]) != nil) + return ex; + } + + if ([updated count] > 0) { + if ((ex = [self updateVEvents:updated]) != nil) + return ex; + } + + if ([deleted count] > 0) { + if ((ex = [self deleteUIDs:deleted]) != nil) + return ex; + } + + return nil /* means: OK */; +} + +- (NSException *)publishICalCalendar:(iCalCalendar *)_iCal { + NSException *ex; + + [self debugWithFormat:@"publish iCalCalendar: %@", _iCal]; + + if ((ex = [self publishVEvents:[_iCal events]]) != nil) + return ex; + + if ((ex = [self publishVToDos:[_iCal todos]]) != nil) + return ex; + + return nil /* means: OK */; +} + +- (NSException *)publishICalendarString:(NSString *)_ical { + NSException *ex; + id root; + + [parser parseFromSource:_ical]; + root = [[sax rootObject] retain]; /* retain to keep it around */ + [sax reset]; + + if (![root isNotNull]) { + [self debugWithFormat:@"invalid iCal input: %@", _ical]; + return [NSException exceptionWithHTTPStatus:400 /* Bad Request */ + reason:@"could not parse iCal input?!"]; + } + + if ([root isKindOfClass:[NSException class]]) + return [root autorelease]; + + if ([root isKindOfClass:[iCalCalendar class]]) { + ex = [self publishICalCalendar:root]; + } + else { + ex = [NSException exceptionWithHTTPStatus:501 /* Not Implemented */ + reason:@"cannot deal with input"]; + } + + [root release]; root = nil; + return ex /* means: OK */; +} + +/* responses */ + +- (WOResponse *)publishOkResponse { + WOResponse *r; + + r = [[self context] response]; + [r setStatus:200 /* OK */]; + return r; +} + +/* actions */ + +- (id)defaultAction { + /* + Note: Apple iCal.app submits no content-type! + */ + NSString *s; + + s = [[[self context] request] contentAsString]; + if ([s length] == 0) { + [self debugWithFormat:@"missing content!"]; + return [NSException exceptionWithHTTPStatus:400 /* Bad Request */ + reason:@"missing iCalendar content"]; + } + + if ([s hasPrefix:@"BEGIN:VCALENDAR"]) { + NSException *e; + + if ((e = [self publishICalendarString:s]) == nil) + return [self publishOkResponse]; + + return e; + } + + [self debugWithFormat:@"ERROR: cannot process input: %@", s]; + + /* Fake successful publish ... */ + return [NSException exceptionWithHTTPStatus:400 /* Bad Request */ + reason:@"invalid input format"]; +} + +/* debugging */ + +- (BOOL)isDebuggingEnabled { + return debugOn; +} + +@end /* SOGoICalFilePublish */ diff --git a/SOGo/Protocols/iCalHTTP/SOGoICalHTTPHandler.m b/SOGo/Protocols/iCalHTTP/SOGoICalHTTPHandler.m index 2dda1c16..ccf3593f 100644 --- a/SOGo/Protocols/iCalHTTP/SOGoICalHTTPHandler.m +++ b/SOGo/Protocols/iCalHTTP/SOGoICalHTTPHandler.m @@ -67,6 +67,31 @@ return folder; } +/* name lookup */ + +- (id)lookupName:(NSString *)_name inContext:(id)_ctx acquire:(BOOL)_flag { + id obj; + + /* check superclass */ + + if ((obj = [super lookupName:_name inContext:_ctx acquire:NO]) != nil) + return obj; + + /* check whether name is an '.ics' file */ + + if ([[_name pathExtension] isEqualToString:@"ics"]) { + /* + If I configure a WebDAV publish URL in Apple iCal.app, iCal.app adds + a calendar filename. Eg: + Base: /SOGo/so/helge/Calendar/ics + File: /SOGo/so/helge/Calendar/ics/SOGo32Publish.ics + */ + return self; + } + + return nil; +} + /* actions */ - (id)updateEventsInContext:(id)_ctx { diff --git a/SOGo/Protocols/iCalHTTP/Version b/SOGo/Protocols/iCalHTTP/Version index bdf59234..59c54d77 100644 --- a/SOGo/Protocols/iCalHTTP/Version +++ b/SOGo/Protocols/iCalHTTP/Version @@ -1,3 +1,5 @@ -# $Id: Version 368 2004-10-06 19:28:12Z znek $ +# Version file SUBMINOR_VERSION:=3 + +# v0.9.3 requires NGiCal v4.3.32 diff --git a/SOGo/Protocols/iCalHTTP/product.plist b/SOGo/Protocols/iCalHTTP/product.plist index 8429db3f..bad045e3 100644 --- a/SOGo/Protocols/iCalHTTP/product.plist +++ b/SOGo/Protocols/iCalHTTP/product.plist @@ -11,6 +11,14 @@ SOGoICalHTTPHandler = { superclass = "NSObject"; protectedBy = "View"; + + defaultRoles = { + /* Note: we need that for PUT by WebDAV clients (checked by WebDAV) */ + + // TODO: should be owner? + "Add Documents, Images, and Files" = "Authenticated"; + }; + methods = { GET = { protectedBy = "View"; @@ -18,10 +26,7 @@ }; PUT = { protectedBy = "View"; - selector = { - name = "updateEventsInContext:"; - addContextParameter = YES; - }; + actionClass = "SOGoICalFilePublish"; }; }; }; diff --git a/SOGo/SoObjects/Appointments/ChangeLog b/SOGo/SoObjects/Appointments/ChangeLog index 9f6a8760..e6ff7181 100644 --- a/SOGo/SoObjects/Appointments/ChangeLog +++ b/SOGo/SoObjects/Appointments/ChangeLog @@ -1,3 +1,10 @@ +2004-10-20 Helge Hess + + * SOGoAppointmentFolder.m: fixed a warning on MacOSX (v0.9.15) + + * SOGoAppointmentObject.m(saveContentString:): fixed a warning with new + objects (v0.9.14) + 2004-10-19 Helge Hess * SOGoAppointmentFolder.h: added -fetchAllSOGoAppointments method diff --git a/SOGo/SoObjects/Appointments/SOGoAppointmentFolder.h b/SOGo/SoObjects/Appointments/SOGoAppointmentFolder.h index 753e2314..51949c80 100644 --- a/SOGo/SoObjects/Appointments/SOGoAppointmentFolder.h +++ b/SOGo/SoObjects/Appointments/SOGoAppointmentFolder.h @@ -49,13 +49,13 @@ /* fetching */ -- (NSArray *)fetchCoreInfosFrom:(NSCalendarDate *)_startDate - to:(NSCalendarDate *)_endDate; - - (NSArray *)fetchCoreInfosFromFolder:(OCSFolder *)_folder from:(NSCalendarDate *)_startDate to:(NSCalendarDate *)_endDate; +- (NSArray *)fetchCoreInfosFrom:(NSCalendarDate *)_startDate + to:(NSCalendarDate *)_endDate; + /* URL generation */ - (NSString *)baseURLForAptWithUID:(NSString *)_uid inContext:(id)_ctx; diff --git a/SOGo/SoObjects/Appointments/SOGoAppointmentFolder.m b/SOGo/SoObjects/Appointments/SOGoAppointmentFolder.m index fadb0f29..24b44372 100644 --- a/SOGo/SoObjects/Appointments/SOGoAppointmentFolder.m +++ b/SOGo/SoObjects/Appointments/SOGoAppointmentFolder.m @@ -29,6 +29,12 @@ #include #include +#if APPLE_Foundation_LIBRARY || NeXT_Foundation_LIBRARY +@interface NSDate(UsedPrivates) +- (id)initWithTimeIntervalSince1970:(NSTimeInterval)_interval; +@end +#endif + @implementation SOGoAppointmentFolder static BOOL debugOn = NO; @@ -66,8 +72,9 @@ static NSTimeZone *MET = nil; /* selection */ - (NSArray *)calendarUIDs { + /* this is used for group calendars (this folder just returns itself) */ NSString *s; - + s = [[self container] nameInContainer]; return [s isNotNull] ? [NSArray arrayWithObjects:&s count:1] : nil; } diff --git a/SOGo/SoObjects/Appointments/SOGoAppointmentObject.m b/SOGo/SoObjects/Appointments/SOGoAppointmentObject.m index badce9c4..d7ffe5b7 100644 --- a/SOGo/SoObjects/Appointments/SOGoAppointmentObject.m +++ b/SOGo/SoObjects/Appointments/SOGoAppointmentObject.m @@ -208,8 +208,15 @@ /* handle old content */ oldContent = [self iCalString]; /* if nil, this is a new appointment */ - oldApt = - [[[SOGoAppointment alloc] initWithICalString:oldContent] autorelease]; + if ([oldContent length] == 0) { + /* new appointment */ + [self debugWithFormat:@"saving new appointment ..."]; + oldApt = nil; + } + else { + oldApt = + [[[SOGoAppointment alloc] initWithICalString:oldContent] autorelease]; + } /* compare sequence if requested */ @@ -217,7 +224,9 @@ // TODO } - oldUIDs = [self attendeeUIDsFromAppointment:oldApt]; + oldUIDs = [oldApt isNotNull] + ? [self attendeeUIDsFromAppointment:oldApt] + : nil; /* handle new content */ @@ -261,8 +270,8 @@ delError = [self deleteInUIDs:removedUIDs]; // TODO: make compound - if (storeError) return storeError; - if (delError) return delError; + if (storeError != nil) return storeError; + if (delError != nil) return delError; return nil; } diff --git a/SOGo/SoObjects/Appointments/Version b/SOGo/SoObjects/Appointments/Version index 1df7b254..8c1adff0 100644 --- a/SOGo/SoObjects/Appointments/Version +++ b/SOGo/SoObjects/Appointments/Version @@ -1,5 +1,5 @@ -# $Id: Version,v 1.9 2004/05/19 14:30:45 helge Exp $ +# Version file -SUBMINOR_VERSION:=13 +SUBMINOR_VERSION:=15 # v0.9.13 requires libSOGo v0.9.26