[CalendarServer-changes] [5308] CalendarServer/branches/users/cdaboo/shared-calendars-5187/ twistedcaldav

source_changes at macosforge.org source_changes at macosforge.org
Mon Mar 15 12:59:17 PDT 2010


Revision: 5308
          http://trac.macosforge.org/projects/calendarserver/changeset/5308
Author:   cdaboo at apple.com
Date:     2010-03-15 12:59:17 -0700 (Mon, 15 Mar 2010)
Log Message:
-----------
Checkpoint for shared calendar resource support.

Modified Paths:
--------------
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/directory/calendaruserproxy.py
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/directorybackedaddressbook.py
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/dropbox.py
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/extensions.py
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/freebusyurl.py
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/mail.py
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/method/delete_common.py
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/notifications.py
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/resource.py
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/schedule.py
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/sharing.py
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/static.py
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/test/test_sharing.py
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/timezoneservice.py

Added Paths:
-----------
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/sharedcalendar.py

Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/directory/calendaruserproxy.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/directory/calendaruserproxy.py	2010-03-15 18:05:21 UTC (rev 5307)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/directory/calendaruserproxy.py	2010-03-15 19:59:17 UTC (rev 5308)
@@ -128,13 +128,13 @@
         """
         return ProxyDBService
 
-    def resourceType(self):
+    def resourceType(self, request):
         if self.proxyType == "calendar-proxy-read":
-            return davxml.ResourceType.calendarproxyread
+            return succeed(davxml.ResourceType.calendarproxyread)
         elif self.proxyType == "calendar-proxy-write":
-            return davxml.ResourceType.calendarproxywrite
+            return succeed(davxml.ResourceType.calendarproxywrite)
         else:
-            return super(CalendarUserProxyPrincipalResource, self).resourceType()
+            return super(CalendarUserProxyPrincipalResource, self).resourceType(request)
 
     def isProxyType(self, read_write):
         if (

Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/directorybackedaddressbook.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/directorybackedaddressbook.py	2010-03-15 18:05:21 UTC (rev 5307)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/directorybackedaddressbook.py	2010-03-15 19:59:17 UTC (rev 5308)
@@ -94,8 +94,8 @@
            ),
         )
 
-    def resourceType(self):
-        return davxml.ResourceType.addressbook #@UndefinedVariable
+    def resourceType(self, request):
+        return succeed(davxml.ResourceType.addressbook)
 
     def isDirectoryBackedAddressBookCollection(self):
         return True

Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/dropbox.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/dropbox.py	2010-03-15 18:05:21 UTC (rev 5307)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/dropbox.py	2010-03-15 19:59:17 UTC (rev 5308)
@@ -32,14 +32,16 @@
 
 from twistedcaldav.customxml import calendarserver_namespace
 
+from twisted.internet.defer import succeed
+
 log = Logger()
 
 class DropBoxHomeResource (DAVResource):
     """
     Drop box collection resource.
     """
-    def resourceType(self):
-        return davxml.ResourceType.dropboxhome
+    def resourceType(self, request):
+        return succeed(davxml.ResourceType.dropboxhome)
 
     def isCollection(self):
         return True
@@ -51,8 +53,8 @@
     """
     Drop box resource.
     """
-    def resourceType(self):
-        return davxml.ResourceType.dropbox
+    def resourceType(self, request):
+        return succeed(davxml.ResourceType.dropbox)
 
     def isCollection(self):
         return True

Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/extensions.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/extensions.py	2010-03-15 18:05:21 UTC (rev 5307)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/extensions.py	2010-03-15 19:59:17 UTC (rev 5308)
@@ -676,7 +676,8 @@
 
         if namespace == dav_namespace:
             if name == "resourcetype":
-                returnValue(self.resourceType())
+                rtype = (yield self.resourceType(request))
+                returnValue(rtype)
 
         elif namespace == calendarserver_namespace:
             if name == "expanded-group-member-set":
@@ -717,14 +718,14 @@
     def expandedGroupMemberships(self):
         return succeed(())
 
-    def resourceType(self):
+    def resourceType(self, request):
         # Allow live property to be overridden by dead property
         if self.deadProperties().contains((dav_namespace, "resourcetype")):
-            return self.deadProperties().get((dav_namespace, "resourcetype"))
+            return succeed(self.deadProperties().get((dav_namespace, "resourcetype")))
         if self.isCollection():
-            return davxml.ResourceType(davxml.Collection(), davxml.Principal())
+            return succeed(davxml.ResourceType(davxml.Collection(), davxml.Principal()))
         else:
-            return davxml.ResourceType(davxml.Principal())
+            return succeed(davxml.ResourceType(davxml.Principal()))
 
 
 class DAVFile (SudoSACLMixin, SuperDAVFile, LoggingMixIn):
@@ -738,17 +739,17 @@
             qname = property.qname()
 
         if qname == (dav_namespace, "resourcetype"):
-            return succeed(self.resourceType())
+            return self.resourceType(request)
 
         return super(DAVFile, self).readProperty(property, request)
 
-    def resourceType(self):
+    def resourceType(self, request):
         # Allow live property to be overridden by dead property
         if self.deadProperties().contains((dav_namespace, "resourcetype")):
-            return self.deadProperties().get((dav_namespace, "resourcetype"))
+            return succeed(self.deadProperties().get((dav_namespace, "resourcetype")))
         if self.isCollection():
-            return davxml.ResourceType.collection
-        return davxml.ResourceType.empty
+            return succeed(davxml.ResourceType.collection)
+        return succeed(davxml.ResourceType.empty)
 
     def render(self, request):
         if not self.fp.exists():
@@ -1065,38 +1066,44 @@
         self.propertyStore = propertyStore
         self.resource = propertyStore.resource
 
-    def get(self, qname):
+    def get(self, qname, uid=None):
         #self.log_debug("Get: %r, %r" % (self.resource.fp.path, qname))
 
         cache = self._cache()
+        
+        cachedQname = qname + (uid,)
 
-        if qname in cache:
-            property = cache.get(qname, None)
+        if cachedQname in cache:
+            property = cache.get(cachedQname, None)
             if property is None:
                 self.log_debug("Cache miss: %r, %r, %r" % (self, self.resource.fp.path, qname))
                 try:
-                    property = self.propertyStore.get(qname)
+                    property = self.propertyStore.get(qname, uid)
                 except HTTPError:
-                    del cache[qname]
+                    del cache[cachedQname]
                     raise PropertyNotFoundError(qname)
-                cache[qname] = property
+                cache[cachedQname] = property
 
             return property
         else:
             raise PropertyNotFoundError(qname)
 
-    def set(self, property):
+    def set(self, property, uid=None):
         #self.log_debug("Set: %r, %r" % (self.resource.fp.path, property))
 
         cache = self._cache()
 
-        cache[property.qname()] = None
-        self.propertyStore.set(property)
-        cache[property.qname()] = property
+        cachedQname = property.qname() + (uid,)
 
-    def contains(self, qname):
+        cache[cachedQname] = None
+        self.propertyStore.set(property, uid)
+        cache[cachedQname] = property
+
+    def contains(self, qname, uid=None):
         #self.log_debug("Contains: %r, %r" % (self.resource.fp.path, qname))
 
+        cachedQname = qname + (uid,)
+
         try:
             cache = self._cache()
         except HTTPError, e:
@@ -1105,30 +1112,40 @@
             else:
                 raise
 
-        if qname in cache:
+        if cachedQname in cache:
             #self.log_debug("Contains cache hit: %r, %r, %r" % (self, self.resource.fp.path, qname))
             return True
         else:
             return False
 
-    def delete(self, qname):
+    def delete(self, qname, uid=None):
         #self.log_debug("Delete: %r, %r" % (self.resource.fp.path, qname))
 
-        if self._data is not None and qname in self._data:
-            del self._data[qname]
+        cachedQname = qname + (uid,)
 
-        self.propertyStore.delete(qname)
+        if self._data is not None and cachedQname in self._data:
+            del self._data[cachedQname]
 
-    def list(self):
+        self.propertyStore.delete(qname, uid)
+
+    def list(self, uid=None, filterByUID=True):
         #self.log_debug("List: %r" % (self.resource.fp.path,))
-        return self._cache().iterkeys()
+        keys = self._cache().iterkeys()
+        if filterByUID:
+            return [ 
+                (namespace, name)
+                for namespace, name, propuid in keys
+                if propuid == uid
+            ]
+        else:
+            return keys
 
     def _cache(self):
         if not hasattr(self, "_data"):
             #self.log_debug("Cache init: %r" % (self.resource.fp.path,))
             self._data = dict(
                 (name, None)
-                for name in self.propertyStore.list()
+                for name in self.propertyStore.list(filterByUID=False)
             )
         return self._data
 

Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/freebusyurl.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/freebusyurl.py	2010-03-15 18:05:21 UTC (rev 5307)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/freebusyurl.py	2010-03-15 19:59:17 UTC (rev 5308)
@@ -26,7 +26,7 @@
 
 from vobject.icalendar import utc
 
-from twisted.internet.defer import inlineCallbacks, returnValue
+from twisted.internet.defer import inlineCallbacks, returnValue, succeed
 
 from twext.python.log import Logger
 from twext.web2 import responsecode
@@ -99,8 +99,8 @@
             )
         return davxml.ACL(*aces)
 
-    def resourceType(self):
-        return davxml.ResourceType.freebusyurl
+    def resourceType(self, request):
+        return succeed(davxml.ResourceType.freebusyurl)
 
     def isCollection(self):
         return False

Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/mail.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/mail.py	2010-03-15 18:05:21 UTC (rev 5307)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/mail.py	2010-03-15 19:59:17 UTC (rev 5308)
@@ -218,8 +218,8 @@
 
         return succeed(self.iMIPACL)
 
-    def resourceType(self):
-        return davxml.ResourceType.ischeduleinbox
+    def resourceType(self, request):
+        return succeed(davxml.ResourceType.ischeduleinbox)
 
     def isCollection(self):
         return False

Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/method/delete_common.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/method/delete_common.py	2010-03-15 18:05:21 UTC (rev 5307)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/method/delete_common.py	2010-03-15 19:59:17 UTC (rev 5308)
@@ -215,6 +215,12 @@
 
         # Now do normal delete
 
+        # Check virtual share first
+        isVirtual = yield delresource.isVirtualShare(self.request)
+        if isVirtual:
+            yield delresource.removeVirtualShare(self.request)
+            returnValue(responsecode.NO_CONTENT)
+
         # Handle sharing
         wasShared = (yield delresource.isShared(self.request))
         if wasShared:

Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/notifications.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/notifications.py	2010-03-15 18:05:21 UTC (rev 5307)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/notifications.py	2010-03-15 19:59:17 UTC (rev 5308)
@@ -73,8 +73,8 @@
     def isCollection(self):
         return True
 
-    def resourceType(self):
-        return davxml.ResourceType.notification
+    def resourceType(self, request):
+        return succeed(davxml.ResourceType.notification)
 
     @inlineCallbacks
     def addNotification(self, request, uid, xmltype, xmldata):

Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/resource.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/resource.py	2010-03-15 18:05:21 UTC (rev 5307)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/resource.py	2010-03-15 19:59:17 UTC (rev 5308)
@@ -266,6 +266,19 @@
         """
         if qname == (u'DAV:', u'displayname'): return False # XXX HACK
         if qname in self.liveProperties:
+            if qname in (
+                davxml.DisplayName.qname(),
+                customxml.Invite.qname(),
+            ):
+                return False
+            else:
+                return True
+        elif qname in (
+            customxml.GETCTag.qname(),
+            caldavxml.MaxResourceSize.qname(),
+            caldavxml.MaxAttendeesPerInstance.qname(),
+            caldavxml.ScheduleCalendarTransp.qname(),
+        ):
             return True
         else:
             return False
@@ -291,8 +304,7 @@
         elif (not self.isGlobalProperty(qname)) and isvirt:
             ownerPrincipal = (yield self.resourceOwnerPrincipal(request))
             p = self.deadProperties().get(qname, uid=ownerPrincipal.principalUID())
-            if p is not None:
-                returnValue(p)
+            returnValue(p)
 
         res = (yield self._readGlobalProperty(qname, property, request))
         returnValue(res)
@@ -469,8 +481,13 @@
     # FIXME: Perhaps this is better done in authorize() instead.
     @inlineCallbacks
     def accessControlList(self, request, *args, **kwargs):
-        acls = (yield super(CalDAVResource, self).accessControlList(request, *args, **kwargs))
 
+        isvirt = (yield self.isVirtualShare(request))
+        if isvirt:
+            acls = self.shareeAccessControlList()
+        else:
+            acls = (yield super(CalDAVResource, self).accessControlList(request, *args, **kwargs))
+
         # Look for private events access classification
         if self.hasDeadProperty(TwistedCalendarAccessProperty):
             access = self.readDeadProperty(TwistedCalendarAccessProperty)
@@ -515,31 +532,39 @@
  
         returnValue(acls)
 
+    @inlineCallbacks
     def owner(self, request):
         """
         Return the DAV:owner property value (MUST be a DAV:href or None).
         """
         
-        def _gotParent(parent):
-            if parent and isinstance(parent, CalDAVResource):
-                return parent.owner(request)
+        isVirt = (yield self.isVirtualShare(request))
+        if isVirt:
+            parent = (yield self.locateParent(request, self._share.hosturl))
+        else:
+            parent = (yield self.locateParent(request, request.urlForResource(self)))
+        if parent and isinstance(parent, CalDAVResource):
+            result = (yield parent.owner(request))
+            returnValue(result)
+        else:
+            returnValue(None)
 
-        d = self.locateParent(request, request.urlForResource(self))
-        d.addCallback(_gotParent)
-        return d
-
+    @inlineCallbacks
     def ownerPrincipal(self, request):
         """
         Return the DAV:owner property value (MUST be a DAV:href or None).
         """
-        def _gotParent(parent):
-            if parent and isinstance(parent, CalDAVResource):
-                return parent.ownerPrincipal(request)
+        isVirt = (yield self.isVirtualShare(request))
+        if isVirt:
+            parent = (yield self.locateParent(request, self._share.hosturl))
+        else:
+            parent = (yield self.locateParent(request, request.urlForResource(self)))
+        if parent and isinstance(parent, CalDAVResource):
+            result = (yield parent.ownerPrincipal(request))
+            returnValue(result)
+        else:
+            returnValue(None)
 
-        d = self.locateParent(request, request.urlForResource(self))
-        d.addCallback(_gotParent)
-        return d
-
     def resourceOwnerPrincipal(self, request):
         """
         This is the owner of the resource based on the URI used to access it. For a shared
@@ -1282,8 +1307,8 @@
 
         self.parent = parent
 
-    def resourceType(self):
-        return davxml.ResourceType.searchaddressbook #@UndefinedVariable
+    def resourceType(self, request):
+        return succeed(davxml.ResourceType.searchaddressbook)
 
     def renderHTTP(self, request):
         return RedirectResponse(request.unparseURL(path="/directory/"))
@@ -1303,8 +1328,8 @@
 
         self.parent = parent
 
-    def resourceType(self):
-        return davxml.ResourceType.searchalladdressbook #@UndefinedVariable
+    def resourceType(self, request):
+        return succeed(davxml.ResourceType.searchalladdressbook)
 
     def renderHTTP(self, request):
         

Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/schedule.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/schedule.py	2010-03-15 18:05:21 UTC (rev 5307)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/schedule.py	2010-03-15 19:59:17 UTC (rev 5308)
@@ -26,7 +26,7 @@
 
 from twext.web2.dav.http import ErrorResponse
 
-from twisted.internet.defer import inlineCallbacks, returnValue
+from twisted.internet.defer import inlineCallbacks, returnValue, succeed
 from twext.web2 import responsecode
 from twext.web2.dav import davxml
 from twext.web2.dav.util import joinURL, normalizeURL
@@ -87,8 +87,8 @@
         (caldav_namespace, "schedule-default-calendar-URL"),
     )
 
-    def resourceType(self):
-        return davxml.ResourceType.scheduleInbox
+    def resourceType(self, request):
+        return succeed(davxml.ResourceType.scheduleInbox)
 
     def defaultAccessControlList(self):
         
@@ -253,8 +253,8 @@
         else:
             return super(ScheduleOutboxResource, self).defaultAccessControlList()
 
-    def resourceType(self):
-        return davxml.ResourceType.scheduleOutbox
+    def resourceType(self, request):
+        return succeed(davxml.ResourceType.scheduleOutbox)
 
     @inlineCallbacks
     def http_POST(self, request):
@@ -310,8 +310,8 @@
             ),
         )
 
-    def resourceType(self):
-        return davxml.ResourceType.ischeduleinbox
+    def resourceType(self, request):
+        return succeed(davxml.ResourceType.ischeduleinbox)
 
     def isCollection(self):
         return False

Added: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/sharedcalendar.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/sharedcalendar.py	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/sharedcalendar.py	2010-03-15 19:59:17 UTC (rev 5308)
@@ -0,0 +1,81 @@
+##
+# Copyright (c) 2010 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.
+##
+
+from twext.python.log import LoggingMixIn
+
+from twisted.internet.defer import inlineCallbacks, returnValue
+
+from twistedcaldav.extensions import DAVResource
+from twistedcaldav.resource import CalDAVComplianceMixIn
+from twistedcaldav.sharing import SharedCollectionMixin
+
+__all__ = [
+    "SharedCalendarResource",
+]
+
+"""
+Sharing behavior
+"""
+
+class SharedCalendarResource(CalDAVComplianceMixIn, SharedCollectionMixin, DAVResource, LoggingMixIn):
+    
+    def __init__(self, parent, share):
+        self.parent = parent
+        self.share = share
+        super(SharedCalendarResource, self).__init__(self.parent.principalCollections())
+
+    @inlineCallbacks
+    def hostedResource(self, request):
+        
+        if not hasattr(self, "_hostedResource"):
+            self._hostedResource = (yield request.locateResource(self.share.hosturl))
+            ownerPrincipal = (yield self.parent.ownerPrincipal(request))
+            self._hostedResource.setVirtualShare(ownerPrincipal, self.share)
+        returnValue(self._hostedResource)
+
+    def isCollection(self):
+        return True
+
+    @inlineCallbacks
+    def locateChild(self, request, segments):
+        hosted = (yield self.hostedResource(request))
+        result = (yield hosted.locateChild(request, segments))
+        returnValue(result)
+
+    def renderHTTP(self, request):
+        return self.hostedResource(request)
+
+    @inlineCallbacks
+    def getChild(self, name):
+        return self._hostedResource.getChild(name)
+
+    @inlineCallbacks
+    def hasProperty(self, property, request):
+        hosted = (yield self.hostedResource(request))
+        result = (yield hosted.hasProperty(property, request))
+        returnValue(result)
+
+    @inlineCallbacks
+    def readProperty(self, property, request):
+        hosted = (yield self.hostedResource(request))
+        result = (yield hosted.readProperty(property, request))
+        returnValue(result)
+
+    @inlineCallbacks
+    def writeProperty(self, property, request):
+        hosted = (yield self.hostedResource(request))
+        result = (yield hosted.writeProperty(property, request))
+        returnValue(result)

Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/sharing.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/sharing.py	2010-03-15 18:05:21 UTC (rev 5307)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/sharing.py	2010-03-15 19:59:17 UTC (rev 5308)
@@ -13,29 +13,35 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 ##
+from twext.web2.dav.resource import TwistedACLInheritable
 
+__all__ = [
+    "SharedCollectionMixin",
+]
+
 from twext.python.log import LoggingMixIn
 from twext.web2 import responsecode
+from twext.web2.dav import davxml
 from twext.web2.dav.http import ErrorResponse, MultiStatusResponse
-from twext.web2.dav.util import allDataFromStream
+from twext.web2.dav.util import allDataFromStream, joinURL
 from twext.web2.http import HTTPError, Response, StatusResponse
+
 from twisted.internet.defer import succeed, inlineCallbacks, DeferredList,\
     returnValue, fail
+
+from twistedcaldav import customxml, caldavxml
 from twistedcaldav.config import config
+from twistedcaldav.extensions import updateCacheTokenOnCallback
 from twistedcaldav.sql import AbstractSQLDatabase, db_prefix
+
 from uuid import uuid4
+
 from vobject.icalendar import dateTimeToString, utc
+
 import datetime
 import os
 import types
 
-__all__ = [
-    "SharedCollectionMixin",
-]
-
-from twistedcaldav import customxml
-from twext.web2.dav import davxml
-
 """
 Sharing behavior
 """
@@ -61,6 +67,7 @@
                 return None
         return self.isShared(request).addCallback(sharedOK)
 
+    @inlineCallbacks
     def upgradeToShare(self, request):
         """ Upgrade this collection to a shared state """
         
@@ -69,21 +76,21 @@
             raise HTTPError(StatusResponse(responsecode.FORBIDDEN, "Cannot upgrade to shared calendar"))
 
         # Change resourcetype
-        rtype = self.resourceType()
+        rtype = (yield self.resourceType(request))
         rtype = davxml.ResourceType(*(rtype.children + (customxml.SharedOwner(),)))
         self.writeDeadProperty(rtype)
         
         # Create invites database
         self.invitesDB().create()
 
-        return succeed(True)
+        returnValue(True)
     
     @inlineCallbacks
     def downgradeFromShare(self, request):
         
         # Change resource type (note this might be called after deleting a resource
         # so we have to cope with that)
-        rtype = self.resourceType()
+        rtype = (yield self.resourceType(request))
         rtype = davxml.ResourceType(*([child for child in rtype.children if child != customxml.SharedOwner()]))
         self.writeDeadProperty(rtype)
         
@@ -133,10 +140,36 @@
         """ Return True if this is an owner shared calendar collection """
         return succeed(self.isSpecialCollection(customxml.SharedOwner))
 
+    def setVirtualShare(self, shareePrincipal, share):
+        self._isVirtualShare = True
+        self._shareePrincipal = shareePrincipal
+        self._share = share
+
     def isVirtualShare(self, request):
         """ Return True if this is a shared calendar collection """
-        return succeed(self.isSpecialCollection(customxml.Shared))
+        return succeed(hasattr(self, "_isVirtualShare"))
 
+    def removeVirtualShare(self, request):
+        """ Return True if this is a shared calendar collection """
+        
+        # Remove from sharee's calendar home
+        shareeHome = self._shareePrincipal.calendarHome()
+        return shareeHome.removeShare(request, self._share)
+
+    @inlineCallbacks
+    def resourceType(self, request):
+        
+        rtype = (yield super(SharedCollectionMixin, self).resourceType(request))
+        isVirt = (yield self.isVirtualShare(request))
+        if isVirt:
+            rtype = davxml.ResourceType(
+                *(
+                    tuple([child for child in rtype.children if child.qname() != customxml.SharedOwner.qname()]) +
+                    (customxml.Shared(),)
+                )
+            )
+        returnValue(rtype)
+        
     def sharedResourceType(self):
         """
         Return the DAV:resourcetype stripped of any shared elements.
@@ -149,6 +182,73 @@
         else:
             return ""
 
+    def shareeAccessControlList(self):
+
+        assert self._isVirtualShare, "Only call this fort a virtual share"
+
+        # Get the invite for this sharee
+        invite = self.invitesDB().recordForInviteUID(self._share.inviteuid)
+        if invite is None:
+            return davxml.ACL()
+        
+        userprivs = [
+        ]
+        if invite.access in ("read-only", "read-write", "read-write-schedule",):
+            userprivs.append(davxml.Privilege(davxml.Read()))
+            userprivs.append(davxml.Privilege(davxml.ReadACL()))
+            userprivs.append(davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()))
+        if invite.access in ("read-write", "read-write-schedule",):
+            userprivs.append(davxml.Privilege(davxml.Write()))
+        proxyprivs = list(userprivs)
+        proxyprivs.remove(davxml.Privilege(davxml.ReadACL()))
+
+        aces = (
+            # Inheritable DAV:all access for the resource's associated principal.
+            davxml.ACE(
+                davxml.Principal(davxml.HRef(self._shareePrincipal.principalURL())),
+                davxml.Grant(*userprivs),
+                davxml.Protected(),
+                TwistedACLInheritable(),
+            ),
+            # Inheritable CALDAV:read-free-busy access for authenticated users.
+            davxml.ACE(
+                davxml.Principal(davxml.Authenticated()),
+                davxml.Grant(davxml.Privilege(caldavxml.ReadFreeBusy())),
+                TwistedACLInheritable(),
+            ),
+        )
+
+        # Give read access to config.ReadPrincipals
+        aces += config.ReadACEs
+
+        # Give all access to config.AdminPrincipals
+        aces += config.AdminACEs
+        
+        if config.EnableProxyPrincipals:
+            aces += (
+                # DAV:read/DAV:read-current-user-privilege-set access for this principal's calendar-proxy-read users.
+                davxml.ACE(
+                    davxml.Principal(davxml.HRef(joinURL(self._shareePrincipal.principalURL(), "calendar-proxy-read/"))),
+                    davxml.Grant(
+                        davxml.Privilege(davxml.Read()),
+                        davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()),
+                    ),
+                    davxml.Protected(),
+                    TwistedACLInheritable(),
+                ),
+                # DAV:read/DAV:read-current-user-privilege-set/DAV:write access for this principal's calendar-proxy-write users.
+                davxml.ACE(
+                    davxml.Principal(davxml.HRef(joinURL(self._shareePrincipal.principalURL(), "calendar-proxy-write/"))),
+                    davxml.Grant(
+                        davxml.Privilege(*proxyprivs),
+                    ),
+                    davxml.Protected(),
+                    TwistedACLInheritable(),
+                ),
+            )
+
+        return davxml.ACL(*aces)
+
     def validUserIDForShare(self, userid):
         """
         Test the user id to see if it is a valid identifier for sharing and return a "normalized"
@@ -184,10 +284,6 @@
                 record.state = "INVALID"
                 self.invitesDB().addOrUpdateRecord(record)
                 
-    def removeVirtualShare(self, request):
-        """ As user of a shared calendar, unlink this calendar collection """
-        return succeed(False) 
-
     def getInviteUsers(self, request):
         return succeed(True)
 
@@ -527,25 +623,203 @@
         ("text", "xml") : xmlPOSTAuth,
     }
 
+inviteAccessMapToXML = {
+    "read-only"           : customxml.ReadAccess,
+    "read-write"          : customxml.ReadWriteAccess,
+    "read-write-schedule" : customxml.ReadWriteScheduleAccess,
+}
+inviteAccessMapFromXML = dict([(v,k) for k,v in inviteAccessMapToXML.iteritems()])
+
+inviteStatusMapToXML = {
+    "NEEDS-ACTION" : customxml.InviteStatusNoResponse,
+    "ACCEPTED"     : customxml.InviteStatusAccepted,
+    "DECLINED"     : customxml.InviteStatusDeclined,
+    "DELETED"      : customxml.InviteStatusDeleted,
+    "INVALID"      : customxml.InviteStatusInvalid,
+}
+inviteStatusMapFromXML = dict([(v,k) for k,v in inviteStatusMapToXML.iteritems()])
+
+class Invite(object):
+    
+    def __init__(self, inviteuid, userid, access, state, summary):
+        self.inviteuid = inviteuid
+        self.userid = userid
+        self.access = access
+        self.state = state
+        self.summary = summary
+        
+    def makePropertyElement(self):
+        
+        return customxml.InviteUser(
+            customxml.UID.fromString(self.inviteuid),
+            davxml.HRef.fromString(self.userid),
+            customxml.InviteAccess(inviteAccessMapToXML[self.access]()),
+            inviteStatusMapToXML[self.state](),
+        )
+
+class InvitesDatabase(AbstractSQLDatabase, LoggingMixIn):
+    
+    db_basename = db_prefix + "invites"
+    schema_version = "1"
+    db_type = "invites"
+
+    def __init__(self, resource):
+        """
+        @param resource: the L{twistedcaldav.static.CalDAVFile} resource for
+            the shared collection. C{resource} must be a calendar/addressbook collection.)
+        """
+        self.resource = resource
+        db_filename = os.path.join(self.resource.fp.path, InvitesDatabase.db_basename)
+        super(InvitesDatabase, self).__init__(db_filename, True, autocommit=True)
+
+    def create(self):
+        """
+        Create the index and initialize it.
+        """
+        self._db()
+
+    def allRecords(self):
+        
+        records = self._db_execute("select * from INVITE order by USERID")
+        return [self._makeRecord(row) for row in (records if records is not None else ())]
+    
+    def recordForUserID(self, userid):
+        
+        row = self._db_execute("select * from INVITE where USERID = :1", userid)
+        return self._makeRecord(row[0]) if row else None
+    
+    def recordForInviteUID(self, inviteUID):
+
+        row = self._db_execute("select * from INVITE where INVITEUID = :1", inviteUID)
+        return self._makeRecord(row[0]) if row else None
+    
+    def addOrUpdateRecord(self, record):
+
+        self._db_execute("""insert or replace into INVITE (INVITEUID, USERID, ACCESS, STATE, SUMMARY)
+            values (:1, :2, :3, :4, :5)
+            """, record.inviteuid, record.userid, record.access, record.state, record.summary,
+        )
+    
+    def removeRecordForUserID(self, userid):
+
+        self._db_execute("delete from INVITE where USERID = :1", userid)
+    
+    def removeRecordForInviteUID(self, inviteUID):
+
+        self._db_execute("delete from INVITE where INVITEUID = :1", inviteUID)
+    
+    def remove(self):
+        
+        self._db_close()
+        os.remove(self.dbpath)
+
+    def _db_version(self):
+        """
+        @return: the schema version assigned to this index.
+        """
+        return InvitesDatabase.schema_version
+
+    def _db_type(self):
+        """
+        @return: the collection type assigned to this index.
+        """
+        return InvitesDatabase.db_type
+
+    def _db_init_data_tables(self, q):
+        """
+        Initialise the underlying database tables.
+        @param q:           a database cursor to use.
+        """
+        #
+        # INVITE table is the primary table
+        #   INVITEUID: UID for this invite
+        #   NAME: identifier of invitee
+        #   ACCESS: Access mode for share
+        #   STATE: Invite response status
+        #   SUMMARY: Invite summary
+        #
+        q.execute(
+            """
+            create table INVITE (
+                INVITEUID      text unique,
+                USERID         text unique,
+                ACCESS         text,
+                STATE          text,
+                SUMMARY        text
+            )
+            """
+        )
+
+        q.execute(
+            """
+            create index USERID on INVITE (USERID)
+            """
+        )
+        q.execute(
+            """
+            create index INVITEUID on INVITE (INVITEUID)
+            """
+        )
+
+    def _db_upgrade_data_tables(self, q, old_version):
+        """
+        Upgrade the data from an older version of the DB.
+        """
+
+        # Nothing to do as we have not changed the schema
+        pass
+
+    def _makeRecord(self, row):
+        
+        return Invite(*[str(item) if type(item) == types.UnicodeType else item for item in row])
+
 class SharedHomeMixin(object):
     """
     A mix-in for calendar/addressbook homes that defines the operations for manipulating a sharee's
     set of shared calendfars.
     """
     
-    def acceptShare(self, request, hostUrl, replytoUID, displayname=None):
-        return self._changeShare(request, "ACCEPTED", hostUrl, replytoUID, displayname)
+    def sharesDB(self):
+        
+        if not hasattr(self, "_sharesDB"):
+            self._sharesDB = SharedCalendarsDatabase(self)
+        return self._sharesDB
 
+    def provisionShares(self):
+        
+        if not hasattr(self, "_provisionedShares"):
+            from twistedcaldav.sharedcalendar import SharedCalendarResource
+            for share in self.sharesDB().allRecords():
+                child = SharedCalendarResource(self, share)
+                self.putChild(share.localname, child)
+            self._provisionedShares = True
+
+    @updateCacheTokenOnCallback
+    def acceptShare(self, request, hostUrl, inviteUID, displayname=None):
+        
+        # Add or update in DB
+        oldShare = self.sharesDB().recordForInviteUID(inviteUID)
+        if not oldShare:
+            share = SharedCalendarRecord(inviteUID, hostUrl, str(uuid4()), displayname)
+            self.sharesDB().addOrUpdateRecord(share)
+
+        return self._changeShare(request, "ACCEPTED", hostUrl, inviteUID, displayname)
+
     def wouldAcceptShare(self, hostUrl, request):
         return succeed(True)
 
-    def removeShare(self, request, hostUrl, resourceName):
+    def removeShare(self, request, share):
         """ Remove a shared calendar named in resourceName """
-        return succeed(True)
+        return self.declineShare(request, share.hosturl, share.inviteuid)
 
-    def declineShare(self, request, hostUrl, replytoUID):
-        return self._changeShare(request, "DECLINED", hostUrl, replytoUID)
+    @updateCacheTokenOnCallback
+    def declineShare(self, request, hostUrl, inviteUID):
 
+        # Remove it if its in the DB
+        self.sharesDB().removeRecordForInviteUID(inviteUID)
+
+        return self._changeShare(request, "DECLINED", hostUrl, inviteUID)
+
     @inlineCallbacks
     def _changeShare(self, request, state, hostUrl, replytoUID, displayname=None):
         """ Accept an invite to a shared calendar """
@@ -598,6 +872,7 @@
         # Add to collections
         yield notifications.addNotification(request, notificationUID, xmltype, xmldata)
 
+    @updateCacheTokenOnCallback
     def xmlPOSTNoAuth(self, encoding, request):
         def _handleResponse(result):
             response = Response(code=responsecode.OK)
@@ -657,54 +932,28 @@
 
         return allDataFromStream(request.stream).addCallback(_getData)
 
-inviteAccessMapToXML = {
-    "read-only"           : customxml.ReadAccess,
-    "read-write"          : customxml.ReadWriteAccess,
-    "read-write-schedule" : customxml.ReadWriteScheduleAccess,
-}
-inviteAccessMapFromXML = dict([(v,k) for k,v in inviteAccessMapToXML.iteritems()])
-
-inviteStatusMapToXML = {
-    "NEEDS-ACTION" : customxml.InviteStatusNoResponse,
-    "ACCEPTED"     : customxml.InviteStatusAccepted,
-    "DECLINED"     : customxml.InviteStatusDeclined,
-    "DELETED"      : customxml.InviteStatusDeleted,
-    "INVALID"      : customxml.InviteStatusInvalid,
-}
-inviteStatusMapFromXML = dict([(v,k) for k,v in inviteStatusMapToXML.iteritems()])
-
-class Invite(object):
+class SharedCalendarRecord(object):
     
-    def __init__(self, inviteuid, userid, access, state, summary):
+    def __init__(self, inviteuid, hosturl, localname, summary):
         self.inviteuid = inviteuid
-        self.userid = userid
-        self.access = access
-        self.state = state
+        self.hosturl = hosturl
+        self.localname = localname
         self.summary = summary
-        
-    def makePropertyElement(self):
-        
-        return customxml.InviteUser(
-            customxml.UID.fromString(self.inviteuid),
-            davxml.HRef.fromString(self.userid),
-            customxml.InviteAccess(inviteAccessMapToXML[self.access]()),
-            inviteStatusMapToXML[self.state](),
-        )
 
-class InvitesDatabase(AbstractSQLDatabase, LoggingMixIn):
+class SharedCalendarsDatabase(AbstractSQLDatabase, LoggingMixIn):
     
-    db_basename = db_prefix + "invites"
+    db_basename = db_prefix + "shares"
     schema_version = "1"
-    db_type = "invites"
+    db_type = "shares"
 
     def __init__(self, resource):
         """
         @param resource: the L{twistedcaldav.static.CalDAVFile} resource for
-            the shared collection. C{resource} must be a calendar/addressbook collection.)
+            the shared collection. C{resource} must be a calendar/addressbook home collection.)
         """
         self.resource = resource
-        db_filename = os.path.join(self.resource.fp.path, InvitesDatabase.db_basename)
-        super(InvitesDatabase, self).__init__(db_filename, True, autocommit=True)
+        db_filename = os.path.join(self.resource.fp.path, SharedCalendarsDatabase.db_basename)
+        super(SharedCalendarsDatabase, self).__init__(db_filename, True, autocommit=True)
 
     def create(self):
         """
@@ -714,33 +963,33 @@
 
     def allRecords(self):
         
-        records = self._db_execute("select * from INVITE order by USERID")
+        records = self._db_execute("select * from SHARES order by LOCALNAME")
         return [self._makeRecord(row) for row in (records if records is not None else ())]
     
-    def recordForUserID(self, userid):
+    def recordForLocalName(self, localname):
         
-        row = self._db_execute("select * from INVITE where USERID = :1", userid)
+        row = self._db_execute("select * from SHARES where LOCALNAME = :1", localname)
         return self._makeRecord(row[0]) if row else None
     
     def recordForInviteUID(self, inviteUID):
 
-        row = self._db_execute("select * from INVITE where INVITEUID = :1", inviteUID)
+        row = self._db_execute("select * from SHARES where INVITEUID = :1", inviteUID)
         return self._makeRecord(row[0]) if row else None
     
     def addOrUpdateRecord(self, record):
 
-        self._db_execute("""insert or replace into INVITE (INVITEUID, USERID, ACCESS, STATE, SUMMARY)
-            values (:1, :2, :3, :4, :5)
-            """, record.inviteuid, record.userid, record.access, record.state, record.summary,
+        self._db_execute("""insert or replace into SHARES (INVITEUID, HOSTURL, LOCALNAME, SUMMARY)
+            values (:1, :2, :3, :4)
+            """, record.inviteuid, record.hosturl, record.localname, record.summary,
         )
     
-    def removeRecordForUserID(self, userid):
+    def removeRecordForLocalName(self, localname):
 
-        self._db_execute("delete from INVITE where USERID = :1", userid)
+        self._db_execute("delete from SHARES where LOCALNAME = :1", localname)
     
     def removeRecordForInviteUID(self, inviteUID):
 
-        self._db_execute("delete from INVITE where INVITEUID = :1", inviteUID)
+        self._db_execute("delete from SHARES where INVITEUID = :1", inviteUID)
     
     def remove(self):
         
@@ -751,13 +1000,13 @@
         """
         @return: the schema version assigned to this index.
         """
-        return InvitesDatabase.schema_version
+        return SharedCalendarsDatabase.schema_version
 
     def _db_type(self):
         """
         @return: the collection type assigned to this index.
         """
-        return InvitesDatabase.db_type
+        return SharedCalendarsDatabase.db_type
 
     def _db_init_data_tables(self, q):
         """
@@ -765,20 +1014,18 @@
         @param q:           a database cursor to use.
         """
         #
-        # INVITE table is the primary table
+        # SHARES table is the primary table
         #   INVITEUID: UID for this invite
-        #   NAME: identifier of invitee
-        #   ACCESS: Access mode for share
-        #   STATE: Invite response status
+        #   HOSTURL: URL for data source
+        #   LOCALNAME: local path name
         #   SUMMARY: Invite summary
         #
         q.execute(
             """
-            create table INVITE (
+            create table SHARES (
                 INVITEUID      text unique,
-                USERID         text unique,
-                ACCESS         text,
-                STATE          text,
+                HOSTURL        text,
+                LOCALNAME      text,
                 SUMMARY        text
             )
             """
@@ -786,14 +1033,19 @@
 
         q.execute(
             """
-            create index USERID on INVITE (USERID)
+            create index INVITEUID on SHARES (INVITEUID)
             """
         )
         q.execute(
             """
-            create index INVITEUID on INVITE (INVITEUID)
+            create index HOSTURL on SHARES (HOSTURL)
             """
         )
+        q.execute(
+            """
+            create index LOCALNAME on SHARES (LOCALNAME)
+            """
+        )
 
     def _db_upgrade_data_tables(self, q, old_version):
         """
@@ -805,5 +1057,4 @@
 
     def _makeRecord(self, row):
         
-        return Invite(*[str(item) if type(item) == types.UnicodeType else item for item in row])
-
+        return SharedCalendarRecord(*[str(item) if type(item) == types.UnicodeType else item for item in row])

Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/static.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/static.py	2010-03-15 18:05:21 UTC (rev 5307)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/static.py	2010-03-15 19:59:17 UTC (rev 5308)
@@ -934,6 +934,12 @@
         CalDAVFile.__init__(self, path)
         DirectoryCalendarHomeResource.__init__(self, parent, record)
 
+    def provision(self):
+        result = super(CalendarHomeFile, self).provision()
+        if config.Sharing.Enabled:
+            self.provisionShares()
+        return result
+
     def provisionChild(self, name):
         if config.EnableDropBox:
             DropBoxHomeFileClass = DropBoxHomeFile
@@ -963,7 +969,6 @@
             child.cacheNotifier = self.cacheNotifier
             child.clientNotifier = self.clientNotifier
             return child
-
         return self.createSimilarFile(self.fp.child(name).path)
 
     def createSimilarFile(self, path):

Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/test/test_sharing.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/test/test_sharing.py	2010-03-15 18:05:21 UTC (rev 5307)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/test/test_sharing.py	2010-03-15 19:59:17 UTC (rev 5308)
@@ -81,13 +81,15 @@
     def test_upgradeToShareOnCreate(self):
         request = SimpleRequest(self.site, "MKCOL", "/calendar/")
 
-        self.assertEquals(self.resource.resourceType(), davxml.ResourceType.calendar)
+        rtype = (yield self.resource.resourceType(request))
+        self.assertEquals(rtype, davxml.ResourceType.calendar)
         propInvite = (yield self.resource.readProperty(customxml.Invite, request))
         self.assertEquals(propInvite, None)
 
         yield self.resource.upgradeToShare(request)
 
-        self.assertEquals(self.resource.resourceType(), davxml.ResourceType.sharedcalendar)
+        rtype = (yield self.resource.resourceType(request))
+        self.assertEquals(rtype, davxml.ResourceType.sharedcalendar)
         propInvite = (yield self.resource.readProperty(customxml.Invite, request))
         self.assertEquals(propInvite, customxml.Invite())
         
@@ -100,13 +102,15 @@
     def test_upgradeToShareAfterCreate(self):
         request = SimpleRequest(self.site, "PROPPATCH", "/calendar/")
 
-        self.assertEquals(self.resource.resourceType(), davxml.ResourceType.calendar)
+        rtype = (yield self.resource.resourceType(request))
+        self.assertEquals(rtype, davxml.ResourceType.calendar)
         propInvite = (yield self.resource.readProperty(customxml.Invite, request))
         self.assertEquals(propInvite, None)
 
         self.assertRaises(HTTPError, self.resource.upgradeToShare, request)
 
-        self.assertEquals(self.resource.resourceType(), davxml.ResourceType.calendar)
+        rtype = (yield self.resource.resourceType(request))
+        self.assertEquals(rtype, davxml.ResourceType.calendar)
         propInvite = (yield self.resource.readProperty(customxml.Invite, request))
         self.assertEquals(propInvite, None)
         
@@ -117,15 +121,19 @@
 
     @inlineCallbacks
     def test_downgradeFromShare(self):
+        request = SimpleRequest(self.site, "PROPPATCH", "/calendar/")
+
         self.resource.writeDeadProperty(davxml.ResourceType.sharedcalendar)
         self.resource.writeDeadProperty(customxml.Invite())
-        self.assertEquals(self.resource.resourceType(), davxml.ResourceType.sharedcalendar)
+        rtype = (yield self.resource.resourceType(request))
+        self.assertEquals(rtype, davxml.ResourceType.sharedcalendar)
         propInvite = (yield self.resource.readProperty(customxml.Invite, None))
         self.assertEquals(propInvite, customxml.Invite())
 
         yield self.resource.downgradeFromShare(None)
 
-        self.assertEquals(self.resource.resourceType(), davxml.ResourceType.calendar)
+        rtype = (yield self.resource.resourceType(request))
+        self.assertEquals(rtype, davxml.ResourceType.calendar)
         propInvite = (yield self.resource.readProperty(customxml.Invite, None))
         self.assertEquals(propInvite, None)
         

Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/timezoneservice.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/timezoneservice.py	2010-03-15 18:05:21 UTC (rev 5307)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/timezoneservice.py	2010-03-15 19:59:17 UTC (rev 5308)
@@ -28,10 +28,11 @@
 from twext.web2.dav import davxml
 from twext.web2.http import HTTPError
 from twext.web2.http import Response
+from twext.web2.http import XMLResponse
 from twext.web2.http_headers import MimeType
 from twext.web2.stream import MemoryStream
 
-from twext.web2.http import XMLResponse
+from twisted.internet.defer import succeed
 
 from twistedcaldav import customxml
 from twistedcaldav.customxml import calendarserver_namespace
@@ -72,8 +73,8 @@
             ),
         )
 
-    def resourceType(self):
-        return davxml.ResourceType.timezones
+    def resourceType(self, request):
+        return succeed(davxml.ResourceType.timezones)
 
     def isCollection(self):
         return False
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20100315/51a23a89/attachment-0001.html>


More information about the calendarserver-changes mailing list