[CalendarServer-changes] [6573] CalendarServer/trunk

source_changes at macosforge.org source_changes at macosforge.org
Sat Nov 6 20:01:43 PDT 2010


Revision: 6573
          http://trac.macosforge.org/projects/calendarserver/changeset/6573
Author:   cdaboo at apple.com
Date:     2010-11-06 20:01:41 -0700 (Sat, 06 Nov 2010)
Log Message:
-----------
Refactor to get rid of proto class for calendar/addressbook collections. Also refactor storebridge
to pull out more "common" behavior between caldav/carddav. More refactoring along these lines to come.

Modified Paths:
--------------
    CalendarServer/trunk/twistedcaldav/method/put_addressbook_common.py
    CalendarServer/trunk/twistedcaldav/resource.py
    CalendarServer/trunk/twistedcaldav/sharing.py
    CalendarServer/trunk/twistedcaldav/storebridge.py
    CalendarServer/trunk/twistedcaldav/test/test_wrapping.py
    CalendarServer/trunk/txdav/caldav/datastore/file.py
    CalendarServer/trunk/txdav/caldav/datastore/sql.py
    CalendarServer/trunk/txdav/carddav/datastore/file.py
    CalendarServer/trunk/txdav/carddav/datastore/sql.py
    CalendarServer/trunk/txdav/common/datastore/file.py
    CalendarServer/trunk/txdav/common/datastore/sql.py

Modified: CalendarServer/trunk/twistedcaldav/method/put_addressbook_common.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/put_addressbook_common.py	2010-11-04 23:05:26 UTC (rev 6572)
+++ CalendarServer/trunk/twistedcaldav/method/put_addressbook_common.py	2010-11-07 03:01:41 UTC (rev 6573)
@@ -353,7 +353,7 @@
     @inlineCallbacks
     def doSourceDelete(self):
         # Delete the source resource
-        yield self.source.storeRemove(self.request, self.source_uri)
+        yield self.source.storeRemove(self.request, False, self.source_uri)
         log.debug("Source removed %s" % (self.source,))
         returnValue(None)
 

Modified: CalendarServer/trunk/twistedcaldav/resource.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/resource.py	2010-11-04 23:05:26 UTC (rev 6572)
+++ CalendarServer/trunk/twistedcaldav/resource.py	2010-11-07 03:01:41 UTC (rev 6573)
@@ -192,6 +192,10 @@
     ##
 
     def render(self, request):
+
+        if not self.exists():
+            return responsecode.NOT_FOUND
+
         if config.EnableMonolithicCalendars:
             #
             # Send listing instead of iCalendar data to HTML agents
@@ -487,9 +491,8 @@
                 # FIXME: is there a better way to get back to the associated
                 # datastore object?
                 dataObject = None
-                for attr in ("_newStoreCalendar", "_newStoreAddressBook"):
-                    if hasattr(self, attr):
-                        dataObject = getattr(self, attr)
+                if hasattr(self, "_newStoreObject"):
+                    dataObject = getattr(self, "_newStoreObject")
                 if dataObject:
                     label = "collection" if isvirt else "default"
                     notifierID = dataObject.notifierID(label=label)
@@ -2322,21 +2325,11 @@
     @inlineCallbacks
     def makeRegularChild(self, name):
         newCalendar = yield self._newStoreHome.calendarWithName(name)
-        if newCalendar is None:
-            # Local imports.due to circular dependency between modules.
-            from twistedcaldav.storebridge import (
-                 ProtoCalendarCollectionResource)
-            similar = ProtoCalendarCollectionResource(
-                self._newStoreHome,
-                name,
-                principalCollections=self.principalCollections()
-            )
-        else:
-            from twistedcaldav.storebridge import CalendarCollectionResource
-            similar = CalendarCollectionResource(
-                newCalendar, self._newStoreHome,
-                principalCollections=self.principalCollections()
-            )
+        from twistedcaldav.storebridge import CalendarCollectionResource
+        similar = CalendarCollectionResource(
+            newCalendar, self._newStoreHome, name=name,
+            principalCollections=self.principalCollections()
+        )
         self.propagateTransaction(similar)
         returnValue(similar)
 
@@ -2488,30 +2481,18 @@
         # Check for public/global path
         from twistedcaldav.storebridge import (
             AddressBookCollectionResource,
-            ProtoAddressBookCollectionResource,
             GlobalAddressBookCollectionResource,
-            ProtoGlobalAddressBookCollectionResource,
         )
         mainCls = AddressBookCollectionResource
-        protoCls = ProtoAddressBookCollectionResource
         if isinstance(self.record, InternalDirectoryRecord):
             if "global" in self.record.shortNames:
                 mainCls = GlobalAddressBookCollectionResource
-                protoCls = ProtoGlobalAddressBookCollectionResource
 
         newAddressBook = yield self._newStoreHome.addressbookWithName(name)
-        if newAddressBook is None:
-            # Local imports.due to circular dependency between modules.
-            similar = protoCls(
-                self._newStoreHome,
-                name,
-                principalCollections=self.principalCollections()
-            )
-        else:
-            similar = mainCls(
-                newAddressBook, self._newStoreHome,
-                principalCollections=self.principalCollections()
-            )
+        similar = mainCls(
+            newAddressBook, self._newStoreHome, name,
+            principalCollections=self.principalCollections()
+        )
         self.propagateTransaction(similar)
         returnValue(similar)
 

Modified: CalendarServer/trunk/twistedcaldav/sharing.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/sharing.py	2010-11-04 23:05:26 UTC (rev 6572)
+++ CalendarServer/trunk/twistedcaldav/sharing.py	2010-11-07 03:01:41 UTC (rev 6573)
@@ -213,10 +213,8 @@
         self._shareePrincipal = shareePrincipal
         self._share = share
 
-        if hasattr(self, "_newStoreCalendar"):
-            self._newStoreCalendar.setSharingUID(self._shareePrincipal.principalUID())
-        elif hasattr(self, "_newStoreAddressBook"):
-            self._newStoreAddressBook.setSharingUID(self._shareePrincipal.principalUID())
+        if hasattr(self, "_newStoreObject"):
+            self._newStoreObject.setSharingUID(self._shareePrincipal.principalUID())
 
 
     def isVirtualShare(self):

Modified: CalendarServer/trunk/twistedcaldav/storebridge.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/storebridge.py	2010-11-04 23:05:26 UTC (rev 6572)
+++ CalendarServer/trunk/twistedcaldav/storebridge.py	2010-11-07 03:01:41 UTC (rev 6573)
@@ -31,9 +31,11 @@
 from twext.python import vcomponent
 from twext.python.log import Logger
 
+from twext.web2 import responsecode
 from twext.web2.dav import davxml
 from twext.web2.dav.element.base import dav_namespace
 from twext.web2.dav.http import ErrorResponse, ResponseQueue
+from twext.web2.dav.noneprops import NonePropertyStore
 from twext.web2.dav.resource import TwistedACLInheritable, AccessDeniedError
 from twext.web2.dav.util import parentForURL, allDataFromStream, joinURL, \
     davXMLFromStream
@@ -184,33 +186,36 @@
 
 
 
-class _CalendarChildHelper(object):
+class _CommonHomeChildCollectionMixin(object):
     """
     Methods for things which are like calendars.
     """
 
-    def _initializeWithCalendar(self, calendar, home):
+    _childClass = None
+    _protoChildClass = None
+
+    def _initializeWithHomeChild(self, child, home):
         """
-        Initialize with a calendar.
+        Initialize with a home child object.
 
-        @param calendar: the wrapped calendar.
-        @type calendar: L{txdav.caldav.icalendarstore.ICalendar}
+        @param child: the new store home child object.
+        @type calendar: L{txdav.common._.CommonHomeChild}
 
-        @param home: the home through which the given calendar was accessed.
-        @type home: L{txdav.caldav.icalendarstore.ICalendarHome}
+        @param home: the home through which the given home child was accessed.
+        @type home: L{txdav.common._.CommonHome}
         """
-        self._newStoreCalendar = calendar
+        self._newStoreObject = child
         self._newStoreParentHome = home
         self._dead_properties = _NewStorePropertiesWrapper(
-            self._newStoreCalendar.properties()
-        )
+            self._newStoreObject.properties()
+        ) if self._newStoreObject else NonePropertyStore(self)
 
 
     def index(self):
         """
         Retrieve the new-style index wrapper.
         """
-        return self._newStoreCalendar.retrieveOldIndex()
+        return self._newStoreObject.retrieveOldIndex()
 
 
     def invitesDB(self):
@@ -218,33 +223,24 @@
         Retrieve the new-style invites DB wrapper.
         """
         if not hasattr(self, "_invitesDB"):
-            self._invitesDB = self._newStoreCalendar.retrieveOldInvites()
+            self._invitesDB = self._newStoreObject.retrieveOldInvites()
         return self._invitesDB
 
 
     def exists(self):
         # FIXME: tests
-        return True
+        return self._newStoreObject is not None
 
 
     @inlineCallbacks
     def _indexWhatChanged(self, revision, depth):
         # The newstore implementation supports this directly
         returnValue(
-            (yield self._newStoreCalendar.resourceNamesSinceToken(revision))
+            (yield self._newStoreObject.resourceNamesSinceToken(revision))
             + ([],)
         )
 
 
-    @classmethod
-    def transform(cls, self, calendar, home):
-        """
-        Transform C{self} into a L{CalendarCollectionResource}.
-        """
-        self.__class__ = cls
-        self._initializeWithCalendar(calendar, home)
-
-
     @inlineCallbacks
     def makeChild(self, name):
         """
@@ -252,38 +248,369 @@
         based on a calendar object name.
         """
 
-        cal = self._newStoreCalendar
-        newStoreObject = yield cal.calendarObjectWithName(name)
-
-        if newStoreObject is not None:
-            similar = CalendarObjectResource(
-                newStoreObject,
-                principalCollections=self._principalCollections
-            )
+        if self._newStoreObject:
+            newStoreObject = yield self._newStoreObject.objectResourceWithName(name)
+    
+            if newStoreObject is not None:
+                similar = self._childClass(
+                    newStoreObject,
+                    principalCollections=self._principalCollections
+                )
+            else:
+                similar = self._protoChildClass(
+                    self._newStoreObject, name,
+                    principalCollections=self._principalCollections
+                )
+    
+            self.propagateTransaction(similar)
+            returnValue(similar)
         else:
-            similar = ProtoCalendarObjectResource(
-                cal, name,
-                principalCollections=self._principalCollections
-            )
+            returnValue(NoParent())
 
-        self.propagateTransaction(similar)
-        returnValue(similar)
-
-
     @inlineCallbacks
     def listChildren(self):
         """
         @return: a sequence of the names of all known children of this resource.
         """
         children = set(self.putChildren.keys())
-        children.update((yield self._newStoreCalendar.listCalendarObjects()))
+        children.update((yield self._newStoreObject.listObjectResources()))
         returnValue(sorted(children))
 
+    def name(self):
+        return self._name
 
 
-class StoreScheduleInboxResource(_CalendarChildHelper, ScheduleInboxResource):
+    def etag(self):
+        return ETag(self._newStoreObject.md5()) if self._newStoreObject else None
 
+
+    def lastModified(self):
+        return self._newStoreObject.modified() if self._newStoreObject else None
+
+
+    def creationDate(self):
+        return self._newStoreObject.created() if self._newStoreObject else None
+
+
+    def getSyncToken(self):
+        return self._newStoreObject.syncToken() if self._newStoreObject else None
+
+    @inlineCallbacks
+    def createCollection(self):
+        """
+        Override C{createCollection} to actually do the work.
+        """
+        self._newStoreObject = (yield self._newStoreParentHome.createChildWithName(self._name))
+        
+        # Re-initialize to get stuff setup again now we have a "real" object
+        self._initializeWithHomeChild(self._newStoreObject, self._newStoreParentHome)
+
+        returnValue(CREATED)
+
+    @requiresPermissions(fromParent=[davxml.Unbind()])
+    @inlineCallbacks
+    def http_DELETE(self, request):
+        """
+        Override http_DELETE to validate 'depth' header. 
+        """
+
+        if not self.exists():
+            log.err("Resource not found: %s" % (self,))
+            raise HTTPError(responsecode.NOT_FOUND)
+
+        depth = request.headers.getHeader("depth", "infinity")
+        if depth != "infinity":
+            msg = "illegal depth header for DELETE on collection: %s" % (
+                depth,
+            )
+            log.err(msg)
+            raise HTTPError(StatusResponse(BAD_REQUEST, msg))
+        response = (yield self.storeRemove(request, True, request.uri))
+        returnValue(response)
+
+    @inlineCallbacks
+    def storeRemove(self, request, viaRequest, where):
+        """
+        Delete this collection resource, first deleting each contained
+        object resource.
+
+        This has to emulate the behavior in fileop.delete in that any errors
+        need to be reported back in a multistatus response.
+
+        @param request: The request used to locate child resources.  Note that
+            this is the request which I{triggered} the C{DELETE}, but which may
+            not actually be a C{DELETE} request itself.
+
+        @type request: L{twext.web2.iweb.IRequest}
+
+        @param viaRequest: Indicates if the delete was a direct result of an http_DELETE
+        which for calendars at least will require implicit cancels to be sent.
+
+        @type request: C{bool}
+
+        @param where: the URI at which the resource is being deleted.
+        @type where: C{str}
+
+        @return: an HTTP response suitable for sending to a client (or
+            including in a multi-status).
+
+        @rtype: something adaptable to L{twext.web2.iweb.IResponse}
+        """
+
+        # Check virtual share first
+        isVirtual = self.isVirtualShare()
+        if isVirtual:
+            log.debug("Removing shared collection %s" % (self,))
+            yield self.removeVirtualShare(request)
+            returnValue(NO_CONTENT)
+
+        log.debug("Deleting collection %s" % (self,))
+
+        # 'deluri' is this resource's URI; I should be able to synthesize it
+        # from 'self'.
+
+        errors = ResponseQueue(where, "DELETE", NO_CONTENT)
+
+        for childname in (yield self.listChildren()):
+
+            childurl = joinURL(where, childname)
+
+            # FIXME: use a more specific API; we should know what this child
+            # resource is, and not have to look it up.  (Sharing information
+            # needs to move into the back-end first, though.)
+            child = (yield request.locateChildResource(self, childname))
+
+            try:
+                yield child.storeRemove(request, viaRequest, childurl)
+            except:
+                logDefaultException()
+                errors.add(childurl, BAD_REQUEST)
+
+        # Now do normal delete
+
+        # Handle sharing
+        wasShared = (yield self.isShared(request))
+        if wasShared:
+            yield self.downgradeFromShare(request)
+
+        # Actually delete it.
+        yield self._newStoreObject.remove()
+
+        # FIXME: handle exceptions, possibly like this:
+
+        #        if isinstance(more_responses, MultiStatusResponse):
+        #            # Merge errors
+        #            errors.responses.update(more_responses.children)
+
+        response = errors.response()
+
+        returnValue(response)
+
+    def http_COPY(self, request):
+        """
+        Copying of calendar collections isn't allowed.
+        """
+        # FIXME: no direct tests
+        return FORBIDDEN
+
+
+    # FIXME: access control
+    @inlineCallbacks
+    def http_MOVE(self, request):
+        """
+        Moving a collection is allowed for the purposes of changing
+        that collections's name.
+        """
+        if not self.exists():
+            log.err("Resource not found: %s" % (self,))
+            raise HTTPError(responsecode.NOT_FOUND)
+
+        # Can not move outside of home or to existing collection
+        sourceURI = request.uri
+        destinationURI = urlsplit(request.headers.getHeader("destination"))[2]
+        if parentForURL(sourceURI) != parentForURL(destinationURI):
+            returnValue(FORBIDDEN)
+
+        destination = yield request.locateResource(destinationURI)
+        if destination.exists():
+            returnValue(FORBIDDEN)
+            
+        # Forget the destination now as after the move we will need to re-init it with its
+        # new store object
+        request._forgetResource(destination, destinationURI)
+
+        # Move is valid so do it
+        basename = destinationURI.rstrip("/").split("/")[-1]
+        yield self._newStoreObject.rename(basename)
+        returnValue(NO_CONTENT)
+
+class CalendarCollectionResource(_CommonHomeChildCollectionMixin, CalDAVResource):
+    """
+    Wrapper around a L{txdav.caldav.icalendar.ICalendar}.
+    """
+
+ 
+    def __init__(self, calendar, home, name=None, *args, **kw):
+        """
+        Create a CalendarCollectionResource from a L{txdav.caldav.icalendar.ICalendar}
+        and the arguments required for L{CalDAVResource}.
+        """
+
+        self._childClass = CalendarObjectResource
+        self._protoChildClass = ProtoCalendarObjectResource
+        super(CalendarCollectionResource, self).__init__(*args, **kw)
+        self._initializeWithHomeChild(calendar, home)
+        self._name = calendar.name() if calendar else name
+
+
+    def __repr__(self):
+        return "<Calendar Collection Resource %r:%r %s>" % (
+            self._newStoreParentHome.uid(),
+            self._name,
+            "" if self._newStoreObject else "Non-existent"
+        )
+
+
+    def isCollection(self):
+        return True
+
+
+    def isCalendarCollection(self):
+        """
+        Yes, it is a calendar collection.
+        """
+        return True
+
+
+    @inlineCallbacks
+    def iCalendarRolledup(self, request):
+        # FIXME: uncached: implement cache in the storage layer
+
+        # Generate a monolithic calendar
+        calendar = vcomponent.VComponent("VCALENDAR")
+        calendar.addProperty(vcomponent.VProperty("VERSION", "2.0"))
+
+        # Do some optimisation of access control calculation by determining any
+        # inherited ACLs outside of the child resource loop and supply those to
+        # the checkPrivileges on each child.
+        filteredaces = (yield self.inheritedACEsforChildren(request))
+
+        tzids = set()
+        isowner = (yield self.isOwner(request, adminprincipals=True, readprincipals=True))
+        accessPrincipal = (yield self.resourceOwnerPrincipal(request))
+
+        for name, uid, type in (yield maybeDeferred(self.index().bruteForceSearch)): #@UnusedVariable
+            try:
+                child = yield request.locateChildResource(self, name)
+            except TypeError:
+                child = None
+
+            if child is not None:
+                # Check privileges of child - skip if access denied
+                try:
+                    yield child.checkPrivileges(request, (davxml.Read(),), inherited_aces=filteredaces)
+                except AccessDeniedError:
+                    continue
+
+                # Get the access filtered view of the data
+                caldata = yield child.iCalendarTextFiltered(isowner, accessPrincipal.principalUID() if accessPrincipal else "")
+                try:
+                    subcalendar = vcomponent.VComponent.fromString(caldata)
+                except ValueError:
+                    continue
+                assert subcalendar.name() == "VCALENDAR"
+
+                for component in subcalendar.subcomponents():
+                    
+                    # Only insert VTIMEZONEs once
+                    if component.name() == "VTIMEZONE":
+                        tzid = component.propertyValue("TZID")
+                        if tzid in tzids:
+                            continue
+                        tzids.add(tzid)
+
+                    calendar.addComponent(component)
+
+        # Cache the data
+        data = str(calendar)
+        data = (yield self.getSyncToken()) + "\r\n" + data
+
+        returnValue(calendar)
+
+
+    createCalendarCollection = _CommonHomeChildCollectionMixin.createCollection
+
+
+    @inlineCallbacks
+    def storeRemove(self, request, implicitly, where):
+        """
+        Delete this calendar collection resource, first deleting each contained
+        calendar resource.
+
+        This has to emulate the behavior in fileop.delete in that any errors
+        need to be reported back in a multistatus response.
+
+        @param request: The request used to locate child resources.  Note that
+            this is the request which I{triggered} the C{DELETE}, but which may
+            not actually be a C{DELETE} request itself.
+
+        @type request: L{twext.web2.iweb.IRequest}
+
+        @param implicitly: Should implicit scheduling operations be triggered
+            as a resut of this C{DELETE}?
+
+        @type implicitly: C{bool}
+
+        @param where: the URI at which the resource is being deleted.
+        @type where: C{str}
+
+        @return: an HTTP response suitable for sending to a client (or
+            including in a multi-status).
+
+        @rtype: something adaptable to L{twext.web2.iweb.IResponse}
+        """
+
+        # Not allowed to delete the default calendar
+        default = (yield self.isDefaultCalendar(request))
+        if default:
+            log.err("Cannot DELETE default calendar: %s" % (self,))
+            raise HTTPError(ErrorResponse(FORBIDDEN,
+                            (caldav_namespace,
+                             "default-calendar-delete-allowed",)))
+
+        response = (yield super(CalendarCollectionResource, self).storeRemove(request, implicitly, where))
+
+        if response == NO_CONTENT:
+            # Do some clean up
+            yield self.deletedCalendar(request)
+
+        returnValue(response)
+
+
+    # FIXME: access control
+    @inlineCallbacks
+    def http_MOVE(self, request):
+        """
+        Moving a calendar collection is allowed for the purposes of changing
+        that calendar's name.
+        """
+        defaultCalendar = (yield self.isDefaultCalendar(request))
+        
+        result = (yield super(CalendarCollectionResource, self).http_MOVE(request))
+        if result == NO_CONTENT:
+            destinationURI = urlsplit(request.headers.getHeader("destination"))[2]
+            destination = yield request.locateResource(destinationURI)
+            yield self.movedCalendar(request, defaultCalendar,
+                               destination, destinationURI)
+        returnValue(result)
+
+
+class StoreScheduleInboxResource(_CommonHomeChildCollectionMixin, ScheduleInboxResource):
+
     def __init__(self, *a, **kw):
+
+        self._childClass = CalendarObjectResource
+        self._protoChildClass = ProtoCalendarObjectResource
         super(StoreScheduleInboxResource, self).__init__(*a, **kw)
         self.parent.propagateTransaction(self)
 
@@ -301,42 +628,30 @@
             # this is a temporary workaround.
             yield home.createCalendarWithName("inbox")
             storage = yield home.calendarWithName("inbox")
-        self._initializeWithCalendar(
+        self._initializeWithHomeChild(
             storage,
             self.parent._newStoreHome
         )
+        self._name = storage.name()
         returnValue(self)
 
 
-    def name(self):
-        return self._newStoreCalendar.name()
-
-
-    def etag(self):
-        return ETag(self._newStoreCalendar.md5())
-
-
-    def lastModified(self):
-        return self._newStoreCalendar.modified()
-
-
-    def creationDate(self):
-        return self._newStoreCalendar.created()
-
-
-    def getSyncToken(self):
-        return self._newStoreCalendar.syncToken()
-
-
     def provisionFile(self):
         pass
 
-
     def provision(self):
         pass
 
+    def http_DELETE(self, request):
+        return FORBIDDEN
 
+    def http_COPY(self, request):
+        return FORBIDDEN
 
+    def http_MOVE(self, request):
+        return FORBIDDEN
+
+
 class _GetChildHelper(CalDAVResource):
 
     def locateChild(self, request, segments):
@@ -702,263 +1017,9 @@
 
 
 
-class CalendarCollectionResource(_CalendarChildHelper, CalDAVResource):
-    """
-    Wrapper around a L{txdav.caldav.icalendar.ICalendar}.
-    """
 
-    def __init__(self, calendar, home, *args, **kw):
-        """
-        Create a CalendarCollectionResource from a L{txdav.caldav.icalendar.ICalendar}
-        and the arguments required for L{CalDAVResource}.
-        """
-        super(CalendarCollectionResource, self).__init__(*args, **kw)
-        self._initializeWithCalendar(calendar, home)
 
 
-    def __repr__(self):
-        return "<Calendar Collection Resource %r:%r>" % (
-            self._newStoreCalendar.ownerCalendarHome().uid(),
-            self._newStoreCalendar.name())
-
-
-    def name(self):
-        return self._newStoreCalendar.name()
-
-
-    def etag(self):
-        return ETag(self._newStoreCalendar.md5())
-
-
-    def lastModified(self):
-        return self._newStoreCalendar.modified()
-
-
-    def creationDate(self):
-        return self._newStoreCalendar.created()
-
-
-    def getSyncToken(self):
-        return self._newStoreCalendar.syncToken()
-
-
-    def isCollection(self):
-        return True
-
-
-    def isCalendarCollection(self):
-        """
-        Yes, it is a calendar collection.
-        """
-        return True
-
-
-    @inlineCallbacks
-    def iCalendarRolledup(self, request):
-        # FIXME: uncached: implement cache in the storage layer
-
-        # Generate a monolithic calendar
-        calendar = vcomponent.VComponent("VCALENDAR")
-        calendar.addProperty(vcomponent.VProperty("VERSION", "2.0"))
-
-        # Do some optimisation of access control calculation by determining any
-        # inherited ACLs outside of the child resource loop and supply those to
-        # the checkPrivileges on each child.
-        filteredaces = (yield self.inheritedACEsforChildren(request))
-
-        tzids = set()
-        isowner = (yield self.isOwner(request, adminprincipals=True, readprincipals=True))
-        accessPrincipal = (yield self.resourceOwnerPrincipal(request))
-
-        for name, uid, type in (yield maybeDeferred(self.index().bruteForceSearch)): #@UnusedVariable
-            try:
-                child = yield request.locateChildResource(self, name)
-            except TypeError:
-                child = None
-
-            if child is not None:
-                # Check privileges of child - skip if access denied
-                try:
-                    yield child.checkPrivileges(request, (davxml.Read(),), inherited_aces=filteredaces)
-                except AccessDeniedError:
-                    continue
-
-                # Get the access filtered view of the data
-                caldata = yield child.iCalendarTextFiltered(isowner, accessPrincipal.principalUID() if accessPrincipal else "")
-                try:
-                    subcalendar = vcomponent.VComponent.fromString(caldata)
-                except ValueError:
-                    continue
-                assert subcalendar.name() == "VCALENDAR"
-
-                for component in subcalendar.subcomponents():
-                    
-                    # Only insert VTIMEZONEs once
-                    if component.name() == "VTIMEZONE":
-                        tzid = component.propertyValue("TZID")
-                        if tzid in tzids:
-                            continue
-                        tzids.add(tzid)
-
-                    calendar.addComponent(component)
-
-        # Cache the data
-        data = str(calendar)
-        data = (yield self.getSyncToken()) + "\r\n" + data
-
-        returnValue(calendar)
-
-
-    @requiresPermissions(fromParent=[davxml.Unbind()])
-    @inlineCallbacks
-    def http_DELETE(self, request):
-        """
-        Override http_DELETE to validate 'depth' header. 
-        """
-
-        depth = request.headers.getHeader("depth", "infinity")
-        if depth != "infinity":
-            msg = "illegal depth header for DELETE on collection: %s" % (
-                depth,
-            )
-            log.err(msg)
-            raise HTTPError(StatusResponse(BAD_REQUEST, msg))
-        response = (yield self.storeRemove(request, True, request.uri))
-        returnValue(response)
-
-
-    @inlineCallbacks
-    def storeRemove(self, request, implicitly, where):
-        """
-        Delete this calendar collection resource, first deleting each contained
-        calendar resource.
-
-        This has to emulate the behavior in fileop.delete in that any errors
-        need to be reported back in a multistatus response.
-
-        @param request: The request used to locate child resources.  Note that
-            this is the request which I{triggered} the C{DELETE}, but which may
-            not actually be a C{DELETE} request itself.
-
-        @type request: L{twext.web2.iweb.IRequest}
-
-        @param implicitly: Should implicit scheduling operations be triggered
-            as a resut of this C{DELETE}?
-
-        @type implicitly: C{bool}
-
-        @param where: the URI at which the resource is being deleted.
-        @type where: C{str}
-
-        @return: an HTTP response suitable for sending to a client (or
-            including in a multi-status).
-
-         @rtype: something adaptable to L{twext.web2.iweb.IResponse}
-        """
-
-        # Not allowed to delete the default calendar
-        default = (yield self.isDefaultCalendar(request))
-        if default:
-            log.err("Cannot DELETE default calendar: %s" % (self,))
-            raise HTTPError(ErrorResponse(FORBIDDEN,
-                            (caldav_namespace,
-                             "default-calendar-delete-allowed",)))
-
-        # Is this a sharee's view of a shared calendar?  If so, they can't do
-        # scheduling onto it, so just delete it and move on.
-        isVirtual = self.isVirtualShare()
-        if isVirtual:
-            log.debug("Removing shared calendar %s" % (self,))
-            yield self.removeVirtualShare(request)
-            returnValue(NO_CONTENT)
-
-        log.debug("Deleting calendar %s" % (self,))
-
-        # 'deluri' is this resource's URI; I should be able to synthesize it
-        # from 'self'.
-
-        errors = ResponseQueue(where, "DELETE", NO_CONTENT)
-
-        for childname in (yield self.listChildren()):
-
-            childurl = joinURL(where, childname)
-
-            # FIXME: use a more specific API; we should know what this child
-            # resource is, and not have to look it up.  (Sharing information
-            # needs to move into the back-end first, though.)
-            child = (yield request.locateChildResource(self, childname))
-
-            try:
-                yield child.storeRemove(request, implicitly, childurl)
-            except:
-                logDefaultException()
-                errors.add(childurl, BAD_REQUEST)
-
-        # Now do normal delete
-
-        # Handle sharing
-        wasShared = (yield self.isShared(request))
-        if wasShared:
-            yield self.downgradeFromShare(request)
-
-        # Actually delete it.
-        yield self._newStoreParentHome.removeCalendarWithName(
-            self._newStoreCalendar.name()
-        )
-        self.__class__ = ProtoCalendarCollectionResource
-        del self._newStoreCalendar
-
-        # FIXME: handle exceptions, possibly like this:
-
-        #        if isinstance(more_responses, MultiStatusResponse):
-        #            # Merge errors
-        #            errors.responses.update(more_responses.children)
-
-        response = errors.response()
-
-        if response == NO_CONTENT:
-            # Do some clean up
-            yield self.deletedCalendar(request)
-
-        returnValue(response)
-
-
-    def http_COPY(self, request):
-        """
-        Copying of calendar collections isn't allowed.
-        """
-        # FIXME: no direct tests
-        return FORBIDDEN
-
-
-    # FIXME: access control
-    @inlineCallbacks
-    def http_MOVE(self, request):
-        """
-        Moving a calendar collection is allowed for the purposes of changing
-        that calendar's name.
-        """
-        defaultCalendar = (yield self.isDefaultCalendar(request))
-        # FIXME: created to fix CDT test, no unit tests yet
-        sourceURI = request.uri
-        destinationURI = urlsplit(request.headers.getHeader("destination"))[2]
-        if parentForURL(sourceURI) != parentForURL(destinationURI):
-            returnValue(FORBIDDEN)
-        destination = yield request.locateResource(destinationURI)
-        # FIXME: should really use something other than 'fp' attribute.
-        basename = destination.name()
-        calendar = self._newStoreCalendar
-        yield calendar.rename(basename)
-        CalendarCollectionResource.transform(destination, calendar,
-                                         self._newStoreParentHome)
-        del self._newStoreCalendar
-        self.__class__ = ProtoCalendarCollectionResource
-        yield self.movedCalendar(request, defaultCalendar,
-                           destination, destinationURI)
-        returnValue(NO_CONTENT)
-
-
-
 class NoParent(CalDAVResource):
     def http_MKCALENDAR(self, request):
         return CONFLICT
@@ -970,75 +1031,6 @@
     def isCollection(self):
         return False
 
-class ProtoCalendarCollectionResource(CalDAVResource):
-    """
-    A resource representing a calendar collection which hasn't yet been created.
-    """
-
-    def __init__(self, home, name, *args, **kw):
-        """
-        A placeholder resource for a calendar collection which does not yet
-        exist, but will become a L{CalendarCollectionResource}.
-
-        @param home: The calendar home which will be this resource's parent,
-            when it exists.
-
-        @type home: L{txdav.caldav.icalendarstore.ICalendarHome}
-        """
-        super(ProtoCalendarCollectionResource, self).__init__(*args, **kw)
-        self._newStoreParentHome = home
-        self._name = name
-
-
-    def isCollection(self):
-        return True
-
-    def makeChild(self, name):
-        # FIXME: this is necessary for 
-        # twistedcaldav.test.test_mkcalendar.
-        #     MKCALENDAR.test_make_calendar_no_parent - there should be a more
-        # structured way to refuse creation with a non-existent parent.
-        return NoParent()
-
-
-    def provisionFile(self):
-        """
-        Create a calendar collection.
-        """
-        # FIXME: there should be no need for this.
-        return self.createCalendarCollection()
-
-
-    @inlineCallbacks
-    def createCalendarCollection(self):
-        """
-        Override C{createCalendarCollection} to actually do the work.
-        """
-        yield self._newStoreParentHome.createCalendarWithName(self._name)
-        newStoreCalendar = yield self._newStoreParentHome.calendarWithName(
-            self._name
-        )
-        CalendarCollectionResource.transform(
-            self, newStoreCalendar, self._newStoreParentHome
-        )
-        returnValue(CREATED)
-
-
-    def exists(self):
-        # FIXME: tests
-        return False
-
-
-    def name(self):
-        return self._name
-
-    def provision(self):
-        """
-        This resource should do nothing if it's provisioned.
-        """
-        # FIXME: should be deleted, or raise an exception
-
-
 class _CalendarObjectMetaDataMixin(object):
 
     def _get_accessMode(self):
@@ -1341,144 +1333,32 @@
         return succeed(0)
 
 
-
-class _AddressBookChildHelper(object):
+class AddressBookCollectionResource(_CommonHomeChildCollectionMixin, CalDAVResource):
     """
-    Methods for things which are like addressbooks.
-    """
-
-    def _initializeWithAddressBook(self, addressbook, home):
-        """
-        Initialize with a addressbook.
-
-        @param addressbook: the wrapped addressbook.
-        @type addressbook: L{txdav.carddav.iaddressbookstore.IAddressBook}
-
-        @param home: the home through which the given addressbook was accessed.
-        @type home: L{txdav.carddav.iaddressbookstore.IAddressBookHome}
-        """
-        self._newStoreAddressBook = addressbook
-        self._newStoreParentHome = home
-        self._dead_properties = _NewStorePropertiesWrapper(
-            self._newStoreAddressBook.properties()
-        )
-
-
-    def index(self):
-        """
-        Retrieve the new-style index wrapper.
-        """
-        return self._newStoreAddressBook.retrieveOldIndex()
-
-
-    def invitesDB(self):
-        """
-        Retrieve the new-style invites DB wrapper.
-        """
-        if not hasattr(self, "_invitesDB"):
-            self._invitesDB = self._newStoreAddressBook.retrieveOldInvites()
-        return self._invitesDB
-
-
-    def exists(self):
-        # FIXME: tests
-        return True
-
-
-    @inlineCallbacks
-    def _indexWhatChanged(self, revision, depth):
-        # The newstore implementation supports this directly
-        returnValue(
-            (yield self._newStoreAddressBook.resourceNamesSinceToken(revision))
-            + ([],)
-        )
-
-
-    @classmethod
-    def transform(cls, self, addressbook, home):
-        """
-        Transform C{self} into a L{AddressBookCollectionResource}.
-        """
-        self.__class__ = cls
-        self._initializeWithAddressBook(addressbook, home)
-
-
-    @inlineCallbacks
-    def makeChild(self, name):
-        """
-        Create a L{AddressBookObjectResource} or L{ProtoAddressBookObjectResource} based on a
-        path object.
-        """
-        newStoreObject = yield self._newStoreAddressBook.addressbookObjectWithName(name)
-
-        if newStoreObject is not None:
-            similar = AddressBookObjectResource(
-                newStoreObject,
-                principalCollections=self._principalCollections
-            )
-        else:
-            # FIXME: creation in http_PUT should talk to a specific resource
-            # type; this is the domain of StoreAddressBookObjectResource.
-            # similar = ProtoAddressBookObjectFile(self._newStoreAddressBook, path)
-            similar = ProtoAddressBookObjectResource(
-                self._newStoreAddressBook,
-                name,
-                principalCollections=self._principalCollections
-            )
-
-        # FIXME: tests should be failing without this line.
-        # Specifically, http_PUT won't be committing its transaction properly.
-        self.propagateTransaction(similar)
-        returnValue(similar)
-
-
-    @inlineCallbacks
-    def listChildren(self):
-        """
-        @return: a sequence of the names of all known children of this resource.
-        """
-        children = set(self.putChildren.keys())
-        children.update(
-            (yield self._newStoreAddressBook.listAddressbookObjects())
-        )
-        returnValue(sorted(children))
-
-
-
-class AddressBookCollectionResource(_AddressBookChildHelper, CalDAVResource):
-    """
     Wrapper around a L{txdav.carddav.iaddressbook.IAddressBook}.
     """
 
-    def __init__(self, addressbook, home, *args, **kw):
+    def __init__(self, addressbook, home, name=None, *args, **kw):
         """
         Create a AddressBookCollectionResource from a L{txdav.carddav.iaddressbook.IAddressBook}
         and the arguments required for L{CalDAVResource}.
         """
+
+        self._childClass = AddressBookObjectResource
+        self._protoChildClass = ProtoAddressBookObjectResource
         super(AddressBookCollectionResource, self).__init__(*args, **kw)
-        self._initializeWithAddressBook(addressbook, home)
+        self._initializeWithHomeChild(addressbook, home)
+        self._name = addressbook.name() if addressbook else name
 
 
-    def name(self):
-        return self._newStoreAddressBook.name()
+    def __repr__(self):
+        return "<AddressBook Collection Resource %r:%r %s>" % (
+            self._newStoreParentHome.uid(),
+            self._name,
+            "" if self._newStoreObject else "Non-existent"
+        )
 
 
-    def etag(self):
-        return ETag(self._newStoreAddressBook.md5())
-
-
-    def lastModified(self):
-        return self._newStoreAddressBook.modified()
-
-
-    def creationDate(self):
-        return self._newStoreAddressBook.created()
-
-
-    def getSyncToken(self):
-        return self._newStoreAddressBook.syncToken()
-
-
     def isCollection(self):
         return True
 
@@ -1490,218 +1370,15 @@
         return True
 
 
-    @requiresPermissions(fromParent=[davxml.Unbind()])
-    @inlineCallbacks
-    def http_DELETE(self, request):
-        """
-        Override http_DELETE to validate 'depth' header. 
-        """
-        depth = request.headers.getHeader("depth", "infinity")
-        if depth != "infinity":
-            msg = "illegal depth header for DELETE on collection: %s" % (
-                depth,
-            )
-            log.err(msg)
-            raise HTTPError(StatusResponse(BAD_REQUEST, msg))
-        response = (yield self.storeRemove(request, request.uri))
-        returnValue(response)
+    createAddressBookCollection = _CommonHomeChildCollectionMixin.createCollection
 
 
-    @inlineCallbacks
-    def storeRemove(self, request, where):
-        """
-        Delete this addressbook collection resource, first deleting each contained
-        addressbook resource.
-
-        This has to emulate the behavior in fileop.delete in that any errors
-        need to be reported back in a multistatus response.
-
-        @param request: The request used to locate child resources.  Note that
-            this is the request which I{triggered} the C{DELETE}, but which may
-            not actually be a C{DELETE} request itself.
-
-        @type request: L{twext.web2.iweb.IRequest}
-
-        @param where: the URI at which the resource is being deleted.
-        @type where: C{str}
-
-        @return: an HTTP response suitable for sending to a client (or
-            including in a multi-status).
-
-         @rtype: something adaptable to L{twext.web2.iweb.IResponse}
-        """
-
-        # Check virtual share first
-        isVirtual = self.isVirtualShare()
-        if isVirtual:
-            log.debug("Removing shared calendar %s" % (self,))
-            yield self.removeVirtualShare(request)
-            returnValue(NO_CONTENT)
-
-        log.debug("Deleting addressbook %s" % (self,))
-
-        # 'deluri' is this resource's URI; I should be able to synthesize it
-        # from 'self'.
-
-        errors = ResponseQueue(where, "DELETE", NO_CONTENT)
-
-        for childname in (yield self.listChildren()):
-
-            childurl = joinURL(where, childname)
-
-            # FIXME: use a more specific API; we should know what this child
-            # resource is, and not have to look it up.  (Sharing information
-            # needs to move into the back-end first, though.)
-            child = (yield request.locateChildResource(self, childname))
-
-            try:
-                yield child.storeRemove(request, childurl)
-            except:
-                logDefaultException()
-                errors.add(childurl, BAD_REQUEST)
-
-        # Now do normal delete
-
-        # Handle sharing
-        wasShared = (yield self.isShared(request))
-        if wasShared:
-            yield self.downgradeFromShare(request)
-
-        # Actually delete it.
-        yield self._newStoreParentHome.removeAddressBookWithName(
-            self._newStoreAddressBook.name()
-        )
-        self.__class__ = ProtoAddressBookCollectionResource
-        del self._newStoreAddressBook
-
-        # FIXME: handle exceptions, possibly like this:
-
-        #        if isinstance(more_responses, MultiStatusResponse):
-        #            # Merge errors
-        #            errors.responses.update(more_responses.children)
-
-        response = errors.response()
-
-        returnValue(response)
-
-
-    def http_COPY(self, request):
-        """
-        Copying of addressbook collections isn't allowed.
-        """
-        # FIXME: no direct tests
-        return FORBIDDEN
-
-
-    # FIXME: access control
-    @inlineCallbacks
-    def http_MOVE(self, request):
-        """
-        Moving a addressbook collection is allowed for the purposes of changing
-        that addressbook's name.
-        """
-        # FIXME: created to fix CDT test, no unit tests yet
-        sourceURI = request.uri
-        destinationURI = urlsplit(request.headers.getHeader("destination"))[2]
-        if parentForURL(sourceURI) != parentForURL(destinationURI):
-            returnValue(FORBIDDEN)
-        destination = yield request.locateResource(destinationURI)
-        # FIXME: should really use something other than 'fp' attribute.
-        basename = destination.name()
-        addressbook = self._newStoreAddressBook
-        yield addressbook.rename(basename)
-        AddressBookCollectionResource.transform(destination, addressbook,
-                                         self._newStoreParentHome)
-        del self._newStoreAddressBook
-        self.__class__ = ProtoAddressBookCollectionResource
-        returnValue(NO_CONTENT)
-
-
-
-class ProtoAddressBookCollectionResource(CalDAVResource):
-    """
-    A resource representing an addressbook collection which hasn't yet been created.
-    """
-
-    def __init__(self, home, name, *args, **kw):
-        """
-        A placeholder resource for an addressbook collection which does not yet
-        exist, but will become a L{AddressBookCollectionResource}.
-
-        @param home: The addressbook home which will be this resource's parent,
-            when it exists.
-
-        @type home: L{txdav.carddav.iaddressbookstore.IAddressBookHome}
-        """
-        super(ProtoAddressBookCollectionResource, self).__init__(*args, **kw)
-        self._newStoreParentHome = home
-        self._name = name
-
-
-    def isCollection(self):
-        return True
-
-
-    def makeChild(self, name):
-        # FIXME: this is necessary for 
-        # twistedcaldav.test.test_mkcol.
-        #     MKCOL.test_make_addressbook_no_parent - there should be a more
-        # structured way to refuse creation with a non-existent parent.
-        return NoParent()
-
-
-    def provisionFile(self):
-        """
-        Create an addressbook collection.
-        """
-        # FIXME: this should be done in the backend; provisionDefaultAddressBooks
-        # should go away.
-        return self.createAddressBookCollection()
-
-
-    @inlineCallbacks
-    def createAddressBookCollection(self):
-        """
-        Override C{createAddressBookCollection} to actually do the work.
-        """
-        yield self._newStoreParentHome.createAddressBookWithName(self._name)
-        newStoreAddressBook = yield self._newStoreParentHome.addressbookWithName(
-            self._name
-        )
-        AddressBookCollectionResource.transform(
-            self, newStoreAddressBook, self._newStoreParentHome
-        )
-        returnValue(CREATED)
-
-
-    def exists(self):
-        # FIXME: tests
-        return False
-
-
-    def name(self):
-        return self._name
-
-    def provision(self):
-        """
-        This resource should do nothing if it's provisioned.
-        """
-        # FIXME: should be deleted, or raise an exception
-
-
 class GlobalAddressBookCollectionResource(GlobalAddressBookResource, AddressBookCollectionResource):
     """
     Wrapper around a L{txdav.carddav.iaddressbook.IAddressBook}.
     """
     pass
 
-class ProtoGlobalAddressBookCollectionResource(GlobalAddressBookResource, ProtoAddressBookCollectionResource):
-    """
-    A resource representing an addressbook collection which hasn't yet been created.
-    """
-    pass
-
-
 class AddressBookObjectResource(_NewStoreFileMetaDataHelper, CalDAVResource, FancyEqMixin):
     """
     A resource wrapping a addressbook object.
@@ -1756,7 +1433,7 @@
         """
         Override http_DELETE to validate 'depth' header. 
         """
-        return self.storeRemove(request, request.uri)
+        return self.storeRemove(request, True, request.uri)
 
 
     @inlineCallbacks
@@ -1771,7 +1448,7 @@
 
 
     @inlineCallbacks
-    def storeRemove(self, request, where):
+    def storeRemove(self, request, viaRequest, where):
         """
         Remove this addressbook object.
         """

Modified: CalendarServer/trunk/twistedcaldav/test/test_wrapping.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_wrapping.py	2010-11-04 23:05:26 UTC (rev 6572)
+++ CalendarServer/trunk/twistedcaldav/test/test_wrapping.py	2010-11-07 03:01:41 UTC (rev 6573)
@@ -32,8 +32,8 @@
 from twistedcaldav.ical import Component as VComponent
 from twistedcaldav.vcard import Component as VCComponent
 
-from twistedcaldav.storebridge import ProtoCalendarCollectionResource, \
-    ProtoAddressBookCollectionResource, DropboxCollection
+from twistedcaldav.storebridge import DropboxCollection,\
+    CalendarCollectionResource, AddressBookCollectionResource
 
 from twistedcaldav.test.util import TestCase
 
@@ -297,8 +297,10 @@
         backend will be initialized to match.
         """
         calDavFile = yield self.getResource("calendars/users/wsanchez/frobozz")
-        self.assertIsInstance(calDavFile, ProtoCalendarCollectionResource)
+        self.assertIsInstance(calDavFile, CalendarCollectionResource)
+        self.assertFalse(calDavFile.exists())
         yield calDavFile.createCalendarCollection()
+        self.assertTrue(calDavFile.exists())
         yield self.commit()
 
 
@@ -315,7 +317,7 @@
                 "calendars/users/wsanchez/%s" % (specialName,)
             )
             self.assertIdentical(
-                getattr(calDavFile, "_newStoreCalendar", None), None
+                getattr(calDavFile, "_newStoreObject", None), None
             )
         yield self.commit()
 
@@ -420,8 +422,10 @@
         initialized to match.
         """
         calDavFile = yield self.getResource("addressbooks/users/wsanchez/frobozz")
-        self.assertIsInstance(calDavFile, ProtoAddressBookCollectionResource)
+        self.assertIsInstance(calDavFile, AddressBookCollectionResource)
+        self.assertFalse(calDavFile.exists())
         yield calDavFile.createAddressBookCollection()
+        self.assertTrue(calDavFile.exists())
         yield self.commit()
         self.assertEquals(calDavFile._principalCollections,
                           frozenset([self.principalsResource]))

Modified: CalendarServer/trunk/txdav/caldav/datastore/file.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/file.py	2010-11-04 23:05:26 UTC (rev 6572)
+++ CalendarServer/trunk/txdav/caldav/datastore/file.py	2010-11-07 03:01:41 UTC (rev 6573)
@@ -85,40 +85,48 @@
 class CalendarHome(CommonHome):
     implements(ICalendarHome)
 
+    _topPath = "calendars"
+    _notifierPrefix = "CalDAV"
+
     def __init__(self, uid, path, calendarStore, transaction, notifier):
         super(CalendarHome, self).__init__(uid, path, calendarStore, transaction, notifier)
 
         self._childClass = Calendar
 
 
-    def calendarWithName(self, name):
+    createCalendarWithName = CommonHome.createChildWithName
+    removeCalendarWithName = CommonHome.removeChildWithName
+
+
+    def childWithName(self, name):
         if name in IGNORE_NAMES:
             # "dropbox" is a file storage area, not a calendar.
             return None
         else:
-            return self.childWithName(name)
+            return super(CalendarHome, self).childWithName(name)
 
+    calendarWithName = childWithName
 
-    createCalendarWithName = CommonHome.createChildWithName
-    removeCalendarWithName = CommonHome.removeChildWithName
 
-    def calendars(self):
+    def children(self):
         """
         Return a generator of the child resource objects.
         """
         for child in self.listCalendars():
             yield self.calendarWithName(child)
 
+    calendars = children
 
-    def listCalendars(self):
+    def listChildren(self):
         """
         Return a generator of the child resource names.
         """
-        for name in self.listChildren():
+        for name in super(CalendarHome, self).listChildren():
             if name in IGNORE_NAMES:
                 continue
             yield name
 
+    listCalendars = listChildren
 
 
     @inlineCallbacks
@@ -138,8 +146,7 @@
 
 
     def createdHome(self):
-        self.createCalendarWithName("calendar")
-        defaultCal = self.calendarWithName("calendar")
+        defaultCal = self.createCalendarWithName("calendar")
         props = defaultCal.properties()
         props[PropertyName(*ScheduleCalendarTransp.qname())] = ScheduleCalendarTransp(
             Opaque())
@@ -153,7 +160,7 @@
     """
     implements(ICalendar)
 
-    def __init__(self, name, calendarHome, notifier, realName=None):
+    def __init__(self, name, calendarHome, realName=None):
         """
         Initialize a calendar pointing at a path on disk.
 
@@ -168,8 +175,7 @@
         will eventually have on disk.
         @type realName: C{str}
         """
-        super(Calendar, self).__init__(name, calendarHome, notifier,
-            realName=realName)
+        super(Calendar, self).__init__(name, calendarHome, realName=realName)
 
         self._index = Index(self)
         self._invites = Invites(self)

Modified: CalendarServer/trunk/txdav/caldav/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/sql.py	2010-11-04 23:05:26 UTC (rev 6572)
+++ CalendarServer/trunk/txdav/caldav/datastore/sql.py	2010-11-07 03:01:41 UTC (rev 6573)
@@ -66,15 +66,16 @@
 
     implements(ICalendarHome)
 
+    _homeTable = CALENDAR_HOME_TABLE
+    _homeMetaDataTable = CALENDAR_HOME_METADATA_TABLE
+    _childTable = CALENDAR_TABLE
+    _bindTable = CALENDAR_BIND_TABLE
+    _notifierPrefix = "CalDAV"
+    _revisionsTable = CALENDAR_OBJECT_REVISIONS_TABLE
+
     def __init__(self, transaction, ownerUID, notifier):
 
-        self._homeTable = CALENDAR_HOME_TABLE
-        self._homeMetaDataTable = CALENDAR_HOME_METADATA_TABLE
         self._childClass = Calendar
-        self._childTable = CALENDAR_TABLE
-        self._bindTable = CALENDAR_BIND_TABLE
-        self._revisionsTable = CALENDAR_OBJECT_REVISIONS_TABLE
-
         super(CalendarHome, self).__init__(transaction, ownerUID, notifier)
         self._shares = SQLLegacyCalendarShares(self)
 
@@ -99,8 +100,7 @@
 
     @inlineCallbacks
     def createdHome(self):
-        yield self.createCalendarWithName("calendar")
-        defaultCal = yield self.calendarWithName("calendar")
+        defaultCal = yield self.createCalendarWithName("calendar")
         props = defaultCal.properties()
         props[PropertyName(*ScheduleCalendarTransp.qname())] = ScheduleCalendarTransp(
             Opaque())
@@ -114,7 +114,12 @@
     """
     implements(ICalendar)
 
-    def __init__(self, home, name, resourceID, notifier):
+    _bindTable = CALENDAR_BIND_TABLE
+    _homeChildTable = CALENDAR_TABLE
+    _revisionsTable = CALENDAR_OBJECT_REVISIONS_TABLE
+    _objectTable = CALENDAR_OBJECT_TABLE
+
+    def __init__(self, home, name, resourceID):
         """
         Initialize a calendar pointing at a record in a database.
 
@@ -124,7 +129,7 @@
         @param home: the home containing this calendar.
         @type home: L{CalendarHome}
         """
-        super(Calendar, self).__init__(home, name, resourceID, notifier)
+        super(Calendar, self).__init__(home, name, resourceID)
 
         if name == 'inbox':
             self._index = PostgresLegacyInboxIndexEmulator(self)
@@ -132,10 +137,6 @@
             self._index = PostgresLegacyIndexEmulator(self)
         self._invites = SQLLegacyCalendarInvites(self)
         self._objectResourceClass = CalendarObject
-        self._bindTable = CALENDAR_BIND_TABLE
-        self._homeChildTable = CALENDAR_TABLE
-        self._revisionsTable = CALENDAR_OBJECT_REVISIONS_TABLE
-        self._objectTable = CALENDAR_OBJECT_TABLE
 
 
     @property

Modified: CalendarServer/trunk/txdav/carddav/datastore/file.py
===================================================================
--- CalendarServer/trunk/txdav/carddav/datastore/file.py	2010-11-04 23:05:26 UTC (rev 6572)
+++ CalendarServer/trunk/txdav/carddav/datastore/file.py	2010-11-07 03:01:41 UTC (rev 6573)
@@ -64,6 +64,9 @@
 
     implements(IAddressBookHome)
 
+    _topPath = "addressbooks"
+    _notifierPrefix = "CardDAV"
+
     def __init__(self, uid, path, addressbookStore, transaction, notifier):
         super(AddressBookHome, self).__init__(uid, path, addressbookStore, transaction, notifier)
 
@@ -88,7 +91,7 @@
     """
     implements(IAddressBook)
 
-    def __init__(self, name, addressbookHome, notifier, realName=None):
+    def __init__(self, name, addressbookHome, realName=None):
         """
         Initialize an addressbook pointing at a path on disk.
 
@@ -104,8 +107,7 @@
         @type realName: C{str}
         """
         
-        super(AddressBook, self).__init__(name, addressbookHome, notifier,
-            realName=realName)
+        super(AddressBook, self).__init__(name, addressbookHome, realName=realName)
 
         self._index = Index(self)
         self._invites = Invites(self)

Modified: CalendarServer/trunk/txdav/carddav/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/carddav/datastore/sql.py	2010-11-04 23:05:26 UTC (rev 6572)
+++ CalendarServer/trunk/txdav/carddav/datastore/sql.py	2010-11-07 03:01:41 UTC (rev 6573)
@@ -58,15 +58,16 @@
 
     implements(IAddressBookHome)
 
+    _homeTable = ADDRESSBOOK_HOME_TABLE
+    _homeMetaDataTable = ADDRESSBOOK_HOME_METADATA_TABLE
+    _childTable = ADDRESSBOOK_TABLE
+    _bindTable = ADDRESSBOOK_BIND_TABLE
+    _notifierPrefix = "CardDAV"
+    _revisionsTable = ADDRESSBOOK_OBJECT_REVISIONS_TABLE
+
     def __init__(self, transaction, ownerUID, notifier):
 
-        self._homeTable = ADDRESSBOOK_HOME_TABLE
-        self._homeMetaDataTable = ADDRESSBOOK_HOME_METADATA_TABLE
         self._childClass = AddressBook
-        self._childTable = ADDRESSBOOK_TABLE
-        self._bindTable = ADDRESSBOOK_BIND_TABLE
-        self._revisionsTable = ADDRESSBOOK_OBJECT_REVISIONS_TABLE
-
         super(AddressBookHome, self).__init__(transaction, ownerUID, notifier)
         self._shares = SQLLegacyAddressBookShares(self)
 
@@ -89,7 +90,12 @@
     """
     implements(IAddressBook)
 
-    def __init__(self, home, name, resourceID, notifier):
+    _bindTable = ADDRESSBOOK_BIND_TABLE
+    _homeChildTable = ADDRESSBOOK_TABLE
+    _revisionsTable = ADDRESSBOOK_OBJECT_REVISIONS_TABLE
+    _objectTable = ADDRESSBOOK_OBJECT_TABLE
+
+    def __init__(self, home, name, resourceID):
         """
         Initialize an addressbook pointing at a path on disk.
 
@@ -105,15 +111,11 @@
         @type realName: C{str}
         """
 
-        super(AddressBook, self).__init__(home, name, resourceID, notifier)
+        super(AddressBook, self).__init__(home, name, resourceID)
 
         self._index = PostgresLegacyABIndexEmulator(self)
         self._invites = SQLLegacyAddressBookInvites(self)
         self._objectResourceClass = AddressBookObject
-        self._bindTable = ADDRESSBOOK_BIND_TABLE
-        self._homeChildTable = ADDRESSBOOK_TABLE
-        self._revisionsTable = ADDRESSBOOK_OBJECT_REVISIONS_TABLE
-        self._objectTable = ADDRESSBOOK_OBJECT_TABLE
 
 
     @property

Modified: CalendarServer/trunk/txdav/common/datastore/file.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/file.py	2010-11-04 23:05:26 UTC (rev 6572)
+++ CalendarServer/trunk/txdav/common/datastore/file.py	2010-11-07 03:01:41 UTC (rev 6573)
@@ -19,6 +19,7 @@
 Common utility functions for a file based datastore.
 """
 
+from twext.internet.decorate import memoizedKey
 from twext.python.log import LoggingMixIn
 from twext.web2.dav.element.rfc2518 import ResourceType, GETContentType, HRef
 from twext.web2.dav.element.rfc5842 import ResourceID
@@ -171,10 +172,9 @@
         from txdav.carddav.datastore.file import AddressBookHome
 
         super(CommonStoreTransaction, self).__init__(dataStore, name)
-        self._homes = {}
-        self._homes[ECALENDARTYPE] = {}
-        self._homes[EADDRESSBOOKTYPE] = {}
-        self._notifications = {}
+        self._calendarHomes = {}
+        self._addressbookHomes = {}
+        self._notificationHomes = {}
         self._notifierFactory = notifierFactory
         self._migrating = migrating
 
@@ -192,23 +192,71 @@
         CommonStoreTransaction._homeClass[EADDRESSBOOKTYPE] = AddressBookHome
 
 
+    @memoizedKey('uid', '_calendarHomes')
     def calendarHomeWithUID(self, uid, create=False):
         return self.homeWithUID(ECALENDARTYPE, uid, create=create)
 
+    @memoizedKey("uid", "_addressbookHomes")
     def addressbookHomeWithUID(self, uid, create=False):
         return self.homeWithUID(EADDRESSBOOKTYPE, uid, create=create)
 
     def homeWithUID(self, storeType, uid, create=False):
-        if (uid, self) in self._homes[storeType]:
-            return self._homes[storeType][(uid, self)]
-
         if uid.startswith("."):
             return None
 
+        if storeType not in (ECALENDARTYPE, EADDRESSBOOKTYPE):
+            raise RuntimeError("Unknown home type.")
+
+        return self._homeClass[storeType].homeWithUID(self, uid, create, storeType == ECALENDARTYPE)
+
+    @memoizedKey("uid", "_notificationHomes")
+    def notificationsWithUID(self, uid, home=None):
+
+        if home is None:
+            home = self.homeWithUID(self._notificationHomeType, uid, create=True)
+        return NotificationCollection.notificationsFromHome(self, home)
+
+
+class StubResource(object):
+    """
+    Just enough resource to keep the shared sql DB classes going.
+    """
+    def __init__(self, commonHome):
+        self._commonHome = commonHome
+
+
+    @property
+    def fp(self):
+        return self._commonHome._path
+
+
+
+class CommonHome(FileMetaDataMixin, LoggingMixIn):
+
+    # All these need to be initialized by derived classes for each store type
+    _childClass = None
+    _topPath = None
+    _notifierPrefix = None
+
+    def __init__(self, uid, path, dataStore, transaction, notifier):
+        self._dataStore = dataStore
+        self._uid = uid
+        self._path = path
+        self._transaction = transaction
+        self._notifier = notifier
+        self._shares = SharedCollectionsDatabase(StubResource(self))
+        self._newChildren = {}
+        self._removedChildren = set()
+        self._cachedChildren = {}
+
+
+    @classmethod
+    def homeWithUID(cls, txn, uid, create=False, withNotifications=False):
+
         assert len(uid) >= 4
 
         childPathSegments = []
-        childPathSegments.append(self._dataStore._path.child(TOPPATHS[storeType]))
+        childPathSegments.append(txn._dataStore._path.child(cls._topPath))
         childPathSegments.append(childPathSegments[-1].child(UIDPATH))
         childPathSegments.append(childPathSegments[-1].child(uid[0:2]))
         childPathSegments.append(childPathSegments[-1].child(uid[2:4]))
@@ -241,111 +289,27 @@
                     # do this _after_ all other file operations
                     home._path = childPath
                     return lambda : None
-                self.addOperation(do, "create home UID %r" % (uid,))
+                txn.addOperation(do, "create home UID %r" % (uid,))
 
         elif not childPath.isdir():
             return None
         else:
             homePath = childPath
 
-        if self._notifierFactory:
-            notifier = self._notifierFactory.newNotifier(id=uid,
-                prefix=NotifierPrefixes[storeType])
+        if txn._notifierFactory:
+            notifier = txn._notifierFactory.newNotifier(id=uid,
+                prefix=cls._notifierPrefix)
         else:
             notifier = None
 
-        home = self._homeClass[storeType](uid, homePath, self._dataStore, self,
-            notifier)
-        self._homes[storeType][(uid, self)] = home
+        home = cls(uid, homePath, txn._dataStore, txn, notifier)
         if creating:
             home.createdHome()
+            if withNotifications:
+                txn.notificationsWithUID(uid, home)
 
-            # Create notification collection
-            if storeType == ECALENDARTYPE:
-                self.notificationsWithUID(uid)
         return home
 
-    def notificationsWithUID(self, uid):
-
-        if (uid, self) in self._notifications:
-            return self._notifications[(uid, self)]
-
-        home = self.homeWithUID(self._notificationHomeType, uid, create=True)
-        if (uid, self) in self._notifications:
-            return self._notifications[(uid, self)]
-
-        notificationCollectionName = "notification"
-        if not home._path.child(notificationCollectionName).isdir():
-            notifications = self._createNotificationCollection(home, notificationCollectionName)
-        else:
-            notifications = NotificationCollection(notificationCollectionName, home)
-        self._notifications[(uid, self)] = notifications
-        return notifications
-
-
-    def _createNotificationCollection(self, home, collectionName):
-        # FIXME: this is a near-copy of CommonHome.createChildWithName.
-        temporary = hidden(home._path.child(collectionName).temporarySibling())
-        temporary.createDirectory()
-        temporaryName = temporary.basename()
-
-        c = NotificationCollection(temporary.basename(), home)
-
-        def do():
-            childPath = home._path.child(collectionName)
-            temporary = childPath.sibling(temporaryName)
-            try:
-                props = c.properties()
-                temporary.moveTo(childPath)
-                c._name = collectionName
-                # FIXME: _lots_ of duplication of work here.
-                props.flush()
-            except (IOError, OSError), e:
-                if e.errno == EEXIST and childPath.isdir():
-                    raise HomeChildNameAlreadyExistsError(collectionName)
-                raise
-            # FIXME: direct tests, undo for index creation
-            # Return undo
-            return lambda: home._path.child(collectionName).remove()
-
-        self.addOperation(do, "create notification child %r" %
-                          (collectionName,))
-        props = c.properties()
-        props[PropertyName(*ResourceType.qname())] = c.resourceType()
-        return c
-
-
-
-class StubResource(object):
-    """
-    Just enough resource to keep the shared sql DB classes going.
-    """
-    def __init__(self, commonHome):
-        self._commonHome = commonHome
-
-
-    @property
-    def fp(self):
-        return self._commonHome._path
-
-
-
-class CommonHome(FileMetaDataMixin, LoggingMixIn):
-
-    _childClass = None
-
-    def __init__(self, uid, path, dataStore, transaction, notifier):
-        self._dataStore = dataStore
-        self._uid = uid
-        self._path = path
-        self._transaction = transaction
-        self._notifier = notifier
-        self._shares = SharedCollectionsDatabase(StubResource(self))
-        self._newChildren = {}
-        self._removedChildren = set()
-        self._cachedChildren = {}
-
-
     def __repr__(self):
         return "<%s: %s>" % (self.__class__.__name__, self._path)
 
@@ -400,18 +364,10 @@
         if name.startswith("."):
             return None
 
-        childPath = self._path.child(name)
-        if childPath.isdir():
-            if self._notifier:
-                childID = "%s/%s" % (self.uid(), name)
-                notifier = self._notifier.clone(label="collection", id=childID)
-            else:
-                notifier = None
-            existingChild = self._childClass(name, self, notifier)
-            self._cachedChildren[name] = existingChild
-            return existingChild
-        else:
-            return None
+        child = self._childClass.objectWithName(self, name)
+        if child is not None:
+            self._cachedChildren[name] = child
+        return child
 
 
     @writeOperation
@@ -432,11 +388,7 @@
 
         # FIXME: some way to roll this back.
 
-        if self._notifier:
-            notifier = self._notifier.clone(label="collection", id=name)
-        else:
-            notifier = None
-        c = self._newChildren[name] = self._childClass(temporary.basename(), self, notifier, realName=name)
+        c = self._newChildren[name] = self._childClass(temporary.basename(), self, realName=name)
         c.retrieveOldIndex().create()
         def do():
             childPath = self._path.child(name)
@@ -461,8 +413,8 @@
         self.createdChild(c)
 
         self.notifyChanged()
+        return c
 
-
     def createdChild(self, child):
         pass
 
@@ -473,46 +425,14 @@
             raise NoSuchHomeChildError(name)
 
         child = self.childWithName(name)
-        if not child:
+        if child is None:
             raise NoSuchHomeChildError()
 
-        self._removedChildren.add(name)
+        try:
+            child.remove()
+        finally:
+            self._removedChildren.add(name)
 
-        def do(transaction=self._transaction):
-            childPath = self._path.child(name)
-            for i in xrange(1000):
-                trash = childPath.sibling("._del_%s_%d" % (childPath.basename(), i))
-                if not trash.exists():
-                    break
-            else:
-                raise InternalDataStoreError("Unable to create trash target for child at %s" % (childPath,))
-
-            try:
-                childPath.moveTo(trash)
-            except (IOError, OSError), e:
-                if e.errno == ENOENT:
-                    raise NoSuchHomeChildError(name)
-                raise
-
-            def cleanup():
-                try:
-                    trash.remove()
-                except Exception, e:
-                    self.log_error("Unable to delete trashed child at %s: %s" % (trash.fp, e))
-
-            transaction.addOperation(cleanup, "remove child backup %r" % (name,))
-            def undo():
-                trash.moveTo(childPath)
-
-            return undo
-
-        # FIXME: direct tests
-        self._transaction.addOperation(
-            do, "prepare child remove %r" % (name,)
-        )
-
-        child.notifyChanged()
-
     @inlineCallbacks
     def syncToken(self):
         
@@ -587,7 +507,7 @@
 
     _objectResourceClass = None
 
-    def __init__(self, name, home, notifier, realName=None):
+    def __init__(self, name, home, realName=None):
         """
         Initialize an home child pointing at a path on disk.
 
@@ -604,7 +524,6 @@
         """
         self._name = name
         self._home = home
-        self._notifier = notifier
         self._transaction = home._transaction
         self._newObjectResources = {}
         self._cachedObjectResources = {}
@@ -613,7 +532,18 @@
         self._invites = None # Derived classes need to set this
         self._renamedName = realName
 
+        if home._notifier:
+            childID = "%s/%s" % (home.uid(), name)
+            notifier = home._notifier.clone(label="collection", id=childID)
+        else:
+            notifier = None
+        self._notifier = notifier
 
+
+    @classmethod
+    def objectWithName(cls, home, name):
+        return cls(name, home) if home._path.child(name).isdir() else None
+
     @property
     def _path(self):
         return self._home._path.child(self._name)
@@ -663,6 +593,44 @@
 
         self.notifyChanged()
 
+    @writeOperation
+    def remove(self):
+
+        def do(transaction=self._transaction):
+            childPath = self._path
+            for i in xrange(1000):
+                trash = childPath.sibling("._del_%s_%d" % (childPath.basename(), i))
+                if not trash.exists():
+                    break
+            else:
+                raise InternalDataStoreError("Unable to create trash target for child at %s" % (childPath,))
+
+            try:
+                childPath.moveTo(trash)
+            except (IOError, OSError), e:
+                if e.errno == ENOENT:
+                    raise NoSuchHomeChildError(self._name)
+                raise
+
+            def cleanup():
+                try:
+                    trash.remove()
+                except Exception, e:
+                    self.log_error("Unable to delete trashed child at %s: %s" % (trash.fp, e))
+
+            self._transaction.addOperation(cleanup, "remove child backup %r" % (self._name,))
+            def undo():
+                trash.moveTo(childPath)
+
+            return undo
+
+        # FIXME: direct tests
+        self._transaction.addOperation(
+            do, "prepare child remove %r" % (self._name,)
+        )
+
+        self.notifyChanged()
+
     def ownerHome(self):
         return self._home
 
@@ -954,6 +922,49 @@
         self._invites = None
         self._objectResourceClass = NotificationObject
 
+    @classmethod
+    def notificationsFromHome(cls, txn, home):
+
+        notificationCollectionName = "notification"
+        if not home._path.child(notificationCollectionName).isdir():
+            notifications = cls._create(txn, home, notificationCollectionName)
+        else:
+            notifications = cls(notificationCollectionName, home)
+        return notifications
+
+
+    @classmethod
+    def _create(cls, txn, home, collectionName):
+        # FIXME: this is a near-copy of CommonHome.createChildWithName.
+        temporary = hidden(home._path.child(collectionName).temporarySibling())
+        temporary.createDirectory()
+        temporaryName = temporary.basename()
+
+        c = cls(temporary.basename(), home)
+
+        def do():
+            childPath = home._path.child(collectionName)
+            temporary = childPath.sibling(temporaryName)
+            try:
+                props = c.properties()
+                temporary.moveTo(childPath)
+                c._name = collectionName
+                # FIXME: _lots_ of duplication of work here.
+                props.flush()
+            except (IOError, OSError), e:
+                if e.errno == EEXIST and childPath.isdir():
+                    raise HomeChildNameAlreadyExistsError(collectionName)
+                raise
+            # FIXME: direct tests, undo for index creation
+            # Return undo
+            return lambda: home._path.child(collectionName).remove()
+
+        txn.addOperation(do, "create notification child %r" %
+                          (collectionName,))
+        props = c.properties()
+        props[PropertyName(*ResourceType.qname())] = c.resourceType()
+        return c
+
     def resourceType(self):
         return ResourceType.notification #@UndefinedVariable
 

Modified: CalendarServer/trunk/txdav/common/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql.py	2010-11-04 23:05:26 UTC (rev 6572)
+++ CalendarServer/trunk/txdav/common/datastore/sql.py	2010-11-07 03:01:41 UTC (rev 6573)
@@ -48,10 +48,8 @@
 
 from txdav.carddav.iaddressbookstore import IAddressBookTransaction
 
-from txdav.common.datastore.sql_tables import CALENDAR_HOME_TABLE, \
-    ADDRESSBOOK_HOME_TABLE, NOTIFICATION_HOME_TABLE, _BIND_MODE_OWN, \
-    _BIND_STATUS_ACCEPTED, NOTIFICATION_OBJECT_REVISIONS_TABLE,\
-    CALENDAR_HOME_METADATA_TABLE, ADDRESSBOOK_HOME_METADATA_TABLE
+from txdav.common.datastore.sql_tables import NOTIFICATION_HOME_TABLE, _BIND_MODE_OWN, \
+    _BIND_STATUS_ACCEPTED, NOTIFICATION_OBJECT_REVISIONS_TABLE
 from txdav.common.icommondatastore import HomeChildNameNotAllowedError, \
     HomeChildNameAlreadyExistsError, NoSuchHomeChildError, \
     ObjectResourceNameNotAllowedError, ObjectResourceNameAlreadyExistsError, \
@@ -132,8 +130,6 @@
     Transaction implementation for SQL database.
     """
     _homeClass = {}
-    _homeTable = {}
-    _homeMetaDataTable = {}
 
     id = 0
 
@@ -163,10 +159,6 @@
         from txdav.carddav.datastore.sql import AddressBookHome
         CommonStoreTransaction._homeClass[ECALENDARTYPE] = CalendarHome
         CommonStoreTransaction._homeClass[EADDRESSBOOKTYPE] = AddressBookHome
-        CommonStoreTransaction._homeTable[ECALENDARTYPE] = CALENDAR_HOME_TABLE
-        CommonStoreTransaction._homeTable[EADDRESSBOOKTYPE] = ADDRESSBOOK_HOME_TABLE
-        CommonStoreTransaction._homeMetaDataTable[ECALENDARTYPE] = CALENDAR_HOME_METADATA_TABLE
-        CommonStoreTransaction._homeMetaDataTable[EADDRESSBOOKTYPE] = ADDRESSBOOK_HOME_METADATA_TABLE
         self._sqlTxn = sqlTxn
 
 
@@ -188,105 +180,18 @@
         return self.homeWithUID(EADDRESSBOOKTYPE, uid, create=create)
 
 
-    @inlineCallbacks
     def homeWithUID(self, storeType, uid, create=False):
         if storeType not in (ECALENDARTYPE, EADDRESSBOOKTYPE):
             raise RuntimeError("Unknown home type.")
 
-        if self._notifierFactory:
-            notifier = self._notifierFactory.newNotifier(
-                id=uid, prefix=NotifierPrefixes[storeType]
-            )
-        else:
-            notifier = None
-        homeObject = self._homeClass[storeType](self, uid, notifier)
-        homeObject = (yield homeObject.initFromStore())
-        if homeObject is not None:
-            returnValue(homeObject)
-        else:
-            if not create:
-                returnValue(None)
-            # Need to lock to prevent race condition
-            # FIXME: this is an entire table lock - ideally we want a row lock
-            # but the row does not exist yet. However, the "exclusive" mode
-            # does allow concurrent reads so the only thing we block is other
-            # attempts to provision a home, which is not too bad
-            yield self.execSQL(
-                "lock %(name)s in exclusive mode" % CommonStoreTransaction._homeTable[storeType],
-            )
-            # Now test again
-            exists = yield self.execSQL(
-                "select %(column_RESOURCE_ID)s from %(name)s"
-                " where %(column_OWNER_UID)s = %%s" % CommonStoreTransaction._homeTable[storeType],
-                [uid]
-            )
-            if not exists:
-                resourceid = (yield self.execSQL("""
-                    insert into %(name)s (%(column_OWNER_UID)s) values (%%s)
-                    returning %(column_RESOURCE_ID)s
-                    """ % CommonStoreTransaction._homeTable[storeType],
-                    [uid]
-                ))[0][0]
-                yield self.execSQL(
-                    "insert into %(name)s (%(column_RESOURCE_ID)s) values (%%s)" % CommonStoreTransaction._homeMetaDataTable[storeType],
-                    [resourceid]
-                )
-            home = yield self.homeWithUID(storeType, uid)
-            if not exists:
-                yield home.createdHome()
-            returnValue(home)
+        return self._homeClass[storeType].homeWithUID(self, uid, create)
 
-    def createHomeWithUIDLocked(self, storeType, uid):
-        # Need to lock to prevent race condition
-        # FIXME: this is an entire table lock - ideally we want a row lock
-        # but the row does not exist yet. However, the "exclusive" mode
-        # does allow concurrent reads so the only thing we block is other
-        # attempts to provision a home, which is not too bad
-
-        if storeType not in (ECALENDARTYPE, EADDRESSBOOKTYPE):
-            raise RuntimeError("Unknown home type.")
-
-        yield self.execSQL(
-            "lock %(name)s in exclusive mode" % CommonStoreTransaction._homeTable[storeType],
-        )
-        # Now test again
-        exists = yield self.execSQL(
-            "select %(column_RESOURCE_ID)s from %(name)s"
-            " where %(column_OWNER_UID)s = %%s" % CommonStoreTransaction._homeTable[storeType],
-            [uid]
-        )
-        if not exists:
-            yield self.execSQL(
-                "insert into %(name)s (%(column_OWNER_UID)s) values (%%s)" % CommonStoreTransaction._homeTable[storeType],
-                [uid]
-            )
-
     @memoizedKey("uid", "_notificationHomes")
-    @inlineCallbacks
     def notificationsWithUID(self, uid):
         """
         Implement notificationsWithUID.
         """
-        rows = yield self.execSQL(
-            """
-            select %(column_RESOURCE_ID)s from %(name)s where
-            %(column_OWNER_UID)s = %%s
-            """ % NOTIFICATION_HOME_TABLE, [uid]
-        )
-        if rows:
-            resourceID = rows[0][0]
-            created = False
-        else:
-            resourceID = str((yield self.execSQL(
-                "insert into %(name)s (%(column_OWNER_UID)s) values (%%s) returning %(column_RESOURCE_ID)s" % NOTIFICATION_HOME_TABLE,
-                [uid]
-            ))[0][0])
-            created = True
-        collection = NotificationCollection(self, uid, resourceID)
-        yield collection._loadPropertyStore()
-        if created:
-            yield collection._initSyncToken()
-        returnValue(collection)
+        return NotificationCollection.notificationsWithUID(self, uid)
 
 
     def postCommit(self, operation):
@@ -324,11 +229,13 @@
 
 class CommonHome(LoggingMixIn):
 
+    # All these need to be initialized by derived classes for each store type
     _homeTable = None
     _homeMetaDataTable = None
     _childClass = None
     _childTable = None
     _bindTable = None
+    _notifierPrefix = None
     _revisionsTable = None
     _notificationRevisionsTable = NOTIFICATION_OBJECT_REVISIONS_TABLE
 
@@ -351,7 +258,7 @@
     @inlineCallbacks
     def initFromStore(self):
         """
-        Initialise this object from the store. We read in and cache all the extra metadata
+        Initialize this object from the store. We read in and cache all the extra meta-data
         from the DB to avoid having to do DB queries for those individually later.
         """
 
@@ -367,6 +274,53 @@
         else:
             returnValue(None)
 
+    @classmethod
+    @inlineCallbacks
+    def homeWithUID(cls, txn, uid, create=False):
+
+        if txn._notifierFactory:
+            notifier = txn._notifierFactory.newNotifier(
+                id=uid, prefix=cls._notifierPrefix
+            )
+        else:
+            notifier = None
+        homeObject = cls(txn, uid, notifier)
+        homeObject = (yield homeObject.initFromStore())
+        if homeObject is not None:
+            returnValue(homeObject)
+        else:
+            if not create:
+                returnValue(None)
+            # Need to lock to prevent race condition
+            # FIXME: this is an entire table lock - ideally we want a row lock
+            # but the row does not exist yet. However, the "exclusive" mode
+            # does allow concurrent reads so the only thing we block is other
+            # attempts to provision a home, which is not too bad
+            yield txn.execSQL(
+                "lock %(name)s in exclusive mode" % cls._homeTable,
+            )
+            # Now test again
+            exists = yield txn.execSQL(
+                "select %(column_RESOURCE_ID)s from %(name)s"
+                " where %(column_OWNER_UID)s = %%s" % cls._homeTable,
+                [uid]
+            )
+            if not exists:
+                resourceid = (yield txn.execSQL("""
+                    insert into %(name)s (%(column_OWNER_UID)s) values (%%s)
+                    returning %(column_RESOURCE_ID)s
+                    """ % cls._homeTable,
+                    [uid]
+                ))[0][0]
+                yield txn.execSQL(
+                    "insert into %(name)s (%(column_RESOURCE_ID)s) values (%%s)" % cls._homeMetaDataTable,
+                    [resourceid]
+                )
+            home = yield cls.homeWithUID(txn, uid)
+            if not exists:
+                yield home.createdHome()
+            returnValue(home)
+
     def __repr__(self):
         return "<%s: %s>" % (self.__class__.__name__, self._resourceID)
 
@@ -412,7 +366,7 @@
 
         @return: an iterable of C{str}s.
         """
-        return self._listChildren(owned=True)
+        return self._childClass.listObjects(self, owned=True)
 
 
     def listSharedChildren(self):
@@ -421,42 +375,9 @@
 
         @return: an iterable of C{str}s.
         """
-        return self._listChildren(owned=False)
+        return self._childClass.listObjects(self, owned=False)
 
 
-    @inlineCallbacks
-    def _listChildren(self, owned):
-        """
-        Retrieve the names of the children in this home.
-
-        @return: an iterable of C{str}s.
-        """
-        # FIXME: not specified on the interface or exercised by the tests, but
-        # required by clients of the implementation!
-        if owned:
-            rows = yield self._txn.execSQL("""
-                select %(column_RESOURCE_NAME)s from %(name)s
-                where
-                  %(column_HOME_RESOURCE_ID)s = %%s and
-                  %(column_BIND_MODE)s = %%s
-                """ % self._bindTable,
-                [self._resourceID, _BIND_MODE_OWN]
-            )
-        else:
-            rows = yield self._txn.execSQL("""
-                select %(column_RESOURCE_NAME)s from %(name)s
-                where
-                  %(column_HOME_RESOURCE_ID)s = %%s and
-                  %(column_BIND_MODE)s != %%s and
-                  %(column_RESOURCE_NAME)s is not null
-                """ % self._bindTable,
-                [self._resourceID, _BIND_MODE_OWN]
-            )
-
-        names = [row[0] for row in rows]
-        returnValue(names)
-
-
     @memoizedKey("name", "_children")
     def childWithName(self, name):
         """
@@ -466,7 +387,7 @@
         @param name: a string.
         @return: an L{ICalendar} or C{None} if no such child exists.
         """
-        return self._childWithName(name, owned=True)
+        return self._childClass.objectWithName(self, name, owned=True)
 
 
     @memoizedKey("name", "_sharedChildren")
@@ -485,104 +406,18 @@
         @return: an L{ICalendar} or C{None} if no such child
             exists.
         """
-        return self._childWithName(name, owned=False)
+        return self._childClass.objectWithName(self, name, owned=False)
 
 
     @inlineCallbacks
-    def _childWithName(self, name, owned):
-        """
-        Retrieve the child with the given C{name} contained in this
-        home.
-
-        @param name: a string.
-        @return: an L{ICalendar} or C{None} if no such child
-            exists.
-        """
-
-        if owned:
-            data = yield self._txn.execSQL("""
-                select %(column_RESOURCE_ID)s from %(name)s
-                where
-                  %(column_RESOURCE_NAME)s = %%s and
-                  %(column_HOME_RESOURCE_ID)s = %%s and
-                  %(column_BIND_MODE)s = %%s
-                """ % self._bindTable,
-                [
-                    name,
-                    self._resourceID,
-                    _BIND_MODE_OWN
-                ]
-            )
-        else:
-            data = yield self._txn.execSQL("""
-                select %(column_RESOURCE_ID)s from %(name)s
-                where
-                  %(column_RESOURCE_NAME)s = %%s and
-                  %(column_HOME_RESOURCE_ID)s = %%s and
-                  %(column_BIND_MODE)s != %%s
-                """ % self._bindTable,
-                [
-                    name,
-                    self._resourceID,
-                    _BIND_MODE_OWN
-                ]
-            )
-
-        if not data:
-            returnValue(None)
-        resourceID = data[0][0]
-        if self._notifier:
-            childID = "%s/%s" % (self.uid(), name)
-            notifier = self._notifier.clone(label="collection", id=childID)
-        else:
-            notifier = None
-        child = self._childClass(self, name, resourceID, notifier)
-        yield child.initFromStore()
-        returnValue(child)
-
-
-    @inlineCallbacks
     def createChildWithName(self, name):
         if name.startswith("."):
             raise HomeChildNameNotAllowedError(name)
 
-        rows = yield self._txn.execSQL(
-            "select %(column_RESOURCE_NAME)s from %(name)s where "
-            "%(column_RESOURCE_NAME)s = %%s AND "
-            "%(column_HOME_RESOURCE_ID)s = %%s" % self._bindTable,
-            [name, self._resourceID]
-        )
-        if rows:
-            raise HomeChildNameAlreadyExistsError(name)
+        yield self._childClass.create(self, name)
+        child = (yield self.childWithName(name))
+        returnValue(child)
 
-        rows = yield self._txn.execSQL("select nextval('RESOURCE_ID_SEQ')")
-        resourceID = rows[0][0]
-        yield self._txn.execSQL(
-            "insert into %(name)s (%(column_RESOURCE_ID)s) values "
-            "(%%s)" % self._childTable,
-            [resourceID])
-
-        yield self._txn.execSQL("""
-            insert into %(name)s (
-                %(column_HOME_RESOURCE_ID)s,
-                %(column_RESOURCE_ID)s, %(column_RESOURCE_NAME)s, %(column_BIND_MODE)s,
-                %(column_SEEN_BY_OWNER)s, %(column_SEEN_BY_SHAREE)s, %(column_BIND_STATUS)s) values (
-            %%s, %%s, %%s, %%s, %%s, %%s, %%s)
-            """ % self._bindTable,
-            [self._resourceID, resourceID, name, _BIND_MODE_OWN, True, True,
-             _BIND_STATUS_ACCEPTED]
-        )
-
-        newChild = yield self.childWithName(name)
-        newChild.properties()[
-            PropertyName.fromElement(ResourceType)
-        ] = newChild.resourceType()
-        yield newChild._initSyncToken()
-        self.createdChild(newChild)
-
-        self.notifyChanged()
-
-
     def createdChild(self, child):
         pass
 
@@ -590,22 +425,15 @@
     @inlineCallbacks
     def removeChildWithName(self, name):
         child = yield self.childWithName(name)
-        if not child:
+        if child is None:
             raise NoSuchHomeChildError()
-        yield child._deletedSyncToken()
 
         try:
-            yield self._txn.execSQL(
-                "delete from %(name)s where %(column_RESOURCE_ID)s = %%s" % self._childTable,
-                [child._resourceID],
-                raiseOnZeroRowCount=NoSuchHomeChildError
-            )
+            yield child.remove()
         finally:
             self._children.pop(name, None)
 
-        child.notifyChanged()
 
-
     @inlineCallbacks
     def syncToken(self):
         revision = (yield self._txn.execSQL(
@@ -822,20 +650,159 @@
     _revisionsTable = None
     _objectTable = None
 
-    def __init__(self, home, name, resourceID, notifier):
+    def __init__(self, home, name, resourceID):
         self._home = home
         self._name = name
         self._resourceID = resourceID
         self._created = None
         self._modified = None
         self._objects = {}
+
+        if home._notifier:
+            childID = "%s/%s" % (home.uid(), name)
+            notifier = home._notifier.clone(label="collection", id=childID)
+        else:
+            notifier = None
         self._notifier = notifier
 
         self._index = None  # Derived classes need to set this
         self._invites = None # Derived classes need to set this
 
+    @classmethod
+    @inlineCallbacks
+    def listObjects(cls, home, owned):
+        """
+        Retrieve the names of the children that exist in this home.
 
+        @return: an iterable of C{str}s.
+        """
+        # FIXME: not specified on the interface or exercised by the tests, but
+        # required by clients of the implementation!
+        if owned:
+            rows = yield home._txn.execSQL("""
+                select %(column_RESOURCE_NAME)s from %(name)s
+                where
+                  %(column_HOME_RESOURCE_ID)s = %%s and
+                  %(column_BIND_MODE)s = %%s
+                """ % cls._bindTable,
+                [home._resourceID, _BIND_MODE_OWN]
+            )
+        else:
+            rows = yield home._txn.execSQL("""
+                select %(column_RESOURCE_NAME)s from %(name)s
+                where
+                  %(column_HOME_RESOURCE_ID)s = %%s and
+                  %(column_BIND_MODE)s != %%s and
+                  %(column_RESOURCE_NAME)s is not null
+                """ % cls._bindTable,
+                [home._resourceID, _BIND_MODE_OWN]
+            )
+
+        names = [row[0] for row in rows]
+        returnValue(names)
+
+    @classmethod
     @inlineCallbacks
+    def objectWithName(cls, home, name, owned):
+        """
+        Retrieve the child with the given C{name} contained in this
+        C{home}.
+
+        @param home: a L{CommonHome}.
+        @param name: a string.
+        @param owned: a boolean - whether or not to get a shared child
+        @param mustExist: a boolean - if False return and empty object
+        @return: an L{CommonHomChild} or C{None} if no such child
+            exists.
+        """
+
+        if owned:
+            data = yield home._txn.execSQL("""
+                select %(column_RESOURCE_ID)s from %(name)s
+                where
+                  %(column_RESOURCE_NAME)s = %%s and
+                  %(column_HOME_RESOURCE_ID)s = %%s and
+                  %(column_BIND_MODE)s = %%s
+                """ % cls._bindTable,
+                [
+                    name,
+                    home._resourceID,
+                    _BIND_MODE_OWN
+                ]
+            )
+        else:
+            data = yield home._txn.execSQL("""
+                select %(column_RESOURCE_ID)s from %(name)s
+                where
+                  %(column_RESOURCE_NAME)s = %%s and
+                  %(column_HOME_RESOURCE_ID)s = %%s and
+                  %(column_BIND_MODE)s != %%s
+                """ % cls._bindTable,
+                [
+                    name,
+                    home._resourceID,
+                    _BIND_MODE_OWN
+                ]
+            )
+
+        if not data:
+            returnValue(None)
+        resourceID = data[0][0]
+        child = cls(home, name, resourceID)
+        yield child.initFromStore()
+        returnValue(child)
+
+    @classmethod
+    @inlineCallbacks
+    def create(cls, home, name):
+        
+        child = (yield cls.objectWithName(home, name, owned=True))
+        if child:
+            raise HomeChildNameAlreadyExistsError(name)
+
+        if name.startswith("."):
+            raise HomeChildNameNotAllowedError(name)
+        
+        # Create and initialize (in a similar manner to initFromStore) this object
+        rows = yield home._txn.execSQL("select nextval('RESOURCE_ID_SEQ')")
+        resourceID = rows[0][0]
+        _created, _modified = (yield home._txn.execSQL("""
+            insert into %(name)s (%(column_RESOURCE_ID)s)
+            values (%%s)
+            returning %(column_CREATED)s, %(column_MODIFIED)s
+            """ % cls._homeChildTable,
+            [resourceID]
+        ))[0]
+        
+        # Bind table needs entry
+        yield home._txn.execSQL("""
+            insert into %(name)s (
+                %(column_HOME_RESOURCE_ID)s,
+                %(column_RESOURCE_ID)s, %(column_RESOURCE_NAME)s, %(column_BIND_MODE)s,
+                %(column_SEEN_BY_OWNER)s, %(column_SEEN_BY_SHAREE)s, %(column_BIND_STATUS)s) values (
+            %%s, %%s, %%s, %%s, %%s, %%s, %%s)
+            """ % cls._bindTable,
+            [home._resourceID, resourceID, name, _BIND_MODE_OWN, True, True,
+             _BIND_STATUS_ACCEPTED]
+        )
+
+        # Initialize other state
+        child = cls(home, name, resourceID)
+        child._created = _created
+        child._modified = _modified
+        yield child._loadPropertyStore()
+
+        child.properties()[
+            PropertyName.fromElement(ResourceType)
+        ] = child.resourceType()
+        yield child._initSyncToken()
+        home.createdChild(child)
+
+        # Change notification for a create is on the home collection
+        home.notifyChanged()
+        returnValue(child)
+
+    @inlineCallbacks
     def initFromStore(self):
         """
         Initialise this object from the store. We read in and cache all the extra metadata
@@ -871,6 +838,12 @@
         return "<%s: %s>" % (self.__class__.__name__, self._resourceID)
 
 
+    def exists(self):
+        """
+        An empty resource-id means this object does not yet exist in the DB.
+        """
+        return self._resourceID is not None
+
     def name(self):
         return self._name
 
@@ -893,6 +866,25 @@
         self.notifyChanged()
 
 
+    @inlineCallbacks
+    def remove(self):
+
+        yield self._deletedSyncToken()
+
+        yield self._txn.execSQL(
+            "delete from %(name)s where %(column_RESOURCE_ID)s = %%s" % self._homeChildTable,
+            [self._resourceID],
+            raiseOnZeroRowCount=NoSuchHomeChildError
+        )
+
+        # Set to non-existent state
+        self._resourceID = None
+        self._created = None
+        self._modified = None
+        self._objects = {}
+
+        self.notifyChanged()
+
     def ownerHome(self):
         return self._home
 
@@ -1288,13 +1280,11 @@
 
 
     def created(self):
-        utc = datetime.datetime.strptime(self._created, "%Y-%m-%d %H:%M:%S.%f")
-        return datetimeMktime(utc)
+        return datetimeMktime(datetime.datetime.strptime(self._created, "%Y-%m-%d %H:%M:%S.%f")) if self._created else None
 
 
     def modified(self):
-        utc = datetime.datetime.strptime(self._modified, "%Y-%m-%d %H:%M:%S.%f")
-        return datetimeMktime(utc)
+        return datetimeMktime(datetime.datetime.strptime(self._modified, "%Y-%m-%d %H:%M:%S.%f")) if self._modified else None
 
 
     def notifierID(self, label="default"):
@@ -1498,6 +1488,32 @@
         self._resourceID = resourceID
         self._notifications = {}
 
+    @classmethod
+    @inlineCallbacks
+    def notificationsWithUID(cls, txn, uid):
+        """
+        Implement notificationsWithUID.
+        """
+        rows = yield txn.execSQL(
+            """
+            select %(column_RESOURCE_ID)s from %(name)s where
+            %(column_OWNER_UID)s = %%s
+            """ % NOTIFICATION_HOME_TABLE, [uid]
+        )
+        if rows:
+            resourceID = rows[0][0]
+            created = False
+        else:
+            resourceID = str((yield txn.execSQL(
+                "insert into %(name)s (%(column_OWNER_UID)s) values (%%s) returning %(column_RESOURCE_ID)s" % NOTIFICATION_HOME_TABLE,
+                [uid]
+            ))[0][0])
+            created = True
+        collection = cls(txn, uid, resourceID)
+        yield collection._loadPropertyStore()
+        if created:
+            yield collection._initSyncToken()
+        returnValue(collection)
 
     @inlineCallbacks
     def _loadPropertyStore(self):
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20101106/afd598e7/attachment-0001.html>


More information about the calendarserver-changes mailing list