[CalendarServer-changes] [1862] CalendarServer/branches/users/cdaboo/server2server-1842

source_changes at macosforge.org source_changes at macosforge.org
Mon Sep 10 20:20:43 PDT 2007


Revision: 1862
          http://trac.macosforge.org/projects/calendarserver/changeset/1862
Author:   cdaboo at apple.com
Date:     2007-09-10 20:20:43 -0700 (Mon, 10 Sep 2007)

Log Message:
-----------
Free-busy URL support. This adds a freebusy resource into each user's calendar home. A GET or POST on that resource
with the appropriate parameters will return a VFREEBUSY component with free-busy information for that user. Currently
following the prototype spec being worked on in Calconnect.

Modified Paths:
--------------
    CalendarServer/branches/users/cdaboo/server2server-1842/conf/caldavd-test.plist
    CalendarServer/branches/users/cdaboo/server2server-1842/conf/caldavd.plist
    CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/config.py
    CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/customxml.py
    CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/directory/calendar.py
    CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/schedule_common.py
    CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/static.py

Added Paths:
-----------
    CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/freebusyurl.py

Modified: CalendarServer/branches/users/cdaboo/server2server-1842/conf/caldavd-test.plist
===================================================================
--- CalendarServer/branches/users/cdaboo/server2server-1842/conf/caldavd-test.plist	2007-09-10 23:40:49 UTC (rev 1861)
+++ CalendarServer/branches/users/cdaboo/server2server-1842/conf/caldavd-test.plist	2007-09-11 03:20:43 UTC (rev 1862)
@@ -343,6 +343,14 @@
   	<string>conf/servertoserver-test.xml</string>
   </dict>
 
+  <!-- Free-busy URL protocol -->
+  <key>FreeBusyURL</key>
+  <dict>
+  	<key>Enabled</key>
+  	<true/>
+  	<key>Time Period</key>
+  	<integer>14</integer>
+  </dict>
 
   <!--
     Twisted

Modified: CalendarServer/branches/users/cdaboo/server2server-1842/conf/caldavd.plist
===================================================================
--- CalendarServer/branches/users/cdaboo/server2server-1842/conf/caldavd.plist	2007-09-10 23:40:49 UTC (rev 1861)
+++ CalendarServer/branches/users/cdaboo/server2server-1842/conf/caldavd.plist	2007-09-11 03:20:43 UTC (rev 1862)
@@ -277,5 +277,14 @@
   	<string>/etc/caldavd/servertoserver.xml</string>
   </dict>
 
+  <!-- Free-busy URL protocol -->
+  <key>FreeBusyURL</key>
+  <dict>
+  	<key>Enabled</key>
+  	<true/>
+  	<key>Time Period</key>
+  	<integer>14</integer>
+  </dict>
+
 </dict>
 </plist>

Modified: CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/config.py
===================================================================
--- CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/config.py	2007-09-10 23:40:49 UTC (rev 1861)
+++ CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/config.py	2007-09-11 03:20:43 UTC (rev 1862)
@@ -154,6 +154,11 @@
         "HTTP Domain"     : "",    # Domain for http calendar user addresses on this server
     },
 
+    "FreeBusyURL": {
+        "Enabled"         : False, # Per-user free-busy-url protocol
+        "Time Period"     : 14,    # Number of days into the future to generate f-b data if no explicit time-range is specified
+    },
+
     #
     # Implementation details
     #

Modified: CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/customxml.py
===================================================================
--- CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/customxml.py	2007-09-10 23:40:49 UTC (rev 1861)
+++ CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/customxml.py	2007-09-11 03:20:43 UTC (rev 1862)
@@ -256,12 +256,20 @@
 
 class ServerToServerInbox (davxml.WebDAVEmptyElement):
     """
-    Denotes the resourcetype of a server-to_server Inbox.
+    Denotes the resourcetype of a server-to-server Inbox.
     (CalDAV-s2s-xx, section x.x.x)
     """
     namespace = calendarserver_namespace
     name = "server-to-server-inbox"
 
+class FreeBusyURL (davxml.WebDAVEmptyElement):
+    """
+    Denotes the resourcetype of a free-busy URL resource.
+    (CalDAV-s2s-xx, section x.x.x)
+    """
+    namespace = calendarserver_namespace
+    name = "free-busy-url"
+
 ##
 # Extensions to davxml.ResourceType
 ##
@@ -272,3 +280,4 @@
 davxml.ResourceType.calendarproxyread = davxml.ResourceType(davxml.Principal(), davxml.Collection(), CalendarProxyRead())
 davxml.ResourceType.calendarproxywrite = davxml.ResourceType(davxml.Principal(), davxml.Collection(), CalendarProxyWrite())
 davxml.ResourceType.servertoserverinbox = davxml.ResourceType(ServerToServerInbox())
+davxml.ResourceType.freebusyurl = davxml.ResourceType(FreeBusyURL())

Modified: CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/directory/calendar.py
===================================================================
--- CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/directory/calendar.py	2007-09-10 23:40:49 UTC (rev 1861)
+++ CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/directory/calendar.py	2007-09-11 03:20:43 UTC (rev 1862)
@@ -26,16 +26,15 @@
     "DirectoryCalendarHomeResource",
 ]
 
-from twisted.web2 import responsecode
 from twisted.web2.dav import davxml
 from twisted.web2.dav.util import joinURL
 from twisted.web2.dav.resource import TwistedACLInheritable, TwistedQuotaRootProperty
-from twisted.web2.http import HTTPError
 
 from twistedcaldav import caldavxml
 from twistedcaldav.config import config
 from twistedcaldav.dropbox import DropBoxHomeResource
 from twistedcaldav.extensions import ReadOnlyResourceMixIn, DAVResource
+from twistedcaldav.freebusyurl import FreeBusyURLResource
 from twistedcaldav.notifications import NotificationsCollectionResource
 from twistedcaldav.resource import CalDAVResource
 from twistedcaldav.schedule import ScheduleInboxResource, ScheduleOutboxResource
@@ -201,6 +200,10 @@
             childlist += (
                 ("notifications", NotificationsCollectionResource),
             )
+        if config.FreeBusyURL["Enabled"]:
+            childlist += (
+                ("freebusy", FreeBusyURLResource),
+            )
         for name, cls in childlist:
             child = self.provisionChild(name)
             assert isinstance(child, cls), "Child %r is not a %s: %r" % (name, cls.__name__, child)

Added: CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/freebusyurl.py
===================================================================
--- CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/freebusyurl.py	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/freebusyurl.py	2007-09-11 03:20:43 UTC (rev 1862)
@@ -0,0 +1,237 @@
+##
+# Copyright (c) 2005-2007 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# DRI: Cyrus Daboo, cdaboo at apple.com
+##
+
+"""
+Free-busy-URL resources.
+"""
+
+__all__ = [
+    "FreeBusyURLResource",
+]
+
+from twisted.internet.defer import deferredGenerator
+from twisted.internet.defer import waitForDeferred
+from twisted.python import log
+from twisted.web2 import responsecode
+from twisted.web2.dav import davxml
+from twisted.web2.dav.http import ErrorResponse
+from twisted.web2.http import HTTPError
+from twisted.web2.http import Response
+from twisted.web2.http import StatusResponse
+from twisted.web2.http_headers import MimeType
+from twisted.web2.stream import MemoryStream
+
+from twistedcaldav import caldavxml
+from twistedcaldav.caldavxml import TimeRange
+from twistedcaldav.config import config
+from twistedcaldav.customxml import calendarserver_namespace
+from twistedcaldav.ical import Property
+from twistedcaldav.ical import parse_datetime
+from twistedcaldav.ical import parse_duration
+from twistedcaldav.resource import CalDAVResource
+from twistedcaldav.schedule_common import Scheduler
+
+from vobject.icalendar import utc
+
+import datetime
+
+class FreeBusyURLResource (CalDAVResource):
+    """
+    Free-busy URL resource.
+
+    Extends L{DAVResource} to provide free-busy URL functionality.
+    """
+
+    def __init__(self, parent):
+        """
+        @param parent: the parent resource of this one.
+        """
+        assert parent is not None
+
+        CalDAVResource.__init__(self, principalCollections=parent.principalCollections())
+
+        self.parent = parent
+
+    def defaultAccessControlList(self):
+        return davxml.ACL(
+            # DAV:Read, CalDAV:schedule for all principals (does not include anonymous)
+            davxml.ACE(
+                davxml.Principal(davxml.Authenticated()),
+                davxml.Grant(
+                    davxml.Privilege(davxml.Read()),
+                    davxml.Privilege(caldavxml.Schedule()),
+                ),
+                davxml.Protected(),
+            ),
+        )
+
+    def resourceType(self):
+        return davxml.ResourceType.freebusyurl
+
+    def isCollection(self):
+        return False
+
+    def isCalendarCollection(self):
+        return False
+
+    def isPseudoCalendarCollection(self):
+        return False
+
+    def render(self, request):
+        output = """<html>
+<head>
+<title>Free-Busy URL Resource</title>
+</head>
+<body>
+<h1>Free-busy URL Resource.</h1>
+</body
+</html>"""
+
+        response = Response(200, {}, output)
+        response.headers.setHeader("content-type", MimeType("text", "html"))
+        return response
+
+    def http_GET(self, request):
+        """
+        The free-busy URL POST method.
+        """
+        return self._processFBURL(request)
+
+    def http_POST(self, request):
+        """
+        The free-busy URL POST method.
+        """
+        return self._processFBURL(request)
+
+    @deferredGenerator
+    def _processFBURL(self, request):
+        
+        #
+        # Check authentication and access controls
+        #
+        x = waitForDeferred(self.authorize(request, (davxml.Read(),)))
+        yield x
+        x.getResult()
+        
+        # Extract query parameters from the URL
+        args = ('start', 'end', 'duration', 'token', 'format', 'user',)
+        for arg in args:
+            setattr(self, arg, request.args.get(arg, [None])[0])
+        
+        # Some things we do not handle
+        if self.token or self.user:
+            raise HTTPError(ErrorResponse(responsecode.NOT_ACCEPTABLE, (calendarserver_namespace, "supported-query-parameter")))
+        
+        # Check format
+        if self.format:
+            self.format = self.format.split(";")[0]
+            if self.format not in ("text/calendar", "text/plain"):
+                raise HTTPError(ErrorResponse(responsecode.NOT_ACCEPTABLE, (calendarserver_namespace, "supported-format")))
+        else:
+            self.format = "text/calendar"
+            
+        # Start/end/duration must be valid iCalendar DATE-TIME UTC or DURATION values
+        try:
+            if self.start:
+                self.start = parse_datetime(self.start)
+                if self.start.tzinfo != utc:
+                    raise ValueError()
+            if self.end:
+                self.end = parse_datetime(self.end)
+                if self.end.tzinfo != utc:
+                    raise ValueError()
+            if self.duration:
+                self.duration = parse_duration(self.duration)
+        except ValueError:
+            raise HTTPError(ErrorResponse(responsecode.BAD_REQUEST, (calendarserver_namespace, "valid-query-parameters")))
+        
+        # Sanity check start/end/duration
+
+        # End and duration cannot both be present
+        if self.end and self.duration:
+            raise HTTPError(ErrorResponse(responsecode.NOT_ACCEPTABLE, (calendarserver_namespace, "valid-query-parameters")))
+        
+        # Duration must be positive
+        if self.duration and self.duration.days < 0:
+            raise HTTPError(ErrorResponse(responsecode.BAD_REQUEST, (calendarserver_namespace, "valid-query-parameters")))
+        
+        # Now fill in the missing pieces
+        if self.start is None:
+            now = datetime.datetime.now()
+            self.start = now.replace(hour=0, minute=0, second=0, tzinfo=utc)
+        if self.duration:
+            self.end = self.start + self.duration
+        if self.end is None:
+            self.end = self.start + datetime.timedelta(days=config.FreeBusyURL["Time Period"])
+            
+        # End > start
+        if self.end <= self.start:
+            raise HTTPError(ErrorResponse(responsecode.BAD_REQUEST, (calendarserver_namespace, "valid-query-parameters")))
+        
+        # TODO: We should probably verify that the actual time-range is within sensible bounds (e.g. not too far in the past or future and not too long)
+        
+        # Now lookup the principal details for the targetted user
+        principal = self.parent.principalForRecord()
+        
+        # Pick the first mailto cu address or the first other type
+        cuaddr = None
+        for item in principal.calendarUserAddresses():
+            if cuaddr is None:
+                cuaddr = item
+            if item.startswith("mailto"):
+                cuaddr = item
+                break
+
+        # Get inbox details
+        inboxURL = principal.scheduleInboxURL()
+        if inboxURL is None:
+            raise HTTPError(StatusResponse(responsecode.INTERNAL_SERVER_ERROR, "No schedule inbox URL for principal: %s" % (principal,)))
+        try:
+            inbox = waitForDeferred(request.locateResource(inboxURL))
+            yield inbox
+            inbox = inbox.getResult()
+        except:
+            log.err("No schedule inbox for principal: %s" % (principal,))
+            inbox = None
+        if inbox is None:
+            raise HTTPError(StatusResponse(responsecode.INTERNAL_SERVER_ERROR, "No schedule inbox for principal: %s" % (principal,)))
+            
+        scheduler = Scheduler(request, self)
+        scheduler.timerange = TimeRange(start="20000101T000000Z", end="20070102T000000Z")
+        scheduler.timerange.start = self.start
+        scheduler.timerange.end = self.end
+        
+        scheduler.organizer = Scheduler.LocalCalendarUser(cuaddr, principal, inbox, inboxURL)
+        
+        attendeeProp = Property("ATTENDEE", scheduler.organizer.cuaddr)
+
+        d = waitForDeferred(scheduler.generateAttendeeFreeBusyResponse(
+            scheduler.organizer,
+            None,
+            None,
+            attendeeProp,
+            False,
+        ))
+        yield d
+        fbresult = d.getResult()
+        
+        response = Response()
+        response.stream = MemoryStream(str(fbresult))
+        response.headers.setHeader("content-type", MimeType.fromString("%s; charset=utf-8" % (self.format,)))
+    
+        yield response

Modified: CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/schedule_common.py
===================================================================
--- CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/schedule_common.py	2007-09-10 23:40:49 UTC (rev 1861)
+++ CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/schedule_common.py	2007-09-11 03:20:43 UTC (rev 1862)
@@ -21,6 +21,7 @@
 """
 
 __all__ = [
+    "Scheduler",
     "CalDAVScheduler",
     "ServerToServerScheduler",
 ]
@@ -92,6 +93,8 @@
         self.recipients = None
         self.calendar = None
         self.organizer = None
+        self.timerange = None
+        self.excludeuid = None
     
     @deferredGenerator
     def doSchedulingViaPOST(self):
@@ -443,60 +446,17 @@
 
         remote = isinstance(self.organizer, Scheduler.RemoteCalendarUser)
 
-        # Find the current recipients calendar-free-busy-set
-        fbset = waitForDeferred(recipient.principal.calendarFreeBusyURIs(self.request))
-        yield fbset
-        fbset = fbset.getResult()
-
-        # First list is BUSY, second BUSY-TENTATIVE, third BUSY-UNAVAILABLE
-        fbinfo = ([], [], [])
-    
         try:
-            # Process the availability property from the Inbox.
-            has_prop = waitForDeferred(recipient.inbox.hasProperty((calendarserver_namespace, "calendar-availability"), self.request))
-            yield has_prop
-            has_prop = has_prop.getResult()
-            if has_prop:
-                availability = waitForDeferred(recipient.inbox.readProperty((calendarserver_namespace, "calendar-availability"), self.request))
-                yield availability
-                availability = availability.getResult()
-                availability = availability.calendar()
-                report_common.processAvailabilityFreeBusy(availability, fbinfo, self.timerange)
+            d = waitForDeferred(self.generateAttendeeFreeBusyResponse(
+                recipient,
+                organizerProp,
+                uid,
+                attendeeProp,
+                remote,
+            ))
+            yield d
+            fbresult = d.getResult()
 
-            # Check to see if the recipient is the same calendar user as the organizer.
-            # Needed for masked UID stuff.
-            if isinstance(self.organizer, Scheduler.LocalCalendarUser):
-                same_calendar_user = self.organizer.principal.principalURL() == recipient.principal.principalURL()
-            else:
-                same_calendar_user = False
-
-            # Now process free-busy set calendars
-            matchtotal = 0
-            for calURL in fbset:
-                cal = waitForDeferred(self.request.locateResource(calURL))
-                yield cal
-                cal = cal.getResult()
-                if cal is None or not cal.exists() or not isCalendarCollectionResource(cal):
-                    # We will ignore missing calendars. If the recipient has failed to
-                    # properly manage the free busy set that should not prevent us from working.
-                    continue
-             
-                matchtotal = waitForDeferred(report_common.generateFreeBusyInfo(
-                    self.request,
-                    cal,
-                    fbinfo,
-                    self.timerange,
-                    matchtotal,
-                    excludeuid=self.excludeuid,
-                    organizer=self.organizer.cuaddr,
-                    same_calendar_user=same_calendar_user,
-                    servertoserver=remote))
-                yield matchtotal
-                matchtotal = matchtotal.getResult()
-        
-            # Build VFREEBUSY iTIP reply for this recipient
-            fbresult = report_common.buildFreeBusyResult(fbinfo, self.timerange, organizer=organizerProp, attendee=attendeeProp, uid=uid)
-
             responses.add(recipient.cuaddr, responsecode.OK, reqstatus="2.0;Success", calendar=fbresult)
             
             yield True
@@ -507,6 +467,64 @@
             
             yield False
     
+    @deferredGenerator
+    def generateAttendeeFreeBusyResponse(self, recipient, organizerProp, uid, attendeeProp, remote):
+
+        # Find the current recipients calendar-free-busy-set
+        fbset = waitForDeferred(recipient.principal.calendarFreeBusyURIs(self.request))
+        yield fbset
+        fbset = fbset.getResult()
+
+        # First list is BUSY, second BUSY-TENTATIVE, third BUSY-UNAVAILABLE
+        fbinfo = ([], [], [])
+    
+        # Process the availability property from the Inbox.
+        has_prop = waitForDeferred(recipient.inbox.hasProperty((calendarserver_namespace, "calendar-availability"), self.request))
+        yield has_prop
+        has_prop = has_prop.getResult()
+        if has_prop:
+            availability = waitForDeferred(recipient.inbox.readProperty((calendarserver_namespace, "calendar-availability"), self.request))
+            yield availability
+            availability = availability.getResult()
+            availability = availability.calendar()
+            report_common.processAvailabilityFreeBusy(availability, fbinfo, self.timerange)
+
+        # Check to see if the recipient is the same calendar user as the organizer.
+        # Needed for masked UID stuff.
+        if isinstance(self.organizer, Scheduler.LocalCalendarUser):
+            same_calendar_user = self.organizer.principal.principalURL() == recipient.principal.principalURL()
+        else:
+            same_calendar_user = False
+
+        # Now process free-busy set calendars
+        matchtotal = 0
+        for calURL in fbset:
+            cal = waitForDeferred(self.request.locateResource(calURL))
+            yield cal
+            cal = cal.getResult()
+            if cal is None or not cal.exists() or not isCalendarCollectionResource(cal):
+                # We will ignore missing calendars. If the recipient has failed to
+                # properly manage the free busy set that should not prevent us from working.
+                continue
+         
+            matchtotal = waitForDeferred(report_common.generateFreeBusyInfo(
+                self.request,
+                cal,
+                fbinfo,
+                self.timerange,
+                matchtotal,
+                excludeuid=self.excludeuid,
+                organizer=self.organizer.cuaddr,
+                same_calendar_user=same_calendar_user,
+                servertoserver=remote))
+            yield matchtotal
+            matchtotal = matchtotal.getResult()
+    
+        # Build VFREEBUSY iTIP reply for this recipient
+        fbresult = report_common.buildFreeBusyResult(fbinfo, self.timerange, organizer=organizerProp, attendee=attendeeProp, uid=uid)
+
+        yield fbresult
+    
     def generateRemoteResponse(self):
         raise NotImplementedError
     

Modified: CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/static.py
===================================================================
--- CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/static.py	2007-09-10 23:40:49 UTC (rev 1861)
+++ CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/static.py	2007-09-11 03:20:43 UTC (rev 1862)
@@ -56,8 +56,8 @@
 from twistedcaldav import customxml
 from twistedcaldav.caldavxml import caldav_namespace
 from twistedcaldav.config import config
-from twistedcaldav.directory.directory import DirectoryService
 from twistedcaldav.extensions import DAVFile
+from twistedcaldav.freebusyurl import FreeBusyURLResource
 from twistedcaldav.ical import Component as iComponent
 from twistedcaldav.ical import Property as iProperty
 from twistedcaldav.index import Index, IndexSchedule
@@ -499,12 +499,17 @@
             NotificationsCollectionFileClass = NotificationsCollectionFile
         else:
             NotificationsCollectionFileClass = None
+        if config.FreeBusyURL["Enabled"]:
+            FreeBusyURLFileClass = FreeBusyURLFile
+        else:
+            FreeBusyURLFileClass = None
             
         cls = {
             "inbox"        : ScheduleInboxFile,
             "outbox"       : ScheduleOutboxFile,
             "dropbox"      : DropBoxHomeFileClass,
             "notifications": NotificationsCollectionFileClass,
+            "freebusy"     : FreeBusyURLFileClass,
         }.get(name, None)
 
         if cls is not None:
@@ -607,7 +612,7 @@
     Server-to-server scheduling inbox resource.
     """
     def __init__(self, path, parent):
-        CalDAVFile.__init__(self, path, parent)
+        CalDAVFile.__init__(self, path, principalCollections=parent.principalCollections())
         ScheduleServerToServerResource.__init__(self, parent)
         
         self.fp.open("w").close()
@@ -623,7 +628,7 @@
         if path == self.fp.path:
             return self
         else:
-            return CalDAVFile(path, principalCollections=self.principalCollections())
+            return responsecode.NOT_FOUND
 
     def http_PUT        (self, request): return responsecode.FORBIDDEN
     def http_COPY       (self, request): return responsecode.FORBIDDEN
@@ -637,6 +642,38 @@
             (caldav_namespace, "calendar-collection-location-ok")
         )
 
+class FreeBusyURLFile (AutoProvisioningFileMixIn, FreeBusyURLResource, CalDAVFile):
+    """
+    Free-busy URL resource.
+    """
+    def __init__(self, path, parent):
+        CalDAVFile.__init__(self, path, principalCollections=parent.principalCollections())
+        FreeBusyURLResource.__init__(self, parent)
+
+    def __repr__(self):
+        return "<%s (free-busy URL resource): %s>" % (self.__class__.__name__, self.fp.path)
+
+    def isCollection(self):
+        return False
+
+    def createSimilarFile(self, path):
+        if path == self.fp.path:
+            return self
+        else:
+            return responsecode.NOT_FOUND
+
+    def http_PUT        (self, request): return responsecode.FORBIDDEN
+    def http_COPY       (self, request): return responsecode.FORBIDDEN
+    def http_MOVE       (self, request): return responsecode.FORBIDDEN
+    def http_DELETE     (self, request): return responsecode.FORBIDDEN
+    def http_MKCOL      (self, request): return responsecode.FORBIDDEN
+
+    def http_MKCALENDAR(self, request):
+        return ErrorResponse(
+            responsecode.FORBIDDEN,
+            (caldav_namespace, "calendar-collection-location-ok")
+        )
+
     ##
     # ACL
     ##

-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20070910/30139fe5/attachment.html


More information about the calendarserver-changes mailing list