]> err.no Git - scalable-opengroupware.org/commitdiff
more work on iCal publish
authorhelge <helge@d1b88da0-ebda-0310-925b-ed51d893ca5b>
Tue, 19 Oct 2004 22:45:25 +0000 (22:45 +0000)
committerhelge <helge@d1b88da0-ebda-0310-925b-ed51d893ca5b>
Tue, 19 Oct 2004 22:45:25 +0000 (22:45 +0000)
git-svn-id: http://svn.opengroupware.org/SOGo/trunk@413 d1b88da0-ebda-0310-925b-ed51d893ca5b

12 files changed:
SOGo/Protocols/iCalHTTP/ChangeLog
SOGo/Protocols/iCalHTTP/GNUmakefile
SOGo/Protocols/iCalHTTP/SOGoICalFileFetch.m
SOGo/Protocols/iCalHTTP/SOGoICalFilePublish.m [new file with mode: 0644]
SOGo/Protocols/iCalHTTP/SOGoICalHTTPHandler.m
SOGo/Protocols/iCalHTTP/Version
SOGo/Protocols/iCalHTTP/product.plist
SOGo/SoObjects/Appointments/ChangeLog
SOGo/SoObjects/Appointments/SOGoAppointmentFolder.h
SOGo/SoObjects/Appointments/SOGoAppointmentFolder.m
SOGo/SoObjects/Appointments/SOGoAppointmentObject.m
SOGo/SoObjects/Appointments/Version

index 5f48bd54d1c6920abb89880fd15036aa3af67e7a..7108d6a653f68ddddac5f9d8827ae9c3a4ce1ad5 100644 (file)
@@ -1,5 +1,7 @@
 2004-10-19  Helge Hess  <helge.hess@opengroupware.org>
 
+       * 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)
index 119e77a92cc57ac42ab0f7a422fbf7b434a374ea..7b280b50f06babdbe78bea343ce3789e92598fb9 100644 (file)
@@ -12,6 +12,7 @@ iCalHTTP_OBJC_FILES = \
        iCalHTTPProduct.m       \
        SOGoICalHTTPHandler.m   \
        SOGoICalFileFetch.m     \
+       SOGoICalFilePublish.m   \
 
 iCalHTTP_RESOURCE_FILES += \
        Version         \
index 2ec3f4626a86623d07814e881a6175ed433ba909..759435579c17e487f75515da9d601bdc674d8163 100644 (file)
 
 @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 (file)
index 0000000..4be72e2
--- /dev/null
@@ -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 <NGObjWeb/WODirectAction.h>
+
+@interface SOGoICalFilePublish : WODirectAction
+{
+}
+
+@end
+
+#include "SOGoICalHTTPHandler.h"
+#include <SOGo/SoObjects/Appointments/SOGoAppointmentFolder.h>
+#include <SOGo/SoObjects/Appointments/SOGoAppointmentObject.h>
+#include <SOGoLogic/SOGoAppointment.h>
+#include <NGiCal/NGiCal.h>
+#include <NGiCal/iCalRenderer.h>
+#include <SaxObjC/SaxObjC.h>
+#include "common.h"
+
+@implementation SOGoICalFilePublish
+
+static BOOL debugOn = YES;
+static id<NSObject,SaxXMLReader> 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 */
index 2dda1c16245a2bcff62e374968a487c9eeb95309..ccf3593fafe009a4c89490920c9ddcd5c82c6a33 100644 (file)
   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 {
index bdf5923429d5ba1e5aeff0854d5fc5d5b17c5330..59c54d77979c2307f5c7363a4ae4d6cbb7cef984 100644 (file)
@@ -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
index 8429db3f1100b23d795d31d0ff9c4c377b4c6765..bad045e35ab0888d5a3f63503a35ec50fefbf52c 100644 (file)
     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";
         };
         PUT = {
           protectedBy = "View";
-          selector = {
-            name                = "updateEventsInContext:"; 
-            addContextParameter = YES;
-          };
+          actionClass = "SOGoICalFilePublish";
         };
       };
     };
index 9f6a87600d539b6104545a6b381859a80ecf7f6f..e6ff7181d406ea8d2f244ea353467caf81be7da9 100644 (file)
@@ -1,3 +1,10 @@
+2004-10-20  Helge Hess  <helge.hess@opengroupware.org>
+
+       * 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  <helge.hess@opengroupware.org>
 
        * SOGoAppointmentFolder.h: added -fetchAllSOGoAppointments method
index 753e2314fb917430f986c761c2782d1103961638..51949c80b394d3f5ba6882b9248261625e8b0d50 100644 (file)
 
 /* 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;
index fadb0f293542dc90f0e9d4350756dd8a9538608a..24b443728a94120fa19a3f12a4087e3b0f1da0d4 100644 (file)
 #include <unistd.h>
 #include <stdlib.h>
 
+#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;
 }
index badce9c45f8e93c325f101961c6eb3bb16b4c063..d7ffe5b7e3e1c035bf9e3325aeb076d633b91277 100644 (file)
   /* 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 */
 
     // TODO
   }
   
-  oldUIDs = [self attendeeUIDsFromAppointment:oldApt];
+  oldUIDs = [oldApt isNotNull]
+    ? [self attendeeUIDsFromAppointment:oldApt] 
+    : nil;
   
   /* handle new content */
   
   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;
 }
index 1df7b254b2a5fec41491147029ed397517a86109..8c1adff0f65eee7be3f2a8067ee3b6141d149f38 100644 (file)
@@ -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