[CalendarServer-changes] [6446] CalendarServer/trunk

source_changes at macosforge.org source_changes at macosforge.org
Tue Oct 19 08:39:57 PDT 2010


Revision: 6446
          http://trac.macosforge.org/projects/calendarserver/changeset/6446
Author:   glyph at apple.com
Date:     2010-10-19 08:39:55 -0700 (Tue, 19 Oct 2010)
Log Message:
-----------
merge more-deferreds-7, make SQL execution asynchronous

Modified Paths:
--------------
    CalendarServer/trunk/calendarserver/tools/purge.py
    CalendarServer/trunk/support/build.sh
    CalendarServer/trunk/twext/web2/dav/resource.py
    CalendarServer/trunk/twext/web2/resource.py
    CalendarServer/trunk/twext/web2/server.py
    CalendarServer/trunk/twext/web2/static.py
    CalendarServer/trunk/twext/web2/test/test_resource.py
    CalendarServer/trunk/twistedcaldav/directory/addressbook.py
    CalendarServer/trunk/twistedcaldav/directory/calendar.py
    CalendarServer/trunk/twistedcaldav/directory/calendaruserproxy.py
    CalendarServer/trunk/twistedcaldav/directory/opendirectorybacker.py
    CalendarServer/trunk/twistedcaldav/directory/principal.py
    CalendarServer/trunk/twistedcaldav/directory/test/test_calendar.py
    CalendarServer/trunk/twistedcaldav/directory/test/test_principal.py
    CalendarServer/trunk/twistedcaldav/dropbox.py
    CalendarServer/trunk/twistedcaldav/extensions.py
    CalendarServer/trunk/twistedcaldav/fileops.py
    CalendarServer/trunk/twistedcaldav/icaldav.py
    CalendarServer/trunk/twistedcaldav/method/put_addressbook_common.py
    CalendarServer/trunk/twistedcaldav/method/put_common.py
    CalendarServer/trunk/twistedcaldav/method/report_addressbook_query.py
    CalendarServer/trunk/twistedcaldav/method/report_calendar_query.py
    CalendarServer/trunk/twistedcaldav/method/report_common.py
    CalendarServer/trunk/twistedcaldav/method/report_multiget_common.py
    CalendarServer/trunk/twistedcaldav/method/report_sync_collection.py
    CalendarServer/trunk/twistedcaldav/notifications.py
    CalendarServer/trunk/twistedcaldav/resource.py
    CalendarServer/trunk/twistedcaldav/schedule.py
    CalendarServer/trunk/twistedcaldav/scheduling/implicit.py
    CalendarServer/trunk/twistedcaldav/scheduling/processing.py
    CalendarServer/trunk/twistedcaldav/scheduling/utils.py
    CalendarServer/trunk/twistedcaldav/sharing.py
    CalendarServer/trunk/twistedcaldav/storebridge.py
    CalendarServer/trunk/twistedcaldav/test/test_addressbookmultiget.py
    CalendarServer/trunk/twistedcaldav/test/test_addressbookquery.py
    CalendarServer/trunk/twistedcaldav/test/test_calendarquery.py
    CalendarServer/trunk/twistedcaldav/test/test_extensions.py
    CalendarServer/trunk/twistedcaldav/test/test_sharing.py
    CalendarServer/trunk/twistedcaldav/test/test_sql.py
    CalendarServer/trunk/twistedcaldav/test/test_wrapping.py
    CalendarServer/trunk/twistedcaldav/test/util.py
    CalendarServer/trunk/twistedcaldav/upgrade.py
    CalendarServer/trunk/txdav/__init__.py
    CalendarServer/trunk/txdav/base/datastore/sql.py
    CalendarServer/trunk/txdav/base/datastore/subpostgres.py
    CalendarServer/trunk/txdav/base/propertystore/sql.py
    CalendarServer/trunk/txdav/base/propertystore/test/base.py
    CalendarServer/trunk/txdav/base/propertystore/test/test_sql.py
    CalendarServer/trunk/txdav/caldav/datastore/file.py
    CalendarServer/trunk/txdav/caldav/datastore/scheduling.py
    CalendarServer/trunk/txdav/caldav/datastore/sql.py
    CalendarServer/trunk/txdav/caldav/datastore/test/common.py
    CalendarServer/trunk/txdav/caldav/datastore/test/test_file.py
    CalendarServer/trunk/txdav/caldav/datastore/test/test_sql.py
    CalendarServer/trunk/txdav/caldav/datastore/test/test_util.py
    CalendarServer/trunk/txdav/caldav/datastore/util.py
    CalendarServer/trunk/txdav/caldav/icalendarstore.py
    CalendarServer/trunk/txdav/carddav/datastore/file.py
    CalendarServer/trunk/txdav/carddav/datastore/sql.py
    CalendarServer/trunk/txdav/carddav/datastore/test/common.py
    CalendarServer/trunk/txdav/carddav/datastore/test/test_file.py
    CalendarServer/trunk/txdav/carddav/datastore/test/test_sql.py
    CalendarServer/trunk/txdav/carddav/datastore/util.py
    CalendarServer/trunk/txdav/carddav/iaddressbookstore.py
    CalendarServer/trunk/txdav/common/datastore/file.py
    CalendarServer/trunk/txdav/common/datastore/sql.py
    CalendarServer/trunk/txdav/common/datastore/sql_legacy.py
    CalendarServer/trunk/txdav/common/datastore/test/test_util.py
    CalendarServer/trunk/txdav/common/datastore/test/util.py
    CalendarServer/trunk/txdav/common/datastore/util.py
    CalendarServer/trunk/txdav/common/icommondatastore.py
    CalendarServer/trunk/txdav/common/inotifications.py

Added Paths:
-----------
    CalendarServer/trunk/twistedcaldav/directory/common.py
    CalendarServer/trunk/txdav/caldav/datastore/index_file.py
    CalendarServer/trunk/txdav/caldav/datastore/test/test_index_file.py
    CalendarServer/trunk/txdav/carddav/datastore/index_file.py
    CalendarServer/trunk/txdav/carddav/datastore/test/test_index_file.py

Removed Paths:
-------------
    CalendarServer/trunk/twistedcaldav/index.py
    CalendarServer/trunk/twistedcaldav/test/test_index.py
    CalendarServer/trunk/twistedcaldav/test/test_vcardindex.py
    CalendarServer/trunk/twistedcaldav/vcardindex.py

Property Changed:
----------------
    CalendarServer/trunk/


Property changes on: CalendarServer/trunk
___________________________________________________________________
Modified: svn:mergeinfo
   - /CalendarServer/branches/config-separation:4379-4443
/CalendarServer/branches/egg-info-351:4589-4625
/CalendarServer/branches/generic-sqlstore:6167-6191
/CalendarServer/branches/new-store:5594-5934
/CalendarServer/branches/new-store-no-caldavfile:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2:5936-5981
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464:4465-4957
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187:5188-5440
/CalendarServer/branches/users/glyph/contacts-server-merge:4971-5080
/CalendarServer/branches/users/glyph/more-deferreds-6:6330
/CalendarServer/branches/users/glyph/sendfdport:5388-5424
/CalendarServer/branches/users/glyph/sql-store:5929-6073
/CalendarServer/branches/users/glyph/use-system-twisted:5084-5149
/CalendarServer/branches/users/sagen/locations-resources:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2:5052-5061
/CalendarServer/branches/users/sagen/resource-delegates-4038:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066:4068-4075
/CalendarServer/branches/users/sagen/resources-2:5084-5093
/CalendarServer/branches/users/wsanchez/transations:5515-5593
   + /CalendarServer/branches/config-separation:4379-4443
/CalendarServer/branches/egg-info-351:4589-4625
/CalendarServer/branches/generic-sqlstore:6167-6191
/CalendarServer/branches/new-store:5594-5934
/CalendarServer/branches/new-store-no-caldavfile:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2:5936-5981
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464:4465-4957
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187:5188-5440
/CalendarServer/branches/users/glyph/contacts-server-merge:4971-5080
/CalendarServer/branches/users/glyph/more-deferreds-6:6322-6368
/CalendarServer/branches/users/glyph/more-deferreds-7:6369-6445
/CalendarServer/branches/users/glyph/sendfdport:5388-5424
/CalendarServer/branches/users/glyph/sql-store:5929-6073
/CalendarServer/branches/users/glyph/use-system-twisted:5084-5149
/CalendarServer/branches/users/sagen/locations-resources:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2:5052-5061
/CalendarServer/branches/users/sagen/resource-delegates-4038:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066:4068-4075
/CalendarServer/branches/users/sagen/resources-2:5084-5093
/CalendarServer/branches/users/wsanchez/transations:5515-5593

Modified: CalendarServer/trunk/calendarserver/tools/purge.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/purge.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/calendarserver/tools/purge.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -286,7 +286,7 @@
         # Get the calendar home
         principalCollection = directory.principalCollection
         principal = principalCollection.principalForRecord(record)
-        calendarHome = principal.calendarHome()
+        calendarHome = yield principal.calendarHome()
 
         if verbose:
             print "%s %-15s :" % (record.uid, record.shortNames[0]),
@@ -383,7 +383,7 @@
 
     principalCollection = directory.principalCollection
     principal = principalCollection.principalForRecord(record)
-    calendarHome = principal.calendarHome()
+    calendarHome = yield principal.calendarHome()
 
     # Anything in the past is left alone
     now = datetime.utcnow().strftime("%Y%m%dT%H%M%SZ")

Modified: CalendarServer/trunk/support/build.sh
===================================================================
--- CalendarServer/trunk/support/build.sh	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/support/build.sh	2010-10-19 15:39:55 UTC (rev 6446)
@@ -489,6 +489,10 @@
   # Python dependencies
   #
 
+  # First, let's make sure that we ourselves are on PYTHONPATH, in case some
+  # code (like, let's say, trial) decides to chdir somewhere.
+  export PYTHONPATH="${wd}:${PYTHONPATH:-}";
+
   local zi="zope.interface-3.3.0";
   py_dependency \
     "Zope Interface" "zope.interface" "${zi}" \

Modified: CalendarServer/trunk/twext/web2/dav/resource.py
===================================================================
--- CalendarServer/trunk/twext/web2/dav/resource.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twext/web2/dav/resource.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -51,8 +51,9 @@
 
 from twisted.cred.error import LoginFailed, UnauthorizedLogin
 from twisted.python.failure import Failure
-from twisted.internet.defer import Deferred, maybeDeferred, succeed
-from twisted.internet.defer import inlineCallbacks, returnValue
+from twisted.internet.defer import (
+    Deferred, maybeDeferred, succeed, inlineCallbacks, returnValue
+)
 from twisted.internet import reactor
 
 from twext.python.log import Logger
@@ -204,10 +205,12 @@
             self.deadProperties().contains(qname)
         )
 
+
     def readProperty(self, property, request):
         """
         See L{IDAVResource.readProperty}.
         """
+        @inlineCallbacks
         def defer():
             if type(property) is tuple:
                 qname = property
@@ -222,22 +225,22 @@
                 if name == "resourcetype":
                     # Allow live property to be overridden by dead property
                     if self.deadProperties().contains(qname):
-                        return self.deadProperties().get(qname)
+                        returnValue(self.deadProperties().get(qname))
                     if self.isCollection():
-                        return davxml.ResourceType.collection
-                    return davxml.ResourceType.empty
+                        returnValue(davxml.ResourceType.collection) #@UndefinedVariable
+                    returnValue(davxml.ResourceType.empty) #@UndefinedVariable
 
                 if name == "getetag":
-                    etag = self.etag()
+                    etag = yield self.etag()
                     if etag is None:
-                        return None
-                    return davxml.GETETag(etag.generate())
+                        returnValue(None)
+                    returnValue(davxml.GETETag(etag.generate()))
 
                 if name == "getcontenttype":
                     mimeType = self.contentType()
                     if mimeType is None:
-                        return None
-                    return davxml.GETContentType(generateContentType(mimeType))
+                        returnValue(None)
+                    returnValue(davxml.GETContentType(generateContentType(mimeType)))
 
                 if name == "getcontentlength":
                     length = self.contentLength()
@@ -245,139 +248,125 @@
                         # TODO: really we should "render" the resource and 
                         # determine its size from that but for now we just 
                         # return an empty element.
-                        return davxml.GETContentLength("")
+                        returnValue(davxml.GETContentLength(""))
                     else:
-                        return davxml.GETContentLength(str(length))
+                        returnValue(davxml.GETContentLength(str(length)))
 
                 if name == "getlastmodified":
-                    lastModified = self.lastModified()
+                    lastModified = yield self.lastModified()
                     if lastModified is None:
-                        return None
-                    return davxml.GETLastModified.fromDate(lastModified)
+                        returnValue(None)
+                    returnValue(davxml.GETLastModified.fromDate(lastModified))
 
                 if name == "creationdate":
-                    creationDate = self.creationDate()
+                    creationDate = yield self.creationDate()
                     if creationDate is None:
-                        return None
-                    return davxml.CreationDate.fromDate(creationDate)
+                        returnValue(None)
+                    returnValue(davxml.CreationDate.fromDate(creationDate))
 
                 if name == "displayname":
                     displayName = self.displayName()
                     if displayName is None:
-                        return None
-                    return davxml.DisplayName(displayName)
+                        returnValue(None)
+                    returnValue(davxml.DisplayName(displayName))
 
                 if name == "supportedlock":
-                    return davxml.SupportedLock(
+                    returnValue(davxml.SupportedLock(
                         davxml.LockEntry(
-                            davxml.LockScope.exclusive,
-                            davxml.LockType.write
+                            davxml.LockScope.exclusive, #@UndefinedVariable
+                            davxml.LockType.write #@UndefinedVariable
                         ),
                         davxml.LockEntry(
-                            davxml.LockScope.shared,
-                            davxml.LockType.write
+                            davxml.LockScope.shared, #@UndefinedVariable
+                            davxml.LockType.write #@UndefinedVariable
                         ),
-                    )
+                    ))
 
                 if name == "supported-report-set":
-                    return davxml.SupportedReportSet(*[
+                    returnValue(davxml.SupportedReportSet(*[
                         davxml.SupportedReport(report,)
                         for report in self.supportedReports()
-                    ])
+                    ]))
 
                 if name == "supported-privilege-set":
-                    return self.supportedPrivileges(request)
+                    returnValue((yield self.supportedPrivileges(request)))
 
                 if name == "acl-restrictions":
-                    return davxml.ACLRestrictions()
+                    returnValue(davxml.ACLRestrictions())
 
                 if name == "inherited-acl-set":
-                    return davxml.InheritedACLSet(*self.inheritedACLSet())
+                    returnValue(davxml.InheritedACLSet(*self.inheritedACLSet()))
 
                 if name == "principal-collection-set":
-                    return davxml.PrincipalCollectionSet(*[
+                    returnValue(davxml.PrincipalCollectionSet(*[
                         davxml.HRef(
                             principalCollection.principalCollectionURL()
                         )
                         for principalCollection in self.principalCollections()
-                    ])
+                    ]))
 
+                @inlineCallbacks
                 def ifAllowed(privileges, callback):
-                    def onError(failure):
-                        failure.trap(AccessDeniedError)
-                        
+                    try:
+                        yield self.checkPrivileges(request, privileges)
+                        result = yield callback()
+                    except AccessDeniedError:
                         raise HTTPError(StatusResponse(
                             responsecode.UNAUTHORIZED,
                             "Access denied while reading property %s."
                             % (sname,)
                         ))
+                    returnValue(result)
 
-                    d = self.checkPrivileges(request, privileges)
-                    d.addCallbacks(lambda _: callback(), onError)
-                    return d
-
                 if name == "current-user-privilege-set":
+                    @inlineCallbacks
                     def callback():
-                        d = self.currentPrivileges(request)
-                        d.addCallback(
-                            lambda privs:
-                                davxml.CurrentUserPrivilegeSet(*privs)
-                        )
-                        return d
-                    return ifAllowed(
+                        privs = yield self.currentPrivileges(request)
+                        returnValue(davxml.CurrentUserPrivilegeSet(*privs))
+                    returnValue((yield ifAllowed(
                         (davxml.ReadCurrentUserPrivilegeSet(),),
                         callback
-                    )
+                    )))
 
                 if name == "acl":
+                    @inlineCallbacks
                     def callback():
-                        def gotACL(acl):
-                            if acl is None:
-                                acl = davxml.ACL()
-                            return acl
-                        d = self.accessControlList(request)
-                        d.addCallback(gotACL)
-                        return d
-                    return ifAllowed((davxml.ReadACL(),), callback)
-                
+                        acl = yield self.accessControlList(request)
+                        if acl is None:
+                            acl = davxml.ACL()
+                        returnValue(acl)
+                    returnValue(
+                        (yield ifAllowed((davxml.ReadACL(),), callback))
+                    )
+
                 if name == "current-user-principal":
-                    return davxml.CurrentUserPrincipal(
+                    returnValue(davxml.CurrentUserPrincipal(
                         self.currentPrincipal(request).children[0]
-                    )
+                    ))
 
                 if name == "quota-available-bytes":
-                    def callback(qvalue):
-                        if qvalue is None:
-                            raise HTTPError(StatusResponse(
-                                responsecode.NOT_FOUND,
-                                "Property %s does not exist." % (sname,)
-                            ))
-                        else:
-                            return davxml.QuotaAvailableBytes(str(qvalue[0]))
-                    d = self.quota(request)
-                    d.addCallback(callback)
-                    return d
+                    qvalue = yield self.quota(request)
+                    if qvalue is None:
+                        raise HTTPError(StatusResponse(
+                            responsecode.NOT_FOUND,
+                            "Property %s does not exist." % (sname,)
+                        ))
+                    else:
+                        returnValue(davxml.QuotaAvailableBytes(str(qvalue[0])))
 
                 if name == "quota-used-bytes":
-                    def callback(qvalue):
-                        if qvalue is None:
-                            raise HTTPError(StatusResponse(
-                                responsecode.NOT_FOUND,
-                                "Property %s does not exist." % (sname,)
-                            ))
-                        else:
-                            return davxml.QuotaUsedBytes(str(qvalue[1]))
-                    d = self.quota(request)
-                    d.addCallback(callback)
-                    return d
+                    qvalue = yield self.quota(request)
+                    if qvalue is None:
+                        raise HTTPError(StatusResponse(
+                            responsecode.NOT_FOUND,
+                            "Property %s does not exist." % (sname,)
+                        ))
+                    else:
+                        returnValue(davxml.QuotaUsedBytes(str(qvalue[1])))
 
             elif namespace == twisted_dav_namespace:
                 if name == "resource-class":
-                    class ResourceClass (davxml.WebDAVTextElement):
-                        namespace = twisted_dav_namespace
-                        name = "resource-class"
-                        hidden = False
-                    return ResourceClass(self.__class__.__name__)
+                    returnValue(ResourceClass(self.__class__.__name__))
 
             elif namespace == twisted_private_namespace:
                 raise HTTPError(StatusResponse(
@@ -385,10 +374,10 @@
                     "Properties in the %s namespace are private to the server."
                     % (sname,)
                 ))
+            returnValue(self.deadProperties().get(qname))
 
-            return self.deadProperties().get(qname)
+        return defer()
 
-        return maybeDeferred(defer)
 
     def writeProperty(self, property, request):
         """
@@ -629,7 +618,7 @@
 
         completionDeferred = Deferred()
         basepath = request.urlForResource(self)
-        children = list(self.listChildren())
+        children = []
 
         def checkPrivilegesError(failure):
             failure.trap(AccessDeniedError)
@@ -679,7 +668,10 @@
                 d.addCallbacks(gotChild, checkPrivilegesError, (childpath,))
                 d.addErrback(completionDeferred.errback)
 
-        getChild()
+        def gotChildren(listChildrenResult):
+            children[:] = list(listChildrenResult)
+            getChild()
+        maybeDeferred(self.listChildren).addCallback(gotChildren)
 
         return completionDeferred
 
@@ -729,7 +721,7 @@
 
         children = []
         basepath = request.urlForResource(self)
-        childnames = list(self.listChildren())
+        childnames = list((yield self.listChildren()))
         for childname in childnames:
             childpath = joinURL(basepath, urllib.quote(childname))
             child = (yield request.locateChildResource(self, childname))
@@ -1090,6 +1082,7 @@
         """
         self.writeDeadProperty(acl)
 
+
     @inlineCallbacks
     def mergeAccessControlList(self, new_acl, request):
         """
@@ -1253,9 +1246,10 @@
         # FIXME: verify acl is self-consistent
 
         # Step 11
-        self.writeNewACEs(new_set)
+        yield self.writeNewACEs(new_set)
         returnValue(None)
-        
+
+
     def writeNewACEs(self, new_aces):
         """
         Write a new ACL to the resource's property store.  This is a
@@ -1264,8 +1258,9 @@
         command.
         @param new_aces: C{list} of L{ACE} for ACL being set.
         """
-        self.setAccessControlList(davxml.ACL(*new_aces))
+        return self.setAccessControlList(davxml.ACL(*new_aces))
 
+
     def matchPrivilege(self, privilege, ace_privileges, supportedPrivileges):
         for ace_privilege in ace_privileges:
             if (
@@ -1276,6 +1271,7 @@
 
         return False
 
+
     @inlineCallbacks
     def checkPrivileges(
         self, request, privileges, recurse=False,
@@ -2674,3 +2670,9 @@
 )
 
 unauthenticatedPrincipal = davxml.Principal(davxml.Unauthenticated())
+
+
+class ResourceClass (davxml.WebDAVTextElement):
+    namespace = twisted_dav_namespace
+    name = "resource-class"
+    hidden = False

Modified: CalendarServer/trunk/twext/web2/resource.py
===================================================================
--- CalendarServer/trunk/twext/web2/resource.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twext/web2/resource.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -30,6 +30,8 @@
 # System Imports
 from zope.interface import implements
 
+from twisted.internet.defer import inlineCallbacks, returnValue
+
 from twext.web2 import iweb, http, server, responsecode
 
 class RenderMixin(object):
@@ -71,6 +73,8 @@
         if method:
             return method(request)
 
+
+    @inlineCallbacks
     def renderHTTP(self, request):
         """
         See L{iweb.IResource.renderHTTP}.
@@ -96,17 +100,15 @@
         @return: an object adaptable to L{iweb.IResponse}.
         """
         method = getattr(self, "http_" + request.method, None)
-        if not method:
+        if method is None:
             response = http.Response(responsecode.NOT_ALLOWED)
             response.headers.setHeader("allow", self.allowedMethods())
-            return response
+            returnValue(response)
 
-        d = self.checkPreconditions(request)
-        if d is None:
-            return method(request)
-        else:
-            return d.addCallback(lambda _: method(request))
+        yield self.checkPreconditions(request)
+        returnValue((yield method(request)))
 
+
     def http_OPTIONS(self, request):
         """
         Respond to a OPTIONS request.

Modified: CalendarServer/trunk/twext/web2/server.py
===================================================================
--- CalendarServer/trunk/twext/web2/server.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twext/web2/server.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -346,6 +346,7 @@
         d.addCallback(self._cbFinishRender)
         d.addErrback(self._processingFailed)
         d.callback(None)
+        return d
 
     def preprocessRequest(self):
         """Do any request processing that doesn't follow the normal
@@ -470,10 +471,10 @@
         @raise NoURLForResourceError: if C{resource} has no URL in this request
             (because it was not obtained from the request).
         """
-        resource = self._urlsByResource.get(resource, None)
-        if resource is None:
+        url = self._urlsByResource.get(resource, None)
+        if url is None:
             raise NoURLForResourceError(resource)
-        return resource
+        return url
 
     def locateResource(self, url):
         """

Modified: CalendarServer/trunk/twext/web2/static.py
===================================================================
--- CalendarServer/trunk/twext/web2/static.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twext/web2/static.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -1,3 +1,4 @@
+# -*- test-case-name: twext.web2.test.test_static -*-
 ##
 # Copyright (c) 2001-2008 Twisted Matrix Laboratories.
 # Copyright (c) 2010 Apple Computer, Inc. All rights reserved.
@@ -34,10 +35,11 @@
 # Sibling Imports
 from twext.web2 import http_headers, resource
 from twext.web2 import http, iweb, stream, responsecode, server, dirlist
+from twext.web2.http import HTTPError
 
 # Twisted Imports
 from twext.python.filepath import CachingFilePath as FilePath
-from twisted.internet.defer import maybeDeferred
+from twisted.internet.defer import inlineCallbacks, returnValue
 from zope.interface import implements
 
 class MetaDataMixin(object):
@@ -94,21 +96,26 @@
         return True
 
 class StaticRenderMixin(resource.RenderMixin, MetaDataMixin):
+
+
+    @inlineCallbacks
     def checkPreconditions(self, request):
         # This code replaces the code in resource.RenderMixin
         if request.method not in ("GET", "HEAD"):
             http.checkPreconditions(
                 request,
                 entityExists = self.exists(),
-                etag = self.etag(),
-                lastModified = self.lastModified(),
+                etag = (yield self.etag()),
+                lastModified = (yield self.lastModified()),
             )
 
         # Check per-method preconditions
         method = getattr(self, "preconditions_" + request.method, None)
         if method:
-            return method(request)
+            returnValue((yield method(request)))
 
+
+    @inlineCallbacks
     def renderHTTP(self, request):
         """
         See L{resource.RenderMixIn.renderHTTP}.
@@ -116,32 +123,27 @@
         This implementation automatically sets some headers on the response
         based on data available from L{MetaDataMixin} methods.
         """
-        def setHeaders(response):
-            response = iweb.IResponse(response)
+        try:
+            response = yield super(StaticRenderMixin, self).renderHTTP(request)
+        except HTTPError, he:
+            response = he.response
 
-            # Don't provide additional resource information to error responses
-            if response.code < 400:
-                # Content-* headers refer to the response content, not
-                # (necessarily) to the resource content, so they depend on the
-                # request method, and therefore can't be set here.
-                for (header, value) in (
-                    ("etag", self.etag()),
-                    ("last-modified", self.lastModified()),
-                ):
-                    if value is not None:
-                        response.headers.setHeader(header, value)
+        response = iweb.IResponse(response)
+        # Don't provide additional resource information to error responses
+        if response.code < 400:
+            # Content-* headers refer to the response content, not
+            # (necessarily) to the resource content, so they depend on the
+            # request method, and therefore can't be set here.
+            for (header, value) in (
+                ("etag", (yield self.etag())),
+                ("last-modified", (yield self.lastModified())),
+            ):
+                if value is not None:
+                    response.headers.setHeader(header, value)
+        returnValue(response)
 
-            return response
 
-        def onError(f):
-            # If we get an HTTPError, run its response through setHeaders() as
-            # well.
-            f.trap(http.HTTPError)
-            return setHeaders(f.value.response)
 
-        d = maybeDeferred(super(StaticRenderMixin, self).renderHTTP, request)
-        return d.addCallbacks(setHeaders, onError)
-
 class Data(resource.Resource):
     """
     This is a static, in-memory resource.
@@ -319,12 +321,6 @@
         """
         self.ignoredExts.append(ext)
 
-    def directoryListing(self):
-        return dirlist.DirectoryLister(self.fp.path,
-                                       self.listChildren(),
-                                       self.contentTypes,
-                                       self.contentEncodings,
-                                       self.defaultType)
 
     def putChild(self, name, child):
         """
@@ -580,7 +576,7 @@
     """
     import mimetypes
     # Grab Python's built-in mimetypes dictionary.
-    contentTypes = mimetypes.types_map
+    contentTypes = mimetypes.types_map #@UndefinedVariable
     # Update Python's semi-erroneous dictionary with a few of the
     # usual suspects.
     contentTypes.update(

Modified: CalendarServer/trunk/twext/web2/test/test_resource.py
===================================================================
--- CalendarServer/trunk/twext/web2/test/test_resource.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twext/web2/test/test_resource.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -9,7 +9,7 @@
 
 from zope.interface import implements
 
-from twisted.internet.defer import succeed, fail
+from twisted.internet.defer import succeed, fail, inlineCallbacks
 from twisted.trial import unittest
 from twext.web2 import responsecode
 from twext.web2.iweb import IResource
@@ -78,6 +78,8 @@
             self._my_allowed_methods
         )
 
+
+    @inlineCallbacks
     def test_checkPreconditions_raises(self):
         """
         RenderMixin.checkPreconditions()
@@ -87,11 +89,17 @@
         request = SimpleRequest(Site(resource), "BLEARGH", "/")
 
         # Check that checkPreconditions raises as expected
-        self.assertRaises(PreconditionError, resource.checkPreconditions, request)
+        self.assertRaises(
+            PreconditionError, resource.checkPreconditions, request
+        )
 
         # Check that renderHTTP calls checkPreconditions
-        self.assertRaises(PreconditionError, resource.renderHTTP, request)
+        yield self.failUnlessFailure(
+            resource.renderHTTP(request), PreconditionError
+        )
 
+
+    @inlineCallbacks
     def test_checkPreconditions_none(self):
         """
         RenderMixin.checkPreconditions()
@@ -101,8 +109,12 @@
         request = SimpleRequest(Site(resource), "SWEETHOOKUPS", "/")
 
         # Check that checkPreconditions without a raise doesn't barf
-        self.assertEquals(resource.renderHTTP(request), responsecode.NO_CONTENT)
+        self.assertEquals(
+            (yield resource.renderHTTP(request)),
+            responsecode.NO_CONTENT
+        )
 
+
     def test_checkPreconditions_deferred(self):
         """
         RenderMixin.checkPreconditions()

Modified: CalendarServer/trunk/twistedcaldav/directory/addressbook.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/addressbook.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/directory/addressbook.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -19,7 +19,6 @@
 """
 
 __all__ = [
-    "uidsResourceName",
     "DirectoryAddressBookHomeProvisioningResource",
     "DirectoryAddressBookHomeTypeProvisioningResource",
     "DirectoryAddressBookHomeUIDProvisioningResource",
@@ -32,10 +31,14 @@
 from twext.web2.http import HTTPError
 from twext.web2.http_headers import ETag, MimeType
 
+from twisted.internet.defer import inlineCallbacks, returnValue
+
 from twistedcaldav.config import config
 from twistedcaldav.directory.idirectory import IDirectoryService
-from twistedcaldav.directory.resource import DirectoryReverseProxyResource
-from twistedcaldav.directory.util import transactionFromRequest
+
+from twistedcaldav.directory.common import CommonUIDProvisioningResource,\
+    uidsResourceName, CommonHomeTypeProvisioningResource
+
 from twistedcaldav.extensions import ReadOnlyResourceMixIn, DAVResource,\
     DAVResourceWithChildrenMixin
 from twistedcaldav.resource import AddressBookHomeResource
@@ -44,8 +47,6 @@
 
 log = Logger()
 
-# Use __underbars__ convention to avoid conflicts with directory resource types.
-uidsResourceName = "__uids__"
 
 # FIXME: copied from resource.py to avoid circular dependency
 class CalDAVComplianceMixIn(object):
@@ -71,7 +72,9 @@
         return MimeType("httpd", "unix-directory")
 
 
-class DirectoryAddressBookHomeProvisioningResource (DirectoryAddressBookProvisioningResource):
+class DirectoryAddressBookHomeProvisioningResource (
+        DirectoryAddressBookProvisioningResource
+    ):
     """
     Resource which provisions address book home collections as needed.    
     """
@@ -134,7 +137,10 @@
         return "addressbooks"
 
 
-class DirectoryAddressBookHomeTypeProvisioningResource (DirectoryAddressBookProvisioningResource):
+class DirectoryAddressBookHomeTypeProvisioningResource (
+        CommonHomeTypeProvisioningResource,
+        DirectoryAddressBookProvisioningResource
+    ):
     """
     Resource which provisions address book home collections of a specific
     record type as needed.
@@ -156,18 +162,7 @@
     def url(self):
         return joinURL(self._parent.url(), self.recordType)
 
-    def locateChild(self, request, segments):
-        name = segments[0]
-        if name == "":
-            return (self, segments[1:])
 
-        record = self.directory.recordWithShortName(self.recordType, name)
-        if record is None:
-            return None, []
-
-        return (self._parent.homeForDirectoryRecord(record, request),
-                segments[1:])
-
     def listChildren(self):
         if config.EnablePrincipalListings:
 
@@ -206,99 +201,33 @@
         return self._parent.principalForRecord(record)
 
 
-class DirectoryAddressBookHomeUIDProvisioningResource (DirectoryAddressBookProvisioningResource):
+class DirectoryAddressBookHomeUIDProvisioningResource (
+        CommonUIDProvisioningResource,
+        DirectoryAddressBookProvisioningResource
+    ):
 
-    def __init__(self, parent):
-        """
-        @param parent: the parent of this resource
-        """
-        assert parent is not None
+    homeResourceTypeName = 'addressbooks'
 
-        super(DirectoryAddressBookHomeUIDProvisioningResource, self).__init__()
+    enabledAttribute = 'enabledForAddressBooks'
 
-        self.directory = parent.directory
-        self.parent = parent
+    def homeResourceCreator(self, record, transaction):
+        return DirectoryAddressBookHomeResource.createHomeResource(
+            self, record, transaction)
 
-    def url(self):
-        return joinURL(self.parent.url(), uidsResourceName)
 
-    def locateChild(self, request, segments):
-
-        name = segments[0]
-        if name == "":
-            return (self, ())
-
-        record = self.directory.recordWithUID(name)
-        if record:
-            return (self.homeResourceForRecord(record, request), segments[1:])
-        else:
-            return (None, ())
-
-    def getChild(self, name, record=None):
-        raise NotImplementedError("DirectoryAddressBookHomeUIDProvisioningResource.getChild no longer exists.")
-
-    def listChildren(self):
-        # Not a listable collection
-        raise HTTPError(responsecode.FORBIDDEN)
-
-    def homeResourceForRecord(self, record, request):
-
-        transaction = transactionFromRequest(request, self.parent._newStore)
-
-        name = record.uid
-
-        if record is None:
-            self.log_msg("No directory record with GUID %r" % (name,))
-            return None
-
-        if not record.enabledForAddressBooks:
-            self.log_msg("Directory record %r is not enabled for address books" % (record,))
-            return None
-
-        assert len(name) > 4, "Directory record has an invalid GUID: %r" % (name,)
-        
-        if record.locallyHosted():
-            child = DirectoryAddressBookHomeResource(self, record, transaction)
-        else:
-            child = DirectoryReverseProxyResource(self, record)
-
-        return child
-
-    ##
-    # DAV
-    ##
-    
-    def isCollection(self):
-        return True
-
-    def displayName(self):
-        return uidsResourceName
-
-    ##
-    # ACL
-    ##
-
-    def principalCollections(self):
-        return self.parent.principalCollections()
-
-    def principalForRecord(self, record):
-        return self.parent.principalForRecord(record)
-
-
 class DirectoryAddressBookHomeResource (AddressBookHomeResource):
     """
     Address book home collection resource.
     """
-    def __init__(self, parent, record, transaction):
-        """
-        @param path: the path to the file which will back the resource.
-        """
-        assert parent is not None
-        assert record is not None
-        assert transaction is not None
 
+    @classmethod
+    @inlineCallbacks
+    def createHomeResource(cls, parent, record, transaction):
+        self = yield super(DirectoryAddressBookHomeResource, cls).createHomeResource(
+            parent, record.uid, transaction)
         self.record = record
-        super(DirectoryAddressBookHomeResource, self).__init__(parent, record.uid, transaction)
+        returnValue(self)
 
+
     def principalForRecord(self):
         return self.parent.principalForRecord(self.record)

Modified: CalendarServer/trunk/twistedcaldav/directory/calendar.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/calendar.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/directory/calendar.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -20,7 +20,6 @@
 """
 
 __all__ = [
-    "uidsResourceName",
     "DirectoryCalendarHomeProvisioningResource",
     "DirectoryCalendarHomeTypeProvisioningResource",
     "DirectoryCalendarHomeUIDProvisioningResource",
@@ -33,12 +32,13 @@
 from twext.web2.http import HTTPError
 from twext.web2.http_headers import ETag, MimeType
 
-from twisted.internet.defer import succeed
+from twisted.internet.defer import succeed, inlineCallbacks, returnValue
 
 from twistedcaldav.config import config
 from twistedcaldav.directory.idirectory import IDirectoryService
-from twistedcaldav.directory.resource import DirectoryReverseProxyResource
-from twistedcaldav.directory.util import transactionFromRequest
+from twistedcaldav.directory.common import uidsResourceName,\
+    CommonUIDProvisioningResource, CommonHomeTypeProvisioningResource
+
 from twistedcaldav.directory.wiki import getWikiACL
 from twistedcaldav.extensions import ReadOnlyResourceMixIn, DAVResource,\
     DAVResourceWithChildrenMixin
@@ -48,9 +48,6 @@
 
 log = Logger()
 
-# Use __underbars__ convention to avoid conflicts with directory resource types.
-uidsResourceName = "__uids__"
-
 # FIXME: copied from resource.py to avoid circular dependency
 class CalDAVComplianceMixIn(object):
     def davComplianceClasses(self):
@@ -136,7 +133,10 @@
     def displayName(self):
         return "calendars"
 
-class DirectoryCalendarHomeTypeProvisioningResource (DirectoryCalendarProvisioningResource):
+class DirectoryCalendarHomeTypeProvisioningResource(
+        CommonHomeTypeProvisioningResource,
+        DirectoryCalendarProvisioningResource
+    ):
     """
     Resource which provisions calendar home collections of a specific
     record type as needed.
@@ -158,18 +158,7 @@
     def url(self):
         return joinURL(self._parent.url(), self.recordType)
 
-    def locateChild(self, request, segments):
-        name = segments[0]
-        if name == "":
-            return (self, segments[1:])
 
-        record = self.directory.recordWithShortName(self.recordType, name)
-        if record is None:
-            return None, []
-
-        return (self._parent.homeForDirectoryRecord(record, request),
-                segments[1:])
-
     def listChildren(self):
         if config.EnablePrincipalListings:
 
@@ -207,99 +196,35 @@
     def principalForRecord(self, record):
         return self._parent.principalForRecord(record)
 
-class DirectoryCalendarHomeUIDProvisioningResource (DirectoryCalendarProvisioningResource):
+class DirectoryCalendarHomeUIDProvisioningResource (
+        CommonUIDProvisioningResource,
+        DirectoryCalendarProvisioningResource
+    ):
 
-    def __init__(self, parent):
-        """
-        @param parent: the parent of this resource
-        """
-        assert parent is not None
+    homeResourceTypeName = 'calendars'
 
-        super(DirectoryCalendarHomeUIDProvisioningResource, self).__init__()
+    enabledAttribute = 'enabledForCalendaring'
 
-        self.directory = parent.directory
-        self.parent = parent
+    def homeResourceCreator(self, record, transaction):
+        return DirectoryCalendarHomeResource.createHomeResource(
+            self, record, transaction)
 
-    def url(self):
-        return joinURL(self.parent.url(), uidsResourceName)
 
-    def locateChild(self, request, segments):
 
-        name = segments[0]
-        if name == "":
-            return (self, ())
-
-        record = self.directory.recordWithUID(name)
-        if record:
-            return (self.homeResourceForRecord(record, request), segments[1:])
-        else:
-            return (None, ())
-
-    def getChild(self, name, record=None):
-        raise NotImplementedError("DirectoryCalendarProvisioningResource.getChild no longer exists.")
-
-    def listChildren(self):
-        # Not a listable collection
-        raise HTTPError(responsecode.FORBIDDEN)
-
-    def homeResourceForRecord(self, record, request):
-
-        transaction = transactionFromRequest(request, self.parent._newStore)
-        name = record.uid
-
-        if record is None:
-            log.debug("No directory record with GUID %r" % (name,))
-            return None
-
-        if not record.enabledForCalendaring:
-            log.debug("Directory record %r is not enabled for calendaring" % (record,))
-            return None
-
-        assert len(name) > 4, "Directory record has an invalid GUID: %r" % (name,)
-        
-        if record.locallyHosted():
-            child = DirectoryCalendarHomeResource(self, record, transaction)
-        else:
-            child = DirectoryReverseProxyResource(self, record)
-
-        return child
-
-    ##
-    # DAV
-    ##
-    
-    def isCollection(self):
-        return True
-
-    def displayName(self):
-        return uidsResourceName
-
-    ##
-    # ACL
-    ##
-
-    def principalCollections(self):
-        return self.parent.principalCollections()
-
-    def principalForRecord(self, record):
-        return self.parent.principalForRecord(record)
-
-
 class DirectoryCalendarHomeResource (CalendarHomeResource):
     """
     Calendar home collection resource.
     """
-    def __init__(self, parent, record, transaction):
-        """
-        @param path: the path to the file which will back the resource.
-        """
-        assert parent is not None
-        assert record is not None
-        assert transaction is not None
 
+    @classmethod
+    @inlineCallbacks
+    def createHomeResource(cls, parent, record, transaction):
+        self = yield super(DirectoryCalendarHomeResource, cls).createHomeResource(
+            parent, record.uid, transaction)
         self.record = record
-        super(DirectoryCalendarHomeResource, self).__init__(parent, record.uid, transaction)
+        returnValue(self)
 
+
     # Special ACLs for Wiki service
     def accessControlList(self, request, inheritance=True, expanding=False, inherited_aces=None):
         def gotACL(wikiACL):

Modified: CalendarServer/trunk/twistedcaldav/directory/calendaruserproxy.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/calendaruserproxy.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/directory/calendaruserproxy.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -131,9 +131,9 @@
 
     def resourceType(self):
         if self.proxyType == "calendar-proxy-read":
-            return davxml.ResourceType.calendarproxyread
+            return davxml.ResourceType.calendarproxyread #@UndefinedVariable
         elif self.proxyType == "calendar-proxy-write":
-            return davxml.ResourceType.calendarproxywrite
+            return davxml.ResourceType.calendarproxywrite #@UndefinedVariable
         else:
             return super(CalendarUserProxyPrincipalResource, self).resourceType()
 

Copied: CalendarServer/trunk/twistedcaldav/directory/common.py (from rev 6445, CalendarServer/branches/users/glyph/more-deferreds-7/twistedcaldav/directory/common.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/common.py	                        (rev 0)
+++ CalendarServer/trunk/twistedcaldav/directory/common.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -0,0 +1,147 @@
+# -*- test-case-name: twistedcaldav.test.test_wrapping,twistedcaldav.directory.test.test_calendar -*-
+##
+# Copyright (c) 2010 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from twisted.internet.defer import inlineCallbacks, returnValue
+from twext.web2.http import HTTPError
+from twext.web2 import responsecode
+from twext.web2.dav.util import joinURL
+from twistedcaldav.directory.util import transactionFromRequest
+from twistedcaldav.directory.resource import DirectoryReverseProxyResource
+
+__all__ = [
+    'uidsResourceName',
+    'CommonUIDProvisioningResource'
+]
+
+# Use __underbars__ convention to avoid conflicts with directory resource
+# types.
+
+uidsResourceName = "__uids__"
+
+class CommonUIDProvisioningResource(object):
+    """
+    Common ancestor for addressbook/calendar UID provisioning resources.
+
+    Must be mixed in to the hierarchy I{before} the appropriate resource type.
+    
+    @ivar homeResourceTypeName: The name of the home resource type ('calendars'
+        or 'addressbooks').
+
+    @ivar enabledAttribute: The name of the attribute of the directory record
+        which determines whether this should be enabled or not.
+    """
+
+    def __init__(self, parent):
+        """
+        @param parent: the parent of this resource
+        """
+
+        super(CommonUIDProvisioningResource, self).__init__()
+
+        self.directory = parent.directory
+        self.parent = parent
+
+
+    @inlineCallbacks
+    def homeResourceForRecord(self, record, request):
+
+        transaction = transactionFromRequest(request, self.parent._newStore)
+        name = record.uid
+
+        if record is None:
+            self.log_msg("No directory record with GUID %r" % (name,))
+            returnValue(None)
+
+        if not getattr(record, self.enabledAttribute):
+            self.log_msg("Directory record %r is not enabled for %s" % (
+                record, self.homeResourceTypeName))
+            returnValue(None)
+
+        assert len(name) > 4, "Directory record has an invalid GUID: %r" % (
+            name,)
+        
+        if record.locallyHosted():
+            child = yield self.homeResourceCreator(record, transaction)
+        else:
+            child = DirectoryReverseProxyResource(self, record)
+
+        returnValue(child)
+
+
+    @inlineCallbacks
+    def locateChild(self, request, segments):
+
+        name = segments[0]
+        if name == "":
+            returnValue((self, ()))
+
+        record = self.directory.recordWithUID(name)
+        if record:
+            child = yield self.homeResourceForRecord(record, request)
+            returnValue((child, segments[1:]))
+        else:
+            returnValue((None, ()))
+
+
+    def listChildren(self):
+        # Not a listable collection
+        raise HTTPError(responsecode.FORBIDDEN)
+
+    ##
+    # ACL
+    ##
+
+    def principalCollections(self):
+        return self.parent.principalCollections()
+
+    def principalForRecord(self, record):
+        return self.parent.principalForRecord(record)
+    ##
+    # DAV
+    ##
+    
+    def isCollection(self):
+        return True
+
+
+    def getChild(self, name, record=None):
+        raise NotImplementedError(self.__class__.__name__ +
+                                  ".getChild no longer exists.")
+
+    def displayName(self):
+        return uidsResourceName
+
+    def url(self):
+        return joinURL(self.parent.url(), uidsResourceName)
+
+
+
+class CommonHomeTypeProvisioningResource(object):
+
+    @inlineCallbacks
+    def locateChild(self, request, segments):
+        name = segments[0]
+        if name == "":
+            returnValue((self, segments[1:]))
+
+        record = self.directory.recordWithShortName(self.recordType, name)
+        if record is None:
+            returnValue((None, []))
+
+        child = yield self._parent.homeForDirectoryRecord(record, request)
+        returnValue((child, segments[1:]))
+

Modified: CalendarServer/trunk/twistedcaldav/directory/opendirectorybacker.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/opendirectorybacker.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/directory/opendirectorybacker.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -397,66 +397,7 @@
                     yield updateLock.release()
                     updateLock = None
                     
-                    #FIXME: implement store based cache
-#                    tmpDirLock = self._tmpDirAddressBookLock
-#                    self.log_debug("blocking on lock of: \"%s\")" % self._tmpDirAddressBookLockPath)
-#                    yield tmpDirLock.acquire()
-#                    
-#                    try:
-#                        self.log_info("Filling directory address book")
-#                        startTime = time.time()
-#                        newAddressBook = CalDAVFile(makeTmpFilename())
-#                        yield newAddressBook.createAddressBookCollection()
-#                        for key, record in records.items():
-#                            try:
-#                                vcard = record.vCard()
-#                                # make up a destination 
-#
-#                                fileName = unquote(record.uriName())
-#                                destination = CalDAVFile(join(newAddressBook.fp.path, fileName))
-#                                destination_uri =  record.hRef()
-#
-#                                self.log_debug("Adding \"%s\", uri=\"%s\"" % (fileName, destination_uri, ))
-#                                self.log_debug("VCard text =\n%s" % (record.vCardText(), ))
-#                                
-#                                yield StoreAddressObjectResource( request = None,
-#                                                            sourceadbk = False,
-#                                                            destinationadbk = True,
-#                                                            destination = destination,
-#                                                            destination_uri = destination_uri,
-#                                                            destinationparent = newAddressBook,
-#                                                            vcard = vcard,
-#                                                            indexdestination = False,
-#                                                            ).run()
-#                            except:
-#                                self.log_info("Could not add record %s" % (record,))
-#                                del records[key]
-#                                newAddressBookCTag = customxml.GETCTag(str(hash(self.baseGUID + ":" + self.realmName + ":" + "".join(str(hash(records[key])) for key in records.keys()))))
-#                        
-#                        self.log_info("Indexing new directory address book")
-#                        newAddressBook.index().recreate()
-#                        elaspedTime = time.time()-startTime
-#                        self.log_info("Timing: Fill address book: %.1f ms (%d vcards, %.2f vcards/sec)" % (elaspedTime*1000, len(records), len(records)/elaspedTime))
-#                        
-#                        updateLock = self.updateLock()
-#                        self.log_debug("blocking on lock of: \"%s\")" % self._updateLockPath)
-#                        yield updateLock.acquire()
-#
-#                        self.log_debug("Swapping in new directory address book")
-#                        
-#                        # move old address book out of the way
-#                        if self.directoryBackedAddressBook.fp.exists():               
-#                            os.rename(self.directoryBackedAddressBook.fp.path, makeTmpFilename())
-#        
-#                        #move new one into place
-#                        os.rename(newAddressBook.fp.path, self.directoryBackedAddressBook.fp.path)
-#                        self.directoryBackedAddressBook.fp.restat()
-#                        
-#                        self.directoryBackedAddressBook.writeDeadProperty(newAddressBookCTag)
-#                    finally:
-#                        self.log_debug("unlocking: \"%s\")" % self._tmpDirAddressBookLockPath)
-#                        yield tmpDirLock.release()
-    
+
                 if not keepLock:
                     self.log_debug("unlocking: \"%s\")" % self._updateLockPath)
                     yield updateLock.release()

Modified: CalendarServer/trunk/twistedcaldav/directory/principal.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/principal.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/directory/principal.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -55,6 +55,7 @@
 from twistedcaldav.directory import calendaruserproxy
 from twistedcaldav.directory import augment
 from twistedcaldav.directory.calendaruserproxy import CalendarUserProxyPrincipalResource
+from twistedcaldav.directory.common import uidsResourceName
 from twistedcaldav.directory.directory import DirectoryService, DirectoryRecord
 from twistedcaldav.extensions import ReadOnlyResourceMixIn, DAVPrincipalResource,\
     DAVResourceWithChildrenMixin
@@ -67,9 +68,6 @@
 
 log = Logger()
 
-# Use __underbars__ convention to avoid conflicts with directory resource types.
-uidsResourceName = "__uids__"
-
 class PermissionsMixIn (ReadOnlyResourceMixIn):
     def defaultAccessControlList(self):
         return authReadACL
@@ -867,26 +865,27 @@
         else:
             return False
 
+    @inlineCallbacks
     def scheduleInbox(self, request):
-        home = self.calendarHome(request)
+        home = yield self.calendarHome(request)
         if home is None:
-            return succeed(None)
+            returnValue(None)
 
-        inbox = home.getChild("inbox")
+        inbox = yield home.getChild("inbox")
         if inbox is None:
-            return succeed(None)
+            returnValue(None)
 
-        return succeed(inbox)
+        returnValue(inbox)
 
+    @inlineCallbacks
     def notificationCollection(self, request):
-        
+
         notification = None
         if config.Sharing.Enabled:
-            home = self.calendarHome(request)
+            home = yield self.calendarHome(request)
             if home is not None:    
-                notification = home.getChild("notification")
-    
-        return succeed(notification)
+                notification = yield home.getChild("notification")
+        returnValue(notification)
 
     def calendarHomeURLs(self):
         homeURL = self._homeChildURL(None)
@@ -929,14 +928,16 @@
         else:
             return joinURL(url, name) if name else url
 
+
     def calendarHome(self, request):
         # FIXME: self.record.service.calendarHomesCollection smells like a hack
         service = self.record.service
         if hasattr(service, "calendarHomesCollection"):
             return service.calendarHomesCollection.homeForDirectoryRecord(self.record, request)
         else:
-            return None
+            return succeed(None)
 
+
     def _addressBookHomeChildURL(self, name):
         if not hasattr(self, "addressBookHomeURL"):
             if not hasattr(self.record.service, "addressBookHomesCollection"):

Modified: CalendarServer/trunk/twistedcaldav/directory/test/test_calendar.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/test_calendar.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/directory/test/test_calendar.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -16,30 +16,51 @@
 
 from twisted.internet.defer import inlineCallbacks
 from twext.web2.dav import davxml
-from twext.web2.test.test_server import SimpleRequest
 
 from twistedcaldav import caldavxml
 
 from twistedcaldav.test.util import TestCase
+from twext.web2.test.test_server import SimpleRequest
+from twistedcaldav.directory.util import transactionFromRequest
 
 class ProvisionedCalendars (TestCase):
     """
     Directory service provisioned principals.
     """
+
+    @inlineCallbacks
     def setUp(self):
-        super(ProvisionedCalendars, self).setUp()
+        yield super(ProvisionedCalendars, self).setUp()
 
         self.createStockDirectoryService()
         self.setupCalendars()
 
 
+    def oneRequest(self, uri):
+        req = self._cleanupRequest = SimpleRequest(self.site, "GET", uri)
+        return req
+
+
+    def tearDown(self):
+        """
+        If the request started by this test has a transaction, commit it.
+        Otherwise, don't bother.
+        """
+        class JustForCleanup(object):
+            def newTransaction(self, *whatever):
+                return self
+            def commit(self):
+                return
+        return transactionFromRequest(self._cleanupRequest, JustForCleanup()).commit()
+
+
     def test_NonExistentCalendarHome(self):
 
         def _response(resource):
             if resource is not None:
                 self.fail("Incorrect response to GET on non-existent calendar home.")
 
-        request = SimpleRequest(self.site, "GET", "/calendars/users/12345/")
+        request = self.oneRequest("/calendars/users/12345/")
         d = request.locateResource(request.uri)
         d.addCallback(_response)
 
@@ -49,7 +70,7 @@
             if resource is None:
                 self.fail("Incorrect response to GET on existent calendar home.")
 
-        request = SimpleRequest(self.site, "GET", "/calendars/users/wsanchez/")
+        request = self.oneRequest("/calendars/users/wsanchez/")
         d = request.locateResource(request.uri)
         d.addCallback(_response)
 
@@ -59,7 +80,7 @@
             if resource is None:
                 self.fail("Incorrect response to GET on existent calendar.")
 
-        request = SimpleRequest(self.site, "GET", "/calendars/users/wsanchez/calendar/")
+        request = self.oneRequest("/calendars/users/wsanchez/calendar/")
         d = request.locateResource(request.uri)
         d.addCallback(_response)
 
@@ -69,14 +90,14 @@
             if resource is None:
                 self.fail("Incorrect response to GET on existent inbox.")
 
-        request = SimpleRequest(self.site, "GET", "/calendars/users/wsanchez/inbox/")
+        request = self.oneRequest("/calendars/users/wsanchez/inbox/")
         d = request.locateResource(request.uri)
         d.addCallback(_response)
 
     @inlineCallbacks
     def test_CalendarTranspProperty(self):
 
-        request = SimpleRequest(self.site, "GET", "/calendars/users/wsanchez/calendar/")
+        request = self.oneRequest("/calendars/users/wsanchez/calendar/")
 
         # Get calendar first
         calendar = (yield request.locateResource("/calendars/users/wsanchez/calendar/"))

Modified: CalendarServer/trunk/twistedcaldav/directory/test/test_principal.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/test_principal.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/directory/test/test_principal.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -68,6 +68,8 @@
 
         calendaruserproxy.ProxyDBService = calendaruserproxy.ProxySqliteDB(os.path.abspath(self.mktemp()))
 
+
+    @inlineCallbacks
     def test_hierarchy(self):
         """
         DirectoryPrincipalProvisioningResource.listChildren(),
@@ -92,7 +94,7 @@
             principalCollections = provisioningResource.principalCollections()
             self.assertEquals(set((provisioningURL,)), set(pc.principalCollectionURL() for pc in principalCollections))
 
-            recordTypes = set(provisioningResource.listChildren())
+            recordTypes = set((yield provisioningResource.listChildren()))
             self.assertEquals(recordTypes, set(directory.recordTypes()))
 
             for recordType in recordTypes:
@@ -106,7 +108,7 @@
                 principalCollections = typeResource.principalCollections()
                 self.assertEquals(set((provisioningURL,)), set(pc.principalCollectionURL() for pc in principalCollections))
 
-                shortNames = set(typeResource.listChildren())
+                shortNames = set((yield typeResource.listChildren()))
                 self.assertEquals(shortNames, set(r.shortNames[0] for r in directory.listRecords(recordType)))
 
                 for shortName in shortNames:
@@ -426,7 +428,7 @@
             for args in _authReadOnlyPrivileges(self, provisioningResource, provisioningResource.principalCollectionURL()):
                 yield self._checkPrivileges(*args)
 
-            for recordType in provisioningResource.listChildren():
+            for recordType in (yield provisioningResource.listChildren()):
                 #print "   -> %s" % (recordType,)
                 typeResource = provisioningResource.getChild(recordType)
 

Modified: CalendarServer/trunk/twistedcaldav/dropbox.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/dropbox.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/dropbox.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -30,7 +30,7 @@
 from twext.web2.dav.resource import DAVResource, TwistedACLInheritable
 from twext.web2.dav.util import joinURL
 
-from twisted.internet.defer import succeed, inlineCallbacks, returnValue
+from twisted.internet.defer import inlineCallbacks, returnValue
 
 from twistedcaldav.config import config
 from twistedcaldav.customxml import calendarserver_namespace
@@ -42,7 +42,7 @@
     Drop box collection resource.
     """
     def resourceType(self):
-        return davxml.ResourceType.dropboxhome
+        return davxml.ResourceType.dropboxhome #@UndefinedVariable
 
     def isCollection(self):
         return True
@@ -83,12 +83,15 @@
     """
     Drop box resource.
     """
+
     def resourceType(self):
-        return davxml.ResourceType.dropbox
+        return davxml.ResourceType.dropbox #@UndefinedVariable
 
+
     def isCollection(self):
         return True
 
+
     def writeNewACEs(self, newaces):
         """
         Write a new ACL to the resource's property store. We override this for calendar collections
@@ -108,10 +111,11 @@
                 edited_aces.append(davxml.ACE(*children))
             else:
                 edited_aces.append(ace)
-        
+
         # Do inherited with possibly modified set of aces
-        super(DropBoxCollectionResource, self).writeNewACEs(edited_aces)
+        return super(DropBoxCollectionResource, self).writeNewACEs(edited_aces)
 
+
     def http_PUT(self, request):
         return ErrorResponse(
             responsecode.FORBIDDEN,

Modified: CalendarServer/trunk/twistedcaldav/extensions.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/extensions.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/extensions.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -38,7 +38,7 @@
 from twisted.cred.error import LoginFailed, UnauthorizedLogin
 
 import twext.web2.server
-from twext.web2 import responsecode, iweb, http, server
+from twext.web2 import responsecode, server
 from twext.web2.auth.wrapper import UnauthorizedResponse
 from twext.web2.http import HTTPError, Response, RedirectResponse
 from twext.web2.http import StatusResponse
@@ -521,7 +521,7 @@
         ]
 
         even = Alternator()
-        for name in sorted(self.listChildren()):
+        for name in sorted((yield self.listChildren())):
             child = self.getChild(name)
 
             url, name, size, lastModified, contentType = self.getChildDirectoryEntry(child, name, request)
@@ -734,6 +734,7 @@
         self.putChildren = {}
         super(DAVResourceWithChildrenMixin, self).__init__(principalCollections=principalCollections)
 
+
     def putChild(self, name, child):
         """
         Register a child with the given name with this resource.
@@ -742,9 +743,12 @@
         """
         self.putChildren[name] = child
 
+
     def getChild(self, name):
         """
-        Look up a child resource.
+        Look up a child resource.  First check C{self.putChildren}, then call
+        C{self.makeChild} if no pre-existing children were found.
+
         @return: the child of this resource with the given name.
         """
         if name == "":
@@ -755,23 +759,34 @@
             result = self.makeChild(name)
         return result
 
+
     def makeChild(self, name):
-        # Subclasses with real children need to override this and return the appropriate object
+        """
+        Called by L{DAVResourceWithChildrenMixin.getChild} to dynamically
+        create children that have not been pre-created with C{putChild}.
+        """
         return None
 
+
     def listChildren(self):
         """
         @return: a sequence of the names of all known children of this resource.
         """
         return self.putChildren.keys()
 
+
     def locateChild(self, req, segments):
         """
-        See L{IResource}C{.locateChild}.
+        See L{IResource.locateChild}.
         """
-        # If getChild() finds a child resource, return it
-        return (self.getChild(segments[0]), segments[1:])
+        thisSegment = segments[0]
+        moreSegments = segments[1:]
+        return maybeDeferred(self.getChild, thisSegment).addCallback(
+            lambda it: (it, moreSegments)
+        )
 
+
+
 class DAVResourceWithoutChildrenMixin (object):
     """
     Bits needed from twext.web2.static
@@ -789,6 +804,8 @@
     def locateChild(self, request, segments):
         return self, server.StopTraversal
 
+
+
 class DAVPrincipalResource (DirectoryPrincipalPropertySearchMixIn,
                             SuperDAVPrincipalResource, LoggingMixIn,
                             DirectoryRenderingMixIn):
@@ -888,8 +905,8 @@
         if self.deadProperties().contains((dav_namespace, "resourcetype")):
             return self.deadProperties().get((dav_namespace, "resourcetype"))
         if self.isCollection():
-            return davxml.ResourceType.collection
-        return davxml.ResourceType.empty
+            return davxml.ResourceType.collection #@UndefinedVariable
+        return davxml.ResourceType.empty #@UndefinedVariable
 
     def render(self, request):
         if not self.fp.exists():

Modified: CalendarServer/trunk/twistedcaldav/fileops.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/fileops.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/fileops.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -1,111 +0,0 @@
-##
-# Copyright (c) 2005-2008 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-
-"""
-Various file utilities.
-"""
-
-from twext.web2.dav.fileop import copy
-from twext.web2.dav.fileop import put
-from twext.web2.dav.xattrprops import xattrPropertyStore
-
-# This class simulates a DAVFile with enough information for use with xattrPropertyStore.
-class FakeXAttrResource(object):
-    
-    def __init__(self, fp):
-        self.fp = fp
-
-def putWithXAttrs(stream, filepath):
-    """
-    Write a file to a possibly existing path and preserve any xattrs at that path.
-    
-    @param stream: the stream to write to the destination.
-    @type stream: C{file}
-    @param filepath: the destination file.
-    @type filepath: L{FilePath}
-    """
-    
-    # Preserve existings xattrs
-    props = []
-    if filepath.exists():
-        xold = xattrPropertyStore(FakeXAttrResource(filepath))
-        for item in xold.list():
-            props.append((xold.get(item)))
-        xold = None
-    
-    # First do the actual file copy
-    def _gotResponse(response):
-    
-        # Restore original xattrs.
-        if props:
-            xnew = xattrPropertyStore(FakeXAttrResource(filepath))
-            for prop in props:
-                xnew.set(prop)
-            xnew = None
-    
-        return response
-
-    d = put(stream, filepath)
-    d.addCallback(_gotResponse)
-    return d
-
-def copyWithXAttrs(source_filepath, destination_filepath, destination_uri):
-    """
-    Copy a file from one path to another and also copy xattrs we care about.
-    
-    @param source_filepath: the file to copy from
-    @type source_filepath: L{FilePath}
-    @param destination_filepath: the file to copy to
-    @type destination_filepath: L{FilePath}
-    @param destination_uri: the URI of the destination resource
-    @type destination_uri: C{str}
-    """
-    
-    # First do the actual file copy
-    def _gotResponse(response):
-    
-        # Now copy over xattrs.
-        copyXAttrs(source_filepath, destination_filepath)
-        
-        return response
-    
-    d = copy(source_filepath, destination_filepath, destination_uri, "0")
-    d.addCallback(_gotResponse)
-    return d
-
-def copyToWithXAttrs(from_fp, to_fp):
-    """
-    Copy a file from one path to another and also copy xattrs we care about.
-    
-    @param from_fp: file being copied
-    @type from_fp: L{FilePath}
-    @param to_fp: file to copy to
-    @type to_fp: L{FilePath}
-    """
-    
-    # First do the actual file copy.
-    from_fp.copyTo(to_fp)
-
-    # Now copy over xattrs.
-    copyXAttrs(from_fp, to_fp)
-
-def copyXAttrs(from_fp, to_fp):    
-    # Create xattr stores for each file and copy over all xattrs.
-    xfrom = xattrPropertyStore(FakeXAttrResource(from_fp))
-    xto = xattrPropertyStore(FakeXAttrResource(to_fp))
-
-    for item in xfrom.list():
-        xto.set(xfrom.get(item))

Modified: CalendarServer/trunk/twistedcaldav/icaldav.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/icaldav.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/icaldav.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -67,29 +67,25 @@
         Create a calendar collection for this resource.
         """
 
-    def iCalendar(name=None):
+    def iCalendar():
         """
         Instantiate an iCalendar component object representing this resource or
         its child with the given name.
         The behavior of this method is not specified if it is called on a
         resource that is not a calendar collection or a calendar resource within
         a calendar collection.
-        @param name: the name of the desired child of this resource, or None
-            if this resource is desired.  Must be None if this resource is
-            not a calendar collection.
+
         @return: a L{twistedcaldav.ical.Component} of type C{"VCALENDAR"}.
         """
 
-    def iCalendarText(name=None):
+    def iCalendarText():
         """
         Obtains the iCalendar text representing this resource or its child with
         the given name.
         The behavior of this method is not specified if it is called on a
         resource that is not a calendar collection or a calendar resource within
         a calendar collection.
-        @param name: the name of the desired child of this resource, or None
-            if this resource is desired.  Must be None if this resource is
-            not a calendar collection.
+
         @return: a string containing iCalendar text with a top-level component
             of type C{"VCALENDAR"}.
         """

Deleted: CalendarServer/trunk/twistedcaldav/index.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/index.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/index.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -1,1156 +0,0 @@
-# -*- test-case-name: twistedcaldav.test.test_index -*-
-##
-# Copyright (c) 2005-2010 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-
-"""
-CalDAV Index.
-
-This API is considered private to static.py and is therefore subject to
-change.
-"""
-
-__all__ = [
-    "db_basename",
-    "ReservationError",
-    "MemcachedUIDReserver",
-    "Index",
-    "IndexSchedule",
-    "IndexedSearchException",
-]
-
-import datetime
-import time
-import hashlib
-
-try:
-    import sqlite3 as sqlite
-except ImportError:
-    from pysqlite2 import dbapi2 as sqlite
-
-from vobject.icalendar import utc
-
-from twisted.internet.defer import maybeDeferred, succeed
-
-from twext.python.log import Logger, LoggingMixIn
-
-from twistedcaldav.ical import Component
-from twistedcaldav.query import calendarquery, calendarqueryfilter
-from twistedcaldav.sql import AbstractSQLDatabase
-from twistedcaldav.sql import db_prefix
-from twistedcaldav.instance import InvalidOverriddenInstanceError
-from twistedcaldav.config import config
-from twistedcaldav.memcachepool import CachePoolUserMixIn
-
-log = Logger()
-
-db_basename = db_prefix + "sqlite"
-schema_version = "10"
-collection_types = {"Calendar": "Regular Calendar Collection", "iTIP": "iTIP Calendar Collection"}
-
-icalfbtype_to_indexfbtype = {
-    "FREE"            : 'F',
-    "BUSY"            : 'B',
-    "BUSY-UNAVAILABLE": 'U',
-    "BUSY-TENTATIVE"  : 'T',
-}
-indexfbtype_to_icalfbtype = dict([(v, k) for k,v in icalfbtype_to_indexfbtype.iteritems()])
-
-#
-# Duration into the future through which recurrences are expanded in the index
-# by default.  This is a caching parameter which affects the size of the index;
-# it does not affect search results beyond this period, but it may affect
-# performance of such a search.
-#
-default_future_expansion_duration = datetime.timedelta(days=365*1)
-
-#
-# Maximum duration into the future through which recurrences are expanded in the
-# index.  This is a caching parameter which affects the size of the index; it
-# does not affect search results beyond this period, but it may affect
-# performance of such a search.
-#
-# When a search is performed on a time span that goes beyond that which is
-# expanded in the index, we have to open each resource which may have data in
-# that time period.  In order to avoid doing that multiple times, we want to
-# cache those results.  However, we don't necessarily want to cache all
-# occurrences into some obscenely far-in-the-future date, so we cap the caching
-# period.  Searches beyond this period will always be relatively expensive for
-# resources with occurrences beyond this period.
-#
-maximum_future_expansion_duration = datetime.timedelta(days=365*5)
-
-class ReservationError(LookupError):
-    """
-    Attempt to reserve a UID which is already reserved or to unreserve a UID
-    which is not reserved.
-    """
-
-class IndexedSearchException(ValueError):
-    pass
-
-class SyncTokenValidException(ValueError):
-    pass
-
-class AbstractCalendarIndex(AbstractSQLDatabase, LoggingMixIn):
-    """
-    Calendar collection index abstract base class that defines the apis for the index.
-    This will be subclassed for the two types of index behaviour we need: one for
-    regular calendar collections, one for schedule calendar collections.
-    """
-
-    def __init__(self, resource):
-        """
-        @param resource: the L{CalDAVResource} resource to
-            index. C{resource} must be a calendar collection (ie.
-            C{resource.isPseudoCalendarCollection()} returns C{True}.)
-        """
-        self.resource = resource
-        db_filename = self.resource.fp.child(db_basename).path
-        super(AbstractCalendarIndex, self).__init__(db_filename, False)
-
-    def create(self):
-        """
-        Create the index and initialize it.
-        """
-        self._db()
-
-    def reserveUID(self, uid):
-        """
-        Reserve a UID for this index's resource.
-        @param uid: the UID to reserve
-        @raise ReservationError: if C{uid} is already reserved
-        """
-        raise NotImplementedError
-
-    def unreserveUID(self, uid):
-        """
-        Unreserve a UID for this index's resource.
-        @param uid: the UID to reserve
-        @raise ReservationError: if C{uid} is not reserved
-        """
-        raise NotImplementedError
-
-    def isReservedUID(self, uid):
-        """
-        Check to see whether a UID is reserved.
-        @param uid: the UID to check
-        @return: True if C{uid} is reserved, False otherwise.
-        """
-        raise NotImplementedError
-
-    def isAllowedUID(self, uid, *names):
-        """
-        Checks to see whether to allow an operation with adds the the specified
-        UID is allowed to the index.  Specifically, the operation may not
-        violate the constraint that UIDs must be unique, and the UID must not
-        be reserved.
-        @param uid: the UID to check
-        @param names: the names of resources being replaced or deleted by the
-            operation; UIDs associated with these resources are not checked.
-        @return: True if the UID is not in the index and is not reserved,
-            False otherwise.
-        """
-        raise NotImplementedError
-
-    def resourceNamesForUID(self, uid):
-        """
-        Looks up the names of the resources with the given UID.
-        @param uid: the UID of the resources to look up.
-        @return: a list of resource names
-        """
-        names = self._db_values_for_sql("select NAME from RESOURCE where UID = :1", uid)
-
-        #
-        # Check that each name exists as a child of self.resource.  If not, the
-        # resource record is stale.
-        #
-        resources = []
-        for name in names:
-            name_utf8 = name.encode("utf-8")
-            if name is not None and self.resource.getChild(name_utf8) is None:
-                # Clean up
-                log.err("Stale resource record found for child %s with UID %s in %s" % (name, uid, self.resource))
-                self._delete_from_db(name, uid, False)
-                self._db_commit()
-            else:
-                resources.append(name_utf8)
-
-        return resources
-
-    def resourceNameForUID(self, uid):
-        """
-        Looks up the name of the resource with the given UID.
-        @param uid: the UID of the resource to look up.
-        @return: If the resource is found, its name; C{None} otherwise.
-        """
-        result = None
-
-        for name in self.resourceNamesForUID(uid):
-            assert result is None, "More than one resource with UID %s in calendar collection %r" % (uid, self)
-            result = name
-
-        return result
-
-    def resourceUIDForName(self, name):
-        """
-        Looks up the UID of the resource with the given name.
-        @param name: the name of the resource to look up.
-        @return: If the resource is found, the UID of the resource; C{None}
-            otherwise.
-        """
-        uid = self._db_value_for_sql("select UID from RESOURCE where NAME = :1", name)
-
-        return uid
-
-    def addResource(self, name, calendar, fast=False, reCreate=False):
-        """
-        Adding or updating an existing resource.
-        To check for an update we attempt to get an existing UID
-        for the resource name. If present, then the index entries for
-        that UID are removed. After that the new index entries are added.
-        @param name: the name of the resource to add.
-        @param calendar: a L{Calendar} object representing the resource
-            contents.
-        @param fast: if C{True} do not do commit, otherwise do commit.
-        """
-        oldUID = self.resourceUIDForName(name)
-        if oldUID is not None:
-            self._delete_from_db(name, oldUID, False)
-        self._add_to_db(name, calendar, reCreate=reCreate)
-        if not fast:
-            self._db_commit()
-
-    def deleteResource(self, name):
-        """
-        Remove this resource from the index.
-        @param name: the name of the resource to add.
-        @param uid: the UID of the calendar component in the resource.
-        """
-        uid = self.resourceUIDForName(name)
-        if uid is not None:
-            self._delete_from_db(name, uid)
-            self._db_commit()
-
-    def resourceExists(self, name):
-        """
-        Determines whether the specified resource name exists in the index.
-        @param name: the name of the resource to test
-        @return: True if the resource exists, False if not
-        """
-        uid = self._db_value_for_sql("select UID from RESOURCE where NAME = :1", name)
-        return uid is not None
-
-    def resourcesExist(self, names):
-        """
-        Determines whether the specified resource name exists in the index.
-        @param names: a C{list} containing the names of the resources to test
-        @return: a C{list} of all names that exist
-        """
-        statement = "select NAME from RESOURCE where NAME in ("
-        for ctr in (item[0] for item in enumerate(names)):
-            if ctr != 0:
-                statement += ", "
-            statement += ":%s" % (ctr,)
-        statement += ")"
-        results = self._db_values_for_sql(statement, *names)
-        return results
-
-
-    def testAndUpdateIndex(self, minDate):
-        # Find out if the index is expanded far enough
-        names = self.notExpandedBeyond(minDate)
-        # Actually expand recurrence max
-        for name in names:
-            self.log_info("Search falls outside range of index for %s %s" % (name, minDate))
-            self.reExpandResource(name, minDate)
-
-    def whatchanged(self, revision, depth):
-
-        results = [(name.encode("utf-8"), deleted) for name, deleted in self._db_execute("select NAME, DELETED from REVISIONS where REVISION > :1", revision)]
-        results.sort(key=lambda x:x[1])
-        
-        changed = []
-        deleted = []
-        for name, wasdeleted in results:
-            if name:
-                if wasdeleted == 'Y':
-                    if revision:
-                        deleted.append(name)
-                else:
-                    changed.append(name)
-            else:
-                raise SyncTokenValidException
-        
-        return changed, deleted,
-
-    def lastRevision(self):
-        return self._db_value_for_sql(
-            "select REVISION from REVISION_SEQUENCE"
-        )
-
-    def bumpRevision(self, fast=False):
-        self._db_execute(
-            """
-            update REVISION_SEQUENCE set REVISION = REVISION + 1
-            """,
-        )
-        self._db_commit()
-        return self._db_value_for_sql(
-            """
-            select REVISION from REVISION_SEQUENCE
-            """,
-        )
-
-    def indexedSearch(self, filter, useruid="", fbtype=False):
-        """
-        Finds resources matching the given qualifiers.
-        @param filter: the L{Filter} for the calendar-query to execute.
-        @return: an iterable of tuples for each resource matching the
-            given C{qualifiers}. The tuples are C{(name, uid, type)}, where
-            C{name} is the resource name, C{uid} is the resource UID, and
-            C{type} is the resource iCalendar component type.
-        """
-
-        # Make sure we have a proper Filter element and get the partial SQL
-        # statement to use.
-        if isinstance(filter, calendarqueryfilter.Filter):
-            if fbtype:
-                # Lookup the useruid - try the empty (default) one if needed
-                dbuseruid = self._db_value_for_sql(
-                    "select PERUSERID from PERUSER where USERUID == :1",
-                    useruid,
-                )
-            else:
-                dbuseruid = ""
-
-            qualifiers = calendarquery.sqlcalendarquery(filter, None, dbuseruid)
-            if qualifiers is not None:
-                # Determine how far we need to extend the current expansion of
-                # events. If we have an open-ended time-range we will expand one
-                # year past the start. That should catch bounded recurrences - unbounded
-                # will have been indexed with an "infinite" value always included.
-                maxDate, isStartDate = filter.getmaxtimerange()
-                if maxDate:
-                    maxDate = maxDate.date()
-                    if isStartDate:
-                        maxDate += datetime.timedelta(days=365)
-                    self.testAndUpdateIndex(maxDate)
-            else:
-                # We cannot handler this filter in an indexed search
-                raise IndexedSearchException()
-
-        else:
-            qualifiers = None
-
-        # Perform the search
-        if qualifiers is None:
-            rowiter = self._db_execute("select NAME, UID, TYPE from RESOURCE")
-        else:
-            if fbtype:
-                # Lookup the useruid - try the empty (default) one if needed
-                dbuseruid = self._db_value_for_sql(
-                    "select PERUSERID from PERUSER where USERUID == :1",
-                    useruid,
-                )
-
-                # For a free-busy time-range query we return all instances
-                rowiter = self._db_execute(
-                    "select DISTINCT RESOURCE.NAME, RESOURCE.UID, RESOURCE.TYPE, RESOURCE.ORGANIZER, TIMESPAN.FLOAT, TIMESPAN.START, TIMESPAN.END, TIMESPAN.FBTYPE, TIMESPAN.TRANSPARENT, TRANSPARENCY.TRANSPARENT" + 
-                    qualifiers[0],
-                    *qualifiers[1]
-                )
-            else:
-                rowiter = self._db_execute("select DISTINCT RESOURCE.NAME, RESOURCE.UID, RESOURCE.TYPE" + qualifiers[0], *qualifiers[1])
-
-        # Check result for missing resources
-
-        for row in rowiter:
-            name = row[0]
-            if self.resource.getChild(name.encode("utf-8")):
-                if fbtype:
-                    row = list(row)
-                    if row[9]:
-                        row[8] = row[9]
-                    del row[9]
-                yield row
-            else:
-                log.err("Calendar resource %s is missing from %s. Removing from index."
-                        % (name, self.resource))
-                self.deleteResource(name)
-
-    def bruteForceSearch(self):
-        """
-        List the whole index and tests for existence, updating the index
-        @return: all resources in the index
-        """
-        # List all resources
-        rowiter = self._db_execute("select NAME, UID, TYPE from RESOURCE")
-
-        # Check result for missing resources:
-
-        for row in rowiter:
-            name = row[0]
-            if self.resource.getChild(name.encode("utf-8")):
-                yield row
-            else:
-                log.err("Calendar resource %s is missing from %s. Removing from index."
-                        % (name, self.resource))
-                self.deleteResource(name)
-
-
-    def _db_version(self):
-        """
-        @return: the schema version assigned to this index.
-        """
-        return schema_version
-
-    def _add_to_db(self, name, calendar, cursor=None, expand_until=None, reCreate=False):
-        """
-        Records the given calendar resource in the index with the given name.
-        Resource names and UIDs must both be unique; only one resource name may
-        be associated with any given UID and vice versa.
-        NB This method does not commit the changes to the db - the caller
-        MUST take care of that
-        @param name: the name of the resource to add.
-        @param calendar: a L{Calendar} object representing the resource
-            contents.
-        """
-        raise NotImplementedError
-
-    def _delete_from_db(self, name, uid, dorevision=True):
-        """
-        Deletes the specified entry from all dbs.
-        @param name: the name of the resource to delete.
-        @param uid: the uid of the resource to delete.
-        """
-        raise NotImplementedError
-
-class CalendarIndex (AbstractCalendarIndex):
-    """
-    Calendar index - abstract class for indexer that indexes calendar objects in a collection.
-    """
-
-    def __init__(self, resource):
-        """
-        @param resource: the L{CalDAVResource} resource to
-            index.
-        """
-        super(CalendarIndex, self).__init__(resource)
-
-    def _db_init_data_tables_base(self, q, uidunique):
-        """
-        Initialise the underlying database tables.
-        @param q:           a database cursor to use.
-        """
-        #
-        # RESOURCE table is the primary index table
-        #   NAME: Last URI component (eg. <uid>.ics, RESOURCE primary key)
-        #   UID: iCalendar UID (may or may not be unique)
-        #   TYPE: iCalendar component type
-        #   RECURRANCE_MAX: Highest date of recurrence expansion
-        #   ORGANIZER: cu-address of the Organizer of the event
-        #
-        q.execute(
-            """
-            create table RESOURCE (
-                RESOURCEID     integer primary key autoincrement,
-                NAME           text unique,
-                UID            text%s,
-                TYPE           text,
-                RECURRANCE_MAX date,
-                ORGANIZER      text
-            )
-            """ % (" unique" if uidunique else "",)
-        )
-
-        #
-        # TIMESPAN table tracks (expanded) time spans for resources
-        #   NAME: Related resource (RESOURCE foreign key)
-        #   FLOAT: 'Y' if start/end are floating, 'N' otherwise
-        #   START: Start date
-        #   END: End date
-        #   FBTYPE: FBTYPE value:
-        #     '?' - unknown
-        #     'F' - free
-        #     'B' - busy
-        #     'U' - busy-unavailable
-        #     'T' - busy-tentative
-        #   TRANSPARENT: Y if transparent, N if opaque (default non-per-user value)
-        #
-        q.execute(
-            """
-            create table TIMESPAN (
-                INSTANCEID   integer primary key autoincrement,
-                RESOURCEID   integer,
-                FLOAT        text(1),
-                START        date,
-                END          date,
-                FBTYPE       text(1),
-                TRANSPARENT  text(1)
-            )
-            """
-        )
-        q.execute(
-            """
-            create index STARTENDFLOAT on TIMESPAN (START, END, FLOAT)
-            """
-        )
-
-        #
-        # PERUSER table tracks per-user ids
-        #   PERUSERID: autoincrement primary key
-        #   UID: User ID used in calendar data
-        #
-        q.execute(
-            """
-            create table PERUSER (
-                PERUSERID       integer primary key autoincrement,
-                USERUID         text
-            )
-            """
-        )
-        q.execute(
-            """
-            create index PERUSER_UID on PERUSER (USERUID)
-            """
-        )
-
-        #
-        # TRANSPARENCY table tracks per-user per-instance transparency
-        #   PERUSERID: user id key
-        #   INSTANCEID: instance id key
-        #   TRANSPARENT: Y if transparent, N if opaque
-        #
-        q.execute(
-            """
-            create table TRANSPARENCY (
-                PERUSERID       integer,
-                INSTANCEID      integer,
-                TRANSPARENT     text(1)
-            )
-            """
-        )
-
-        #
-        # REVISIONS table tracks changes
-        #   NAME: Last URI component (eg. <uid>.ics, RESOURCE primary key)
-        #   REVISION: revision number
-        #   WASDELETED: Y if revision deleted, N if added or changed
-        #
-        q.execute(
-            """
-            create table REVISION_SEQUENCE (
-                REVISION        integer
-            )
-            """
-        )
-        q.execute(
-            """
-            insert into REVISION_SEQUENCE (REVISION) values (0)
-            """
-        )
-        q.execute(
-            """
-            create table REVISIONS (
-                NAME            text unique,
-                REVISION        integer,
-                DELETED         text(1)
-            )
-            """
-        )
-        q.execute(
-            """
-            create index REVISION on REVISIONS (REVISION)
-            """
-        )
-
-        if uidunique:
-            #
-            # RESERVED table tracks reserved UIDs
-            #   UID: The UID being reserved
-            #   TIME: When the reservation was made
-            #
-            q.execute(
-                """
-                create table RESERVED (
-                    UID  text unique,
-                    TIME date
-                )
-                """
-            )
-
-        # Cascading triggers to help on delete
-        q.execute(
-            """
-            create trigger resourceDelete after delete on RESOURCE
-            for each row
-            begin
-                delete from TIMESPAN where TIMESPAN.RESOURCEID = OLD.RESOURCEID;
-            end
-            """
-        )
-        q.execute(
-            """
-            create trigger timespanDelete after delete on TIMESPAN
-            for each row
-            begin
-                delete from TRANSPARENCY where INSTANCEID = OLD.INSTANCEID;
-            end
-            """
-        )
-        
-    def _db_can_upgrade(self, old_version):
-        """
-        Can we do an in-place upgrade
-        """
-        
-        # v10 is a big change - no upgrade possible
-        return False
-
-    def _db_upgrade_data_tables(self, q, old_version):
-        """
-        Upgrade the data from an older version of the DB.
-        """
-
-        # v10 is a big change - no upgrade possible
-        pass
-
-    def notExpandedBeyond(self, minDate):
-        """
-        Gives all resources which have not been expanded beyond a given date
-        in the index
-        """
-        return self._db_values_for_sql("select NAME from RESOURCE where RECURRANCE_MAX < :1", minDate)
-
-    def reExpandResource(self, name, expand_until):
-        """
-        Given a resource name, remove it from the database and re-add it
-        with a longer expansion.
-        """
-        calendar = self.resource.getChild(name).iCalendar()
-        self._add_to_db(name, calendar, expand_until=expand_until, reCreate=True)
-        self._db_commit()
-
-    def _add_to_db(self, name, calendar, cursor = None, expand_until=None, reCreate=False):
-        """
-        Records the given calendar resource in the index with the given name.
-        Resource names and UIDs must both be unique; only one resource name may
-        be associated with any given UID and vice versa.
-        NB This method does not commit the changes to the db - the caller
-        MUST take care of that
-        @param name: the name of the resource to add.
-        @param calendar: a L{Calendar} object representing the resource
-            contents.
-        """
-        uid = calendar.resourceUID()
-        organizer = calendar.getOrganizer()
-        if not organizer:
-            organizer = ""
-
-        # Decide how far to expand based on the component
-        master = calendar.masterComponent()
-        if master is None or not calendar.isRecurring() and not calendar.isRecurringUnbounded():
-            # When there is no master we have a set of overridden components - index them all.
-            # When there is one instance - index it.
-            # When bounded - index all.
-            expand = datetime.datetime(2100, 1, 1, 0, 0, 0, tzinfo=utc)
-        else:
-            if expand_until:
-                expand = expand_until
-            else:
-                expand = datetime.date.today() + default_future_expansion_duration
-    
-            if expand > (datetime.date.today() + maximum_future_expansion_duration):
-                raise IndexedSearchException
-
-        try:
-            instances = calendar.expandTimeRanges(expand, ignoreInvalidInstances=reCreate)
-        except InvalidOverriddenInstanceError, e:
-            log.err("Invalid instance %s when indexing %s in %s" % (e.rid, name, self.resource,))
-            raise
-
-        self._delete_from_db(name, uid, False)
-
-        # Add RESOURCE item
-        self._db_execute(
-            """
-            insert into RESOURCE (NAME, UID, TYPE, RECURRANCE_MAX, ORGANIZER)
-            values (:1, :2, :3, :4, :5)
-            """, name, uid, calendar.resourceType(), instances.limit, organizer
-        )
-        resourceid = self.lastrowid
-
-        # Get a set of all referenced per-user UIDs and map those to entries already
-        # in the DB and add new ones as needed
-        useruids = calendar.allPerUserUIDs()
-        useruids.add("")
-        useruidmap = {}
-        for useruid in useruids:
-            peruserid = self._db_value_for_sql(
-                "select PERUSERID from PERUSER where USERUID = :1",
-                useruid
-            )
-            if peruserid is None:
-                self._db_execute(
-                    """
-                    insert into PERUSER (USERUID)
-                    values (:1)
-                    """, useruid
-                )
-                peruserid = self.lastrowid
-            useruidmap[useruid] = peruserid
-            
-        for key in instances:
-            instance = instances[key]
-            start = instance.start.replace(tzinfo=utc)
-            end = instance.end.replace(tzinfo=utc)
-            float = 'Y' if instance.start.tzinfo is None else 'N'
-            transp = 'T' if instance.component.propertyValue("TRANSP") == "TRANSPARENT" else 'F'
-            self._db_execute(
-                """
-                insert into TIMESPAN (RESOURCEID, FLOAT, START, END, FBTYPE, TRANSPARENT)
-                values (:1, :2, :3, :4, :5, :6)
-                """,
-                resourceid,
-                float,
-                start,
-                end,
-                icalfbtype_to_indexfbtype.get(instance.component.getFBType(), 'F'),
-                transp
-            )
-            instanceid = self.lastrowid
-            peruserdata = calendar.perUserTransparency(instance.rid)
-            for useruid, transp in peruserdata:
-                peruserid = useruidmap[useruid]
-                self._db_execute(
-                    """
-                    insert into TRANSPARENCY (PERUSERID, INSTANCEID, TRANSPARENT)
-                    values (:1, :2, :3)
-                    """, peruserid, instanceid, 'T' if transp else 'F'
-                )
-                    
-
-        # Special - for unbounded recurrence we insert a value for "infinity"
-        # that will allow an open-ended time-range to always match it.
-        if calendar.isRecurringUnbounded():
-            start = datetime.datetime(2100, 1, 1, 0, 0, 0, tzinfo=utc)
-            end = datetime.datetime(2100, 1, 1, 1, 0, 0, tzinfo=utc)
-            float = 'N'
-            self._db_execute(
-                """
-                insert into TIMESPAN (RESOURCEID, FLOAT, START, END, FBTYPE, TRANSPARENT)
-                values (:1, :2, :3, :4, :5, :6)
-                """, resourceid, float, start, end, '?', '?'
-            )
-            instanceid = self.lastrowid
-            peruserdata = calendar.perUserTransparency(None)
-            for useruid, transp in peruserdata:
-                peruserid = useruidmap[useruid]
-                self._db_execute(
-                    """
-                    insert into TRANSPARENCY (PERUSERID, INSTANCEID, TRANSPARENT)
-                    values (:1, :2, :3)
-                    """, peruserid, instanceid, 'T' if transp else 'F'
-                )
-            
-        self._db_execute(
-            """
-            insert or replace into REVISIONS (NAME, REVISION, DELETED)
-            values (:1, :2, :3)
-            """, name, self.bumpRevision(fast=True), 'N',
-        )
-
-    def _delete_from_db(self, name, uid, dorevision=True):
-        """
-        Deletes the specified entry from all dbs.
-        @param name: the name of the resource to delete.
-        @param uid: the uid of the resource to delete.
-        """
-        self._db_execute("delete from RESOURCE where NAME = :1", name)
-        if dorevision:
-            self._db_execute(
-                """
-                update REVISIONS SET REVISION = :1, DELETED = :2
-                where NAME = :3
-                """, self.bumpRevision(fast=True), 'Y', name
-            )
-
-
-def wrapInDeferred(f):
-    def _(*args, **kwargs):
-        return maybeDeferred(f, *args, **kwargs)
-
-    return _
-
-
-class MemcachedUIDReserver(CachePoolUserMixIn, LoggingMixIn):
-    def __init__(self, index, cachePool=None):
-        self.index = index
-        self._cachePool = cachePool
-
-    def _key(self, uid):
-        return 'reservation:%s' % (
-            hashlib.md5('%s:%s' % (uid,
-                                   self.index.resource.fp.path)).hexdigest())
-
-    def reserveUID(self, uid):
-        uid = uid.encode('utf-8')
-        self.log_debug("Reserving UID %r @ %r" % (
-                uid,
-                self.index.resource.fp.path))
-
-        def _handleFalse(result):
-            if result is False:
-                raise ReservationError(
-                    "UID %s already reserved for calendar collection %s."
-                    % (uid, self.index.resource)
-                    )
-
-        d = self.getCachePool().add(self._key(uid),
-                                    'reserved',
-                                    expireTime=config.UIDReservationTimeOut)
-        d.addCallback(_handleFalse)
-        return d
-
-
-    def unreserveUID(self, uid):
-        uid = uid.encode('utf-8')
-        self.log_debug("Unreserving UID %r @ %r" % (
-                uid,
-                self.index.resource.fp.path))
-
-        def _handleFalse(result):
-            if result is False:
-                raise ReservationError(
-                    "UID %s is not reserved for calendar collection %s."
-                    % (uid, self.index.resource)
-                    )
-
-        d =self.getCachePool().delete(self._key(uid))
-        d.addCallback(_handleFalse)
-        return d
-
-
-    def isReservedUID(self, uid):
-        uid = uid.encode('utf-8')
-        self.log_debug("Is reserved UID %r @ %r" % (
-                uid,
-                self.index.resource.fp.path))
-
-        def _checkValue((flags, value)):
-            if value is None:
-                return False
-            else:
-                return True
-
-        d = self.getCachePool().get(self._key(uid))
-        d.addCallback(_checkValue)
-        return d
-
-
-
-class SQLUIDReserver(object):
-    def __init__(self, index):
-        self.index = index
-
-    @wrapInDeferred
-    def reserveUID(self, uid):
-        """
-        Reserve a UID for this index's resource.
-        @param uid: the UID to reserve
-        @raise ReservationError: if C{uid} is already reserved
-        """
-
-        try:
-            self.index._db_execute("insert into RESERVED (UID, TIME) values (:1, :2)", uid, datetime.datetime.now())
-            self.index._db_commit()
-        except sqlite.IntegrityError:
-            self.index._db_rollback()
-            raise ReservationError(
-                "UID %s already reserved for calendar collection %s."
-                % (uid, self.index.resource)
-            )
-        except sqlite.Error, e:
-            log.err("Unable to reserve UID: %s", (e,))
-            self.index._db_rollback()
-            raise
-
-    def unreserveUID(self, uid):
-        """
-        Unreserve a UID for this index's resource.
-        @param uid: the UID to reserve
-        @raise ReservationError: if C{uid} is not reserved
-        """
-
-        def _cb(result):
-            if result == False:
-                raise ReservationError(
-                    "UID %s is not reserved for calendar collection %s."
-                    % (uid, self.index.resource)
-                    )
-            else:
-                try:
-                    self.index._db_execute(
-                        "delete from RESERVED where UID = :1", uid)
-                    self.index._db_commit()
-                except sqlite.Error, e:
-                    log.err("Unable to unreserve UID: %s", (e,))
-                    self.index._db_rollback()
-                    raise
-
-        d = self.isReservedUID(uid)
-        d.addCallback(_cb)
-        return d
-
-
-    @wrapInDeferred
-    def isReservedUID(self, uid):
-        """
-        Check to see whether a UID is reserved.
-        @param uid: the UID to check
-        @return: True if C{uid} is reserved, False otherwise.
-        """
-
-        rowiter = self.index._db_execute("select UID, TIME from RESERVED where UID = :1", uid)
-        for uid, attime in rowiter:
-            # Double check that the time is within a reasonable period of now
-            # otherwise we probably have a stale reservation
-            tm = time.strptime(attime[:19], "%Y-%m-%d %H:%M:%S")
-            dt = datetime.datetime(year=tm.tm_year, month=tm.tm_mon, day=tm.tm_mday, hour=tm.tm_hour, minute=tm.tm_min, second = tm.tm_sec)
-            if datetime.datetime.now() - dt > datetime.timedelta(seconds=config.UIDReservationTimeOut):
-                try:
-                    self.index._db_execute("delete from RESERVED where UID = :1", uid)
-                    self.index._db_commit()
-                except sqlite.Error, e:
-                    log.err("Unable to unreserve UID: %s", (e,))
-                    self.index._db_rollback()
-                    raise
-                return False
-            else:
-                return True
-
-        return False
-
-
-
-class Index (CalendarIndex):
-    """
-    Calendar collection index - regular collection that enforces CalDAV UID uniqueness requirement.
-    """
-
-    def __init__(self, resource):
-        """
-        @param resource: the L{CalDAVResource} resource to
-            index. C{resource} must be a calendar collection (i.e.
-            C{resource.isPseudoCalendarCollection()} returns C{True}.)
-        """
-        assert resource.isCalendarCollection(), "non-calendar collection resource %s has no index." % (resource,)
-        super(Index, self).__init__(resource)
-
-        if (
-            hasattr(config, "Memcached") and
-            config.Memcached.Pools.Default.ClientEnabled
-        ):
-            self.reserver = MemcachedUIDReserver(self)
-        else:
-            self.reserver = SQLUIDReserver(self)
-
-    #
-    # A dict of sets. The dict keys are calendar collection paths,
-    # and the sets contains reserved UIDs for each path.
-    #
-
-    def reserveUID(self, uid):
-        return self.reserver.reserveUID(uid)
-
-
-    def unreserveUID(self, uid):
-        return self.reserver.unreserveUID(uid)
-
-
-    def isReservedUID(self, uid):
-        return self.reserver.isReservedUID(uid)
-
-
-    def isAllowedUID(self, uid, *names):
-        """
-        Checks to see whether to allow an operation which would add the
-        specified UID to the index.  Specifically, the operation may not
-        violate the constraint that UIDs must be unique.
-        @param uid: the UID to check
-        @param names: the names of resources being replaced or deleted by the
-            operation; UIDs associated with these resources are not checked.
-        @return: True if the UID is not in the index and is not reserved,
-            False otherwise.
-        """
-        rname = self.resourceNameForUID(uid)
-        return (rname is None or rname in names)
-
-    def _db_type(self):
-        """
-        @return: the collection type assigned to this index.
-        """
-        return collection_types["Calendar"]
-
-    def _db_init_data_tables(self, q):
-        """
-        Initialise the underlying database tables.
-        @param q:           a database cursor to use.
-        """
-
-        # Create database where the RESOURCE table has unique UID column.
-        self._db_init_data_tables_base(q, True)
-
-    def _db_recreate(self, do_commit=True):
-        """
-        Re-create the database tables from existing calendar data.
-        """
-
-        #
-        # Populate the DB with data from already existing resources.
-        # This allows for index recovery if the DB file gets
-        # deleted.
-        #
-        fp = self.resource.fp
-        for name in fp.listdir():
-            if name.startswith("."):
-                continue
-
-            try:
-                stream = fp.child(name).open()
-            except (IOError, OSError), e:
-                log.err("Unable to open resource %s: %s" % (name, e))
-                continue
-
-            # FIXME: This is blocking I/O
-            try:
-                calendar = Component.fromStream(stream)
-                calendar.validateForCalDAV()
-            except ValueError:
-                log.err("Non-calendar resource: %s" % (name,))
-            else:
-                #log.msg("Indexing resource: %s" % (name,))
-                self.addResource(name, calendar, True, reCreate=True)
-            finally:
-                stream.close()
-
-        # Do commit outside of the loop for better performance
-        if do_commit:
-            self._db_commit()
-
-class IndexSchedule (CalendarIndex):
-    """
-    Schedule collection index - does not require UID uniqueness.
-    """
-
-    def reserveUID(self, uid): #@UnusedVariable
-        """
-        Reserve a UID for this index's resource.
-        @param uid: the UID to reserve
-        @raise ReservationError: if C{uid} is already reserved
-        """
-
-        # iTIP does not require unique UIDs
-        return succeed(None)
-
-    def unreserveUID(self, uid): #@UnusedVariable
-        """
-        Unreserve a UID for this index's resource.
-        @param uid: the UID to reserve
-        @raise ReservationError: if C{uid} is not reserved
-        """
-
-        # iTIP does not require unique UIDs
-        return succeed(None)
-
-    def isReservedUID(self, uid): #@UnusedVariable
-        """
-        Check to see whether a UID is reserved.
-        @param uid: the UID to check
-        @return: True if C{uid} is reserved, False otherwise.
-        """
-
-        # iTIP does not require unique UIDs
-        return succeed(False)
-
-    def isAllowedUID(self, uid, *names): #@UnusedVariable
-        """
-        Checks to see whether to allow an operation with adds the the specified
-        UID is allowed to the index.  Specifically, the operation may not
-        violate the constraint that UIDs must be unique, and the UID must not
-        be reserved.
-        @param uid: the UID to check
-        @param names: the names of resources being replaced or deleted by the
-            operation; UIDs associated with these resources are not checked.
-        @return: True if the UID is not in the index and is not reserved,
-            False otherwise.
-        """
-
-        # iTIP does not require unique UIDs
-        return True
-
-    def _db_type(self):
-        """
-        @return: the collection type assigned to this index.
-        """
-        return collection_types["iTIP"]
-
-    def _db_init_data_tables(self, q):
-        """
-        Initialise the underlying database tables.
-        @param q:           a database cursor to use.
-        """
-
-        # Create database where the RESOURCE table has a UID column that is not unique.
-        self._db_init_data_tables_base(q, False)
-
-    def _db_recreate(self, do_commit=True):
-        """
-        Re-create the database tables from existing calendar data.
-        """
-
-        #
-        # Populate the DB with data from already existing resources.
-        # This allows for index recovery if the DB file gets
-        # deleted.
-        #
-        fp = self.resource.fp
-        for name in fp.listdir():
-            if name.startswith("."):
-                continue
-
-            try:
-                stream = fp.child(name).open()
-            except (IOError, OSError), e:
-                log.err("Unable to open resource %s: %s" % (name, e))
-                continue
-
-            # FIXME: This is blocking I/O
-            try:
-                calendar = Component.fromStream(stream)
-                calendar.validCalendarForCalDAV()
-                calendar.validateComponentsForCalDAV(True)
-            except ValueError:
-                log.err("Non-calendar resource: %s" % (name,))
-            else:
-                #log.msg("Indexing resource: %s" % (name,))
-                self.addResource(name, calendar, True, reCreate=True)
-            finally:
-                stream.close()
-
-        # Do commit outside of the loop for better performance
-        if do_commit:
-            self._db_commit()

Modified: CalendarServer/trunk/twistedcaldav/method/put_addressbook_common.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/put_addressbook_common.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/method/put_addressbook_common.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -25,7 +25,9 @@
 from twisted.internet import reactor
 from twisted.python.failure import Failure
 
-from twisted.internet.defer import Deferred, inlineCallbacks, succeed
+from txdav.common.icommondatastore import ReservationError
+
+from twisted.internet.defer import Deferred, inlineCallbacks
 from twisted.internet.defer import returnValue
 from twext.web2 import responsecode
 from twext.web2.dav import davxml
@@ -42,7 +44,6 @@
 from twistedcaldav.config import config
 from twistedcaldav.carddavxml import NoUIDConflict, carddav_namespace
 from twistedcaldav.vcard import Component
-from twistedcaldav.vcardindex import ReservationError
 from twext.python.log import Logger
 
 log = Logger()
@@ -149,6 +150,8 @@
         
         self.access = None
 
+
+    @inlineCallbacks
     def fullValidation(self):
         """
         Do full validation of source and destination vcard data.
@@ -191,7 +194,7 @@
             else:
                 # Get UID from original resource
                 self.source_index = self.sourceparent.index()
-                self.uid = self.source_index.resourceUIDForName(self.source.name())
+                self.uid = yield self.source_index.resourceUIDForName(self.source.name())
                 if self.uid is None:
                     log.err("Source vcard does not have a UID: %s" % self.source.name())
                     raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (carddav_namespace, "valid-addressbook-object-resource")))
@@ -207,7 +210,7 @@
                 raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (carddav_namespace, "max-resource-size")))
 
             # Check access
-            return succeed(None)
+            returnValue(None)
     
     def validResourceName(self):
         """
@@ -271,6 +274,8 @@
 
         return result, message
 
+
+    @inlineCallbacks
     def noUIDConflict(self, uid):
         """
         Check that the UID of the new vcard object conforms to the requirements of
@@ -292,8 +297,8 @@
 
         # UID must be unique
         index = self.destinationparent.index()
-        if not index.isAllowedUID(uid, oldname, self.destination.name()):
-            rname = index.resourceNameForUID(uid)
+        if not (yield index.isAllowedUID(uid, oldname, self.destination.name())):
+            rname = yield index.resourceNameForUID(uid)
             # This can happen if two simultaneous PUTs occur with the same UID.
             # i.e. one PUT has reserved the UID but has not yet written the resource,
             # the other PUT tries to reserve and fails but no index entry exists yet.
@@ -305,14 +310,15 @@
         else:
             # Cannot overwrite a resource with different UID
             if self.destination.exists():
-                olduid = index.resourceUIDForName(self.destination.name())
+                olduid = yield index.resourceUIDForName(self.destination.name())
                 if olduid != uid:
                     rname = self.destination.name()
                     result = False
                     message = "Cannot overwrite vcard resource %s with different UID %s" % (rname, olduid)
         
-        return result, message, rname
+        returnValue((result, message, rname))
 
+
     @inlineCallbacks
     def checkQuota(self):
         """
@@ -350,7 +356,7 @@
             # Retrieve information from the source, in case we have to delete
             # it.
             sourceProperties = dict(source.newStoreProperties().iteritems())
-            sourceText = source.vCardText()
+            sourceText = yield source.vCardText()
 
             # Delete the original source if needed (for example, if this is a
             # same-calendar MOVE of a calendar object, implemented as an
@@ -436,7 +442,7 @@
             
                 # UID conflict check - note we do this after reserving the UID to avoid a race condition where two requests
                 # try to write the same vcard data to two different resource URIs.
-                result, message, rname = self.noUIDConflict(self.uid)
+                result, message, rname = yield self.noUIDConflict(self.uid)
                 if not result:
                     log.err(message)
                     raise HTTPError(ErrorResponse(responsecode.FORBIDDEN,

Modified: CalendarServer/trunk/twistedcaldav/method/put_common.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/put_common.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/method/put_common.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -46,6 +46,8 @@
 from twext.python.log import Logger
 from twext.web2.dav.http import ErrorResponse
 
+from txdav.common.icommondatastore import ReservationError
+
 from twistedcaldav.config import config
 from twistedcaldav.caldavxml import ScheduleTag, NoUIDConflict
 from twistedcaldav.caldavxml import NumberOfRecurrencesWithinLimits
@@ -57,7 +59,6 @@
 from twistedcaldav.datafilters.peruserdata import PerUserDataFilter
 
 from twistedcaldav.ical import Component, Property
-from twistedcaldav.index import ReservationError
 from twistedcaldav.instance import TooManyInstancesError,\
     InvalidOverriddenInstanceError
 from twistedcaldav.memcachelock import MemcacheLock, MemcacheLockTimeoutError
@@ -280,7 +281,7 @@
             else:
                 # Get UID from original resource
                 self.source_index = self.sourceparent.index()
-                self.uid = self.source_index.resourceUIDForName(self.source.name())
+                self.uid = yield self.source_index.resourceUIDForName(self.source.name())
                 if self.uid is None:
                     log.err("Source calendar does not have a UID: %s" % self.source)
                     raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "valid-calendar-object-resource")))
@@ -573,6 +574,7 @@
         returnValue(new_has_private_comments)
 
 
+    @inlineCallbacks
     def noUIDConflict(self, uid): 
         """ 
         Check that the UID of the new calendar object conforms to the requirements of 
@@ -586,7 +588,7 @@
         result = True 
         message = "" 
         rname = "" 
-        
+
         # Adjust for a move into same calendar collection 
         oldname = None 
         if self.sourceparent and (self.sourceparent == self.destinationparent) and self.deletesource: 
@@ -594,8 +596,8 @@
         
         # UID must be unique 
         index = self.destinationparent.index() 
-        if not index.isAllowedUID(uid, oldname, self.destination.name()): 
-            rname = index.resourceNameForUID(uid) 
+        if not (yield index.isAllowedUID(uid, oldname, self.destination.name())): 
+            rname = yield index.resourceNameForUID(uid) 
             # This can happen if two simultaneous PUTs occur with the same UID. 
             # i.e. one PUT has reserved the UID but has not yet written the resource, 
             # the other PUT tries to reserve and fails but no index entry exists yet. 
@@ -606,16 +608,15 @@
         else: 
             # Cannot overwrite a resource with different UID 
             if self.destination.exists(): 
-                olduid = index.resourceUIDForName(self.destination.name()) 
+                olduid = yield index.resourceUIDForName(self.destination.name()) 
                 if olduid != uid: 
                     rname = self.destination.name() 
                     result = False 
                     message = "Cannot overwrite calendar resource %s with different UID %s" % (rname, olduid) 
          
-        return result, message, rname 
+        returnValue((result, message, rname))
 
 
-
     @inlineCallbacks
     def doImplicitScheduling(self):
 
@@ -696,14 +697,17 @@
             
         returnValue((is_scheduling_resource, data_changed, did_implicit_action,))
 
+
     @inlineCallbacks
     def mergePerUserData(self):
-        
         if self.calendar:
             accessUID = (yield self.destination.resourceOwnerPrincipal(self.request))
             accessUID = accessUID.principalUID() if accessUID else ""
-            oldCal = self.destination.iCalendar() if self.destination.exists() and self.destinationcal else None
-            
+            if self.destination.exists() and self.destinationcal:
+                oldCal = yield self.destination.iCalendar()
+            else:
+                oldCal = None
+
             # Duplicate before we do the merge because someone else may "own" the calendar object
             # and we should not change it. This is not ideal as we may duplicate it unnecessarily
             # but we currently have no api to let the caller tell us whether it cares about the
@@ -715,7 +719,8 @@
                 log.err(msg)
                 raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "valid-calendar-data"), description=msg))
             self.calendardata = None
-            
+
+
     @inlineCallbacks
     def doStore(self, implicit):
 
@@ -730,7 +735,7 @@
             sourceProperties = dict(source.newStoreProperties().iteritems())
             if not implicit:
                 # Only needed in implicit case; see below.
-                sourceText = source.iCalendarText()
+                sourceText = yield source.iCalendarText()
 
             # Delete the original source if needed (for example, if this is a
             # same-calendar MOVE of a calendar object, implemented as an
@@ -758,6 +763,7 @@
 
         returnValue(IResponse(response))
 
+
     @inlineCallbacks
     def doStorePut(self):
 
@@ -830,7 +836,7 @@
                 # UID conflict check - note we do this after reserving the UID to avoid a race condition where two requests 
                 # try to write the same calendar data to two different resource URIs. 
                 if not self.isiTIP: 
-                    result, message, rname = self.noUIDConflict(self.uid) 
+                    result, message, rname = yield self.noUIDConflict(self.uid) 
                     if not result: 
                         log.err(message)
                         raise HTTPError(ErrorResponse(responsecode.FORBIDDEN,
@@ -931,7 +937,7 @@
                             etags = self.destination.readDeadProperty(TwistedScheduleMatchETags).children
                         else:
                             etags = ()
-                    etags += (davxml.GETETag.fromString(self.destination.etag().tag),)
+                    etags += (davxml.GETETag.fromString((yield self.destination.etag()).tag),)
                     self.destination.writeDeadProperty(TwistedScheduleMatchETags(*etags))
                 else:
                     self.destination.removeDeadProperty(TwistedScheduleMatchETags)                

Modified: CalendarServer/trunk/twistedcaldav/method/report_addressbook_query.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/report_addressbook_query.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/method/report_addressbook_query.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -1,3 +1,4 @@
+# -*- test-case-name: twistedcaldav.test.test_addressbookquery -*-
 ##
 # Copyright (c) 2006-2010 Apple Inc. All rights reserved.
 #
@@ -146,12 +147,12 @@
             for vCardRecord in records:
                 
                 # match against original filter
-                if filter.match(vCardRecord.vCard()):
+                if filter.match((yield vCardRecord.vCard())):
  
                     # Check size of results is within limit
                     checkMaxResults()
                    
-                    yield report_common.responseForHref(request, responses, vCardRecord.hRef(), vCardRecord, propertiesForResource, query, vcard=vCardRecord.vCard())
+                    yield report_common.responseForHref(request, responses, vCardRecord.hRef(), vCardRecord, propertiesForResource, query, vcard=(yield vCardRecord.vCard()))
  
  
             
@@ -213,7 +214,7 @@
                         index_query_ok = addrresource.index().searchValid(filter)
                     
                         # Get list of children that match the search and have read access
-                        names = [name for name, ignore_uid in addrresource.index().search(filter)] #@UnusedVariable
+                        names = [name for name, ignore_uid in (yield addrresource.index().search(filter))] #@UnusedVariable
                         if not names:
                             return
                           
@@ -228,13 +229,12 @@
                             (davxml.Read(),),
                             inherited_aces=filteredaces
                         )
-                        
                         for child, child_uri in ok_resources:
                             child_uri_name = child_uri[child_uri.rfind("/") + 1:]
                             child_path_name = urllib.unquote(child_uri_name)
                             
                             if generate_address_data or not index_query_ok:
-                                vcard = addrresource.vCard(child_path_name)
+                                vcard = yield addrresource.vCard(child_path_name)
                                 assert vcard is not None, "vCard %s is missing from address book collection %r" % (child_uri_name, self)
                             else:
                                 vcard = None
@@ -262,7 +262,7 @@
                         handled = True
 
                 if not handled:
-                    vcard = addrresource.vCard()
+                    vcard = yield addrresource.vCard()
                     yield queryAddressBookObjectResource(addrresource, uri, None, vcard)
         
             if limited[0]:

Modified: CalendarServer/trunk/twistedcaldav/method/report_calendar_query.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/report_calendar_query.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/method/report_calendar_query.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -38,7 +38,7 @@
     NumberOfRecurrencesWithinLimits
 from twistedcaldav.config import config
 from twistedcaldav.customxml import TwistedCalendarAccessProperty
-from twistedcaldav.index import IndexedSearchException
+from txdav.common.icommondatastore import IndexedSearchException
 from twistedcaldav.instance import TooManyInstancesError
 from twistedcaldav.method import report_common
 from twistedcaldav.query import calendarqueryfilter
@@ -175,12 +175,11 @@
                 try:
                     # Get list of children that match the search and have read
                     # access
-                    names = [name for name, ignore_uid, ignore_type
-                        in calresource.index().indexedSearch(filter)]
+                    records = yield calresource.index().indexedSearch(filter)
                 except IndexedSearchException:
-                    names = [name for name, ignore_uid, ignore_type
-                        in calresource.index().bruteForceSearch()]
+                    records = yield calresource.index().bruteForceSearch()
                     index_query_ok = False
+                names = [name for name, ignore_uid, ignore_type in records]
 
                 if not names:
                     returnValue(True)

Modified: CalendarServer/trunk/twistedcaldav/method/report_common.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/report_common.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/method/report_common.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -64,9 +64,11 @@
 from twistedcaldav.dateops import clipPeriod, normalizePeriodList, timeRangesOverlap
 from twistedcaldav.ical import Component, Property, iCalendarProductID
 from twistedcaldav.instance import InstanceList
-from twistedcaldav.index import IndexedSearchException
+
 from twistedcaldav.query import calendarqueryfilter
 
+from txdav.common.icommondatastore import IndexedSearchException
+
 log = Logger()
 
 COLLECTION_TYPE_REGULAR     = "collection"
@@ -381,9 +383,12 @@
 fbtype_mapper = {"BUSY": 0, "BUSY-TENTATIVE": 1, "BUSY-UNAVAILABLE": 2}
 fbtype_index_mapper = {'B': 0, 'T': 1, 'U': 2}
 
+
+
 @inlineCallbacks
 def generateFreeBusyInfo(request, calresource, fbinfo, timerange, matchtotal,
-                         excludeuid=None, organizer=None, organizerPrincipal=None, same_calendar_user=False,
+                         excludeuid=None, organizer=None,
+                         organizerPrincipal=None, same_calendar_user=False,
                          servertoserver=False):
     """
     Run a free busy report on the specified calendar collection
@@ -393,15 +398,18 @@
     @param fbinfo:      the array of busy periods to update.
     @param timerange:   the L{TimeRange} for the query.
     @param matchtotal:  the running total for the number of matches.
-    @param excludeuid:  a C{str} containing a UID value to exclude any components with that
-        UID from contributing to free-busy.
-    @param organizer:   a C{str} containing the value of the ORGANIZER property in the VFREEBUSY request.
-        This is used in conjunction with the UID value to process exclusions.
-    @param same_calendar_user:   a C{bool} indicating whether the calendar user requesting the free-busy information
-        is the same as the calendar user being targeted.
-    @param servertoserver:       a C{bool} indicating whether we are doing a local or remote lookup request.
+    @param excludeuid:  a C{str} containing a UID value to exclude any
+        components with that UID from contributing to free-busy.
+    @param organizer:   a C{str} containing the value of the ORGANIZER property
+        in the VFREEBUSY request.  This is used in conjunction with the UID
+        value to process exclusions.
+    @param same_calendar_user: a C{bool} indicating whether the calendar user
+        requesting the free-busy information is the same as the calendar user
+        being targeted.
+    @param servertoserver: a C{bool} indicating whether we are doing a local or
+        remote lookup request.
     """
-    
+
     # First check the privilege on this collection
     # TODO: for server-to-server we bypass this right now as we have no way to authorize external users.
     if not servertoserver:
@@ -441,16 +449,22 @@
         tz = None
     tzinfo = filter.settimezone(tz)
 
-    # Do some optimization of access control calculation by determining any inherited ACLs outside of
-    # the child resource loop and supply those to the checkPrivileges on each child.
+    # Do some optimization 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 calresource.inheritedACEsforChildren(request))
 
+    userPrincipal = (yield calresource.resourceOwnerPrincipal(request))
+    if userPrincipal:
+        useruid = userPrincipal.principalUID()
+    else:
+        useruid = ""
     try:
-        useruid = (yield calresource.resourceOwnerPrincipal(request))
-        useruid = useruid.principalUID() if useruid else ""
-        resources = calresource.index().indexedSearch(filter, useruid=useruid, fbtype=True)
+        resources = yield calresource.index().indexedSearch(
+            filter, useruid=useruid, fbtype=True
+        )
     except IndexedSearchException:
-        resources = calresource.index().bruteForceSearch()
+        resources = yield calresource.index().bruteForceSearch()
 
     # We care about separate instances for VEVENTs only
     aggregated_resources = {}

Modified: CalendarServer/trunk/twistedcaldav/method/report_multiget_common.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/report_multiget_common.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/method/report_multiget_common.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -179,7 +179,9 @@
                 returnValue(None)
         
             # Verify that valid requested resources are calendar objects
-            exists_names = tuple(self.index().resourcesExist(valid_names))
+            exists_names = tuple(
+                (yield self.index().resourcesExist(valid_names))
+            )
             checked_names = []
             for name in valid_names:
                 if name not in exists_names:
@@ -309,11 +311,11 @@
                     parent = (yield child.locateParent(request, resource_uri))
     
                     if collection_type == COLLECTION_TYPE_CALENDAR:
-                        if not parent.isCalendarCollection() or not parent.index().resourceExists(name):
+                        if not parent.isCalendarCollection() or not (yield parent.index().resourceExists(name)):
                             responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.FORBIDDEN)))
                             continue
                     elif collection_type == COLLECTION_TYPE_ADDRESSBOOK:
-                        if not parent.isAddressBookCollection() or not parent.index().resourceExists(name):
+                        if not parent.isAddressBookCollection() or not (yield parent.index().resourceExists(name)):
                             responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.FORBIDDEN)))
                             continue
                     
@@ -343,11 +345,11 @@
                     parent = (yield self.locateParent(request, resource_uri))
     
                     if collection_type == COLLECTION_TYPE_CALENDAR:
-                        if not parent.isPseudoCalendarCollection() or not parent.index().resourceExists(name):
+                        if not parent.isPseudoCalendarCollection() or not (yield parent.index().resourceExists(name)):
                             responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.FORBIDDEN)))
                             continue
                     elif collection_type == COLLECTION_TYPE_ADDRESSBOOK:
-                        if not parent.isAddressBookCollection() or not parent.index().resourceExists(name):
+                        if not parent.isAddressBookCollection() or not (yield parent.index().resourceExists(name)):
                             responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.FORBIDDEN)))
                             continue
                     child = self

Modified: CalendarServer/trunk/twistedcaldav/method/report_sync_collection.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/report_sync_collection.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/method/report_sync_collection.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -106,7 +106,7 @@
     # the child resource loop and supply those to the checkPrivileges on each child.
     filteredaces = (yield self.inheritedACEsforChildren(request))
 
-    changed, removed, notallowed, newtoken = self.whatchanged(sync_collection.sync_token, depth)
+    changed, removed, notallowed, newtoken = yield self.whatchanged(sync_collection.sync_token, depth)
 
     # Now determine which valid resources are readable and which are not
     ok_resources = []

Modified: CalendarServer/trunk/twistedcaldav/notifications.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/notifications.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/notifications.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -27,7 +27,8 @@
 from twext.web2 import responsecode
 from twext.web2.dav import davxml
 
-from twisted.internet.defer import succeed, inlineCallbacks, returnValue
+from twisted.internet.defer import succeed, inlineCallbacks, returnValue,\
+    maybeDeferred
 
 from twistedcaldav.resource import ReadOnlyNoCopyResourceMixIn, CalDAVResource
 from twistedcaldav.sql import AbstractSQLDatabase, db_prefix
@@ -89,39 +90,45 @@
         # Update database
         self.notificationsDB().addOrUpdateRecord(NotificationRecord(uid, rname, xmltype.name))
 
+
     def getNotifictionMessages(self, request, componentType=None, returnLatestVersion=True):
         return succeed([])
 
+
     def getNotifictionMessageByUID(self, request, uid):
-        return succeed(self.notificationsDB().recordForUID(uid))
+        return maybeDeferred(self.notificationsDB().recordForUID, uid)
 
+
     @inlineCallbacks
     def deleteNotifictionMessageByUID(self, request, uid):
         
         # See if it exists and delete the resource
-        record = self.notificationsDB().recordForUID(uid)
+        record = yield self.notificationsDB().recordForUID(uid)
         if record:
             yield self.deleteNotification(request, record)
 
+
     @inlineCallbacks
     def deleteNotifictionMessageByName(self, request, rname):
 
         # See if it exists and delete the resource
-        record = self.notificationsDB().recordForName(rname)
+        record = yield self.notificationsDB().recordForName(rname)
         if record:
             yield self.deleteNotification(request, record)
         
         returnValue(None)
 
+
     @inlineCallbacks
     def deleteNotification(self, request, record):
         yield self._deleteNotification(request, record.name)
-        self.notificationsDB().removeRecordForUID(record.uid)
-        
+        yield self.notificationsDB().removeRecordForUID(record.uid)
+
+
     def removedNotifictionMessage(self, request, rname):
-        self.notificationsDB().removeRecordForName(rname)
-        return succeed(None)
-        
+        return maybeDeferred(self.notificationsDB().removeRecordForName, rname)
+
+
 class NotificationRecord(object):
     
     def __init__(self, uid, name, xmltype):

Modified: CalendarServer/trunk/twistedcaldav/resource.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/resource.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/resource.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -1,4 +1,4 @@
-# -*- test-case-name: twistedcaldav.test.test_resource -*-
+# -*- test-case-name: twistedcaldav.test.test_resource,twistedcaldav.test.test_wrapping -*-
 ##
 # Copyright (c) 2005-2010 Apple Inc. All rights reserved.
 #
@@ -72,20 +72,18 @@
 from twistedcaldav.extensions import DAVResource, DAVPrincipalResource,\
     PropertyNotFoundError, DAVResourceWithChildrenMixin
 from twistedcaldav.ical import Component
-from twistedcaldav.ical import Component as iComponent
-from twistedcaldav.ical import Property as iProperty
+
 from twistedcaldav.ical import allowedComponents
 from twistedcaldav.icaldav import ICalDAVResource, ICalendarPrincipalResource
-from twistedcaldav.index import SyncTokenValidException, Index
 from twistedcaldav.linkresource import LinkResource
 from twistedcaldav.notify import (getPubSubConfiguration, getPubSubPath,
     getPubSubXMPPURI, getPubSubHeartbeatURI, getPubSubAPSConfiguration,
     getNodeCacher, NodeCreationException)
 from twistedcaldav.sharing import SharedCollectionMixin, SharedHomeMixin
 from twistedcaldav.vcard import Component as vComponent
-from twistedcaldav.vcardindex import AddressBookIndex
 
-from txdav.common.icommondatastore import InternalDataStoreError
+from txdav.common.icommondatastore import InternalDataStoreError, \
+    SyncTokenValidException
 
 ##
 # Sharing Conts
@@ -181,7 +179,10 @@
 
 calendarPrivilegeSet = _calendarPrivilegeSet()
 
-class CalDAVResource (CalDAVComplianceMixIn, SharedCollectionMixin, DAVResourceWithChildrenMixin, DAVResource, LoggingMixIn):
+class CalDAVResource (
+        CalDAVComplianceMixIn, SharedCollectionMixin,
+        DAVResourceWithChildrenMixin, DAVResource, LoggingMixIn
+    ):
     """
     CalDAV resource.
 
@@ -274,6 +275,7 @@
         self._transactionError = True
 
 
+    @inlineCallbacks
     def renderHTTP(self, request, transaction=None):
         """
         Override C{renderHTTP} to commit the transaction when the resource is
@@ -284,17 +286,15 @@
         @param transaction: optional transaction to use instead of associated transaction
         @type transaction: L{txdav.caldav.idav.ITransaction}
         """
-        d = maybeDeferred(super(CalDAVResource, self).renderHTTP, request)
-        def succeeded(result, transaction=None):
-            if transaction is None:
-                transaction = self._associatedTransaction
-            if transaction is not None:
-                if self._transactionError:
-                    transaction.abort()
-                else:
-                    transaction.commit()
-            return result
-        return d.addCallback(succeeded, transaction=transaction)
+        result = yield super(CalDAVResource, self).renderHTTP(request)
+        if transaction is None:
+            transaction = self._associatedTransaction
+        if transaction is not None:
+            if self._transactionError:
+                yield transaction.abort()
+            else:
+                yield transaction.commit()
+        returnValue(result)
 
 
     # Begin transitional new-store resource interface:
@@ -538,12 +538,12 @@
         elif qname == customxml.GETCTag.qname() and (
             self.isPseudoCalendarCollection() or self.isAddressBookCollection()
         ):
-            returnValue(customxml.GETCTag.fromString(self.getSyncToken()))
+            returnValue(customxml.GETCTag.fromString((yield self.getSyncToken())))
 
         elif qname == davxml.SyncToken.qname() and config.EnableSyncReport and (
             davxml.Report(SyncCollection(),) in self.supportedReports()
         ):
-            returnValue(davxml.SyncToken.fromString(self.getSyncToken()))
+            returnValue(davxml.SyncToken.fromString((yield self.getSyncToken())))
 
         elif qname == davxml.AddMember.qname() and config.EnableAddMember and (
             self.isCalendarCollection() or self.isAddressBookCollection()
@@ -840,6 +840,7 @@
         else:
             returnValue(None)
 
+
     @inlineCallbacks
     def resourceOwnerPrincipal(self, request):
         """
@@ -851,13 +852,16 @@
         if isVirt:
             returnValue(self._shareePrincipal)
         else:
-            parent = (yield self.locateParent(request, request.urlForResource(self)))
+            parent = (yield self.locateParent(
+                request, request.urlForResource(self)
+            ))
         if parent and isinstance(parent, CalDAVResource):
             result = (yield parent.resourceOwnerPrincipal(request))
             returnValue(result)
         else:
             returnValue(None)
 
+
     def isOwner(self, request, adminprincipals=False, readprincipals=False):
         """
         Determine whether the DAV:owner of this resource matches the currently authorized principal
@@ -1012,8 +1016,11 @@
 
         if depth != "0" and self.isCollection():
             basepath = request.urlForResource(self)
-            children = list(self.listChildren())
-            getChild()
+            children = []
+            def gotChildren(childNames):
+                children[:] = list(childNames)
+                getChild()
+            maybeDeferred(self.listChildren).addCallback(gotChildren)
         else:
             completionDeferred.callback(None)
 
@@ -1025,7 +1032,7 @@
 
         if depth != "0" and self.isCollection():
             basepath = request.urlForResource(self)
-            for childname in self.listChildren():
+            for childname in (yield self.listChildren()):
                 childpath = joinURL(basepath, childname)
                 child = (yield request.locateResource(childpath))
                 if privileges:
@@ -1111,35 +1118,19 @@
 
         returnValue(False)
 
-    def iCalendar(self, name=None):
-        """
-        See L{ICalDAVResource.iCalendar}.
 
-        This implementation returns the an object created from the data returned
-        by L{iCalendarText} when given the same arguments.
-
-        Note that L{iCalendarText} by default calls this method, which creates
-        an infinite loop.  A subclass must override one of both of these
-        methods.
-        """
-        
-        try:
-            calendar_data = self.iCalendarText(name)
-        except InternalDataStoreError:
-            return None
-
-        if calendar_data is None: return None
-
-        try:
-            return iComponent.fromString(calendar_data)
-        except ValueError:
-            return None
-
     @inlineCallbacks
     def iCalendarForUser(self, request, name=None):
-        
-        caldata = self.iCalendar(name)
-        
+        if name is not None:
+            # FIXME: this is really the caller's job; why am I looking up sub-
+            # resources?
+            returnValue(
+                (yield (yield request.locateChildResource(self, name)
+                    ).iCalendarForUser(request))
+            )
+
+        caldata = yield self.iCalendar()
+
         accessUID = (yield self.resourceOwnerPrincipal(request))
         if accessUID is None:
             accessUID = ""
@@ -1148,6 +1139,7 @@
 
         returnValue(PerUserDataFilter(accessUID).filter(caldata))
 
+
     def iCalendarAddressDoNormalization(self, ical):
         """
         Normalize calendar user addresses in the supplied iCalendar object into their
@@ -1176,6 +1168,8 @@
                 return principal
         return None
 
+
+    @inlineCallbacks
     def vCard(self, name=None):
         """
         See L{ICalDAVResource.vCard}.
@@ -1188,17 +1182,19 @@
         methods.
         """
         try:
-            vcard_data = self.vCardText(name)
+            vcard_data = yield self.vCardText(name)
         except InternalDataStoreError:
-            return None
+            returnValue(None)
 
-        if vcard_data is None: return None
+        if vcard_data is None:
+            returnValue(None)
 
         try:
-            return vComponent.fromString(vcard_data)
+            returnValue(vComponent.fromString(vcard_data))
         except ValueError:
-            return None
+            returnValue(None)
 
+
     def supportedReports(self):
         result = super(CalDAVResource, self).supportedReports()
         result.append(davxml.Report(caldavxml.CalendarQuery(),))
@@ -1321,9 +1317,10 @@
 
     # Collection sync stuff
 
+
+    @inlineCallbacks
     def whatchanged(self, client_token, depth):
-        
-        current_token = self.getSyncToken()
+        current_token = yield self.getSyncToken()
         current_uuid, current_revision = current_token.split("#", 1)
         current_revision = int(current_revision)
 
@@ -1343,11 +1340,11 @@
             revision = 0
 
         try:
-            changed, removed, notallowed = self._indexWhatChanged(revision, depth)
+            changed, removed, notallowed = yield self._indexWhatChanged(revision, depth)
         except SyncTokenValidException:
             raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (dav_namespace, "valid-sync-token")))
 
-        return changed, removed, notallowed, current_token
+        returnValue((changed, removed, notallowed, current_token))
 
     def _indexWhatChanged(self, revision, depth):
         # Now handled directly by newstore
@@ -1460,124 +1457,14 @@
         return fail(NotImplementedError())
 
 
-    def createSpecialCollection(self, resourceType=None):
-        #
-        # Create the collection once we know it is safe to do so
-        #
-        def onCollection(status):
-            if status != responsecode.CREATED:
-                raise HTTPError(status)
+    def iCalendarRolledup(self):
+        """
+        Only implemented by calendar collections; see storebridge.
+        """
+        
 
-            self.writeDeadProperty(resourceType)
-            return status
 
-        def onError(f):
-            try:
-                rmdir(self.fp)
-            except Exception, e:
-                log.err("Unable to clean up after failed MKCOL (special resource type: %s): %s" % (e, resourceType,))
-            return f
-
-        d = mkcollection(self.fp)
-        if resourceType is not None:
-            d.addCallback(onCollection)
-        d.addErrback(onError)
-        return d
-
     @inlineCallbacks
-    def iCalendarRolledup(self, request):
-        if self.isPseudoCalendarCollection():
-
-
-# FIXME: move cache implementation!
-            # Determine the cache key
-#            isvirt = self.isVirtualShare()
-#            if isvirt:
-#                principal = (yield self.resourceOwnerPrincipal(request))
-#                if principal:
-#                    cacheKey = principal.principalUID()
-#                else:
-#                    cacheKey = "unknown"
-#            else:
-#                isowner = (yield self.isOwner(request, adminprincipals=True, readprincipals=True))
-#                cacheKey = "owner" if isowner else "notowner"
-                
-            # Now check for a cached .ics
-#            rolled = self.fp.child(".subscriptions")
-#            if not rolled.exists():
-#                try:
-#                    rolled.makedirs()
-#                except IOError, e:
-#                    self.log_error("Unable to create internet calendar subscription cache directory: %s because of: %s" % (rolled.path, e,))
-#                    raise HTTPError(ErrorResponse(responsecode.INTERNAL_SERVER_ERROR))
-#            cached = rolled.child(cacheKey)
-#            if cached.exists():
-#                try:
-#                    cachedData = cached.open().read()
-#                except IOError, e:
-#                    self.log_error("Unable to open or read internet calendar subscription cache file: %s because of: %s" % (cached.path, e,))
-#                else:
-#                    # Check the cache token
-#                    token, data = cachedData.split("\r\n", 1)
-#                    if token == self.getSyncToken():
-#                        returnValue(data)
-
-            # Generate a monolithic calendar
-            calendar = iComponent("VCALENDAR")
-            calendar.addProperty(iProperty("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 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 = child.iCalendarTextFiltered(isowner, accessPrincipal.principalUID() if accessPrincipal else "")
-                    try:
-                        subcalendar = iComponent.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 = self.getSyncToken() + "\r\n" + data
-#            try:
-#                cached.open(mode='w').write(data)
-#            except IOError, e:
-#                self.log_error("Unable to open or write internet calendar subscription cache file: %s because of: %s" % (cached.path, e,))
-                
-            returnValue(calendar)
-
-        raise HTTPError(ErrorResponse(responsecode.BAD_REQUEST))
-
     def iCalendarTextFiltered(self, isowner, accessUID=None):
         try:
             access = self.readDeadProperty(TwistedCalendarAccessProperty)
@@ -1585,28 +1472,23 @@
             access = None
 
         # Now "filter" the resource calendar data
-        caldata = PrivateEventFilter(access, isowner).filter(self.iCalendarText())
+        caldata = PrivateEventFilter(access, isowner).filter(
+            (yield self.iCalendarText())
+        )
         if accessUID:
             caldata = PerUserDataFilter(accessUID).filter(caldata)
-        return str(caldata)
+        returnValue(str(caldata))
 
-    def iCalendarText(self, name=None):
-        if self.isPseudoCalendarCollection():
-            if name is None:
-                return str(self.iCalendar())
 
-            calendar_resource = self.getChild(name)
-            return calendar_resource.iCalendarText()
+    def iCalendarText(self):
+        # storebridge handles this method
+        raise NotImplementedError()
 
-        elif self.isCollection():
-            return None
 
-        else:
-            if name is not None:
-                raise AssertionError("name must be None for non-collection calendar resource")
+    def iCalendar(self):
+        # storebridge handles this method
+        raise NotImplementedError()
 
-        # FIXME: StoreBridge handles this case
-        raise NotImplementedError
 
     def createAddressBook(self, request):
         """
@@ -1668,26 +1550,22 @@
         # TODO: just catenate all the vCards together 
         yield fail(HTTPError((ErrorResponse(responsecode.BAD_REQUEST))))
 
+
+    @inlineCallbacks
     def vCardText(self, name=None):
         if self.isAddressBookCollection():
             if name is None:
-                return str(self.vCard())
-
-            vcard_resource = self.getChild(name)
-            return vcard_resource.vCardText()
-
+                returnValue(str((yield self.vCard())))
+            vcard_resource = yield self.getChild(name)
+            returnValue((yield vcard_resource.vCardText()))
         elif self.isCollection():
-            return None
-
+            returnValue(None)
         else:
             if name is not None:
                 raise AssertionError("name must be None for non-collection vcard resource")
-
         # FIXME: StoreBridge handles this case
         raise NotImplementedError
 
-    def vCardXML(self, name=None):
-        return carddavxml.AddressData.fromAddressData(self.vCardText(name))
 
     def supportedPrivileges(self, request):
         # read-free-busy support on calendar collection and calendar object resources
@@ -1706,18 +1584,6 @@
 
         return super(CalDAVResource, self).supportedPrivileges(request)
 
-    def index(self):
-        """
-        Obtains the index for a calendar collection resource.
-        @return: the index object for this resource.
-        @raise AssertionError: if this resource is not a calendar collection
-            resource.
-        """
-        if self.isAddressBookCollection():
-            return AddressBookIndex(self)
-        else:
-            return Index(self)
-
     ##
     # Quota
     ##
@@ -1830,9 +1696,6 @@
     def isCalendarCollection(self):
         return False
 
-    def isPseudoCalendarCollection(self):
-        return False
-
     def isAddressBookCollection(self):
         return False
 
@@ -2098,35 +1961,51 @@
 
 class CommonHomeResource(SharedHomeMixin, CalDAVResource):
     """
-    Calendar home collection resource.
+    Logic common to Calendar and Addressbook home resources.
     """
-    def __init__(self, parent, name, transaction):
-        """
-        """
-
+    def __init__(self, parent, name, transaction, home):
         self.parent = parent
         self.name = name
         self.associateWithTransaction(transaction)
         self._provisionedChildren = {}
         self._provisionedLinks = {}
         self._setupProvisions()
-
-        self._newStoreHome, created = self.makeNewStore()
+        self._newStoreHome = home
         CalDAVResource.__init__(self)
 
         from twistedcaldav.storebridge import _NewStorePropertiesWrapper
         self._dead_properties = _NewStorePropertiesWrapper(
             self._newStoreHome.properties()
         )
+
+
+    @classmethod
+    @inlineCallbacks
+    def createHomeResource(cls, parent, name, transaction):
+        home, created = yield cls.homeFromTransaction(
+            transaction, name)
+        resource = cls(parent, name, transaction, home)
         if created:
-            self.postCreateHome()
+            yield resource.postCreateHome()
+        returnValue(resource)
 
+
+    @classmethod
+    def homeFromTransaction(cls, transaction, uid):
+        """
+        Create or retrieve an appropriate back-end-home object from a
+        transaction and a home UID.
+
+        @return: a L{Deferred} which fires a 2-tuple of C{(created, home)}
+            where C{created} is a boolean indicating whether this call created
+            the home in the back-end, and C{home} is the home object itself.
+        """
+        raise NotImplementedError("Subclasses must implement.")
+
+
     def _setupProvisions(self):
         pass
 
-    def makeNewStore(self):
-        raise NotImplementedError
-
     def postCreateHome(self):
         pass
 
@@ -2178,8 +2057,8 @@
         """
         Always get quota root value from config.
 
-        @return: a C{int} containing the maximum allowed bytes if this collection
-            is quota-controlled, or C{None} if not quota controlled.
+        @return: a C{int} containing the maximum allowed bytes if this
+            collection is quota-controlled, or C{None} if not quota controlled.
         """
         return config.UserQuota if config.UserQuota != 0 else None
 
@@ -2192,8 +2071,8 @@
 
     def _mergeSyncTokens(self, hometoken, notificationtoken):
         """
-        Merge two sync tokens, choosing the higher revision number of the two, but keeping
-        the home resource-id intact.
+        Merge two sync tokens, choosing the higher revision number of the two,
+        but keeping the home resource-id intact.
         """
         homekey, homerev = hometoken.split("#", 1)
         notrev = notificationtoken.split("#", 1)[1]
@@ -2204,38 +2083,40 @@
     def canShare(self):
         raise NotImplementedError
 
+
+    @inlineCallbacks
     def makeChild(self, name):
-        
         # Try built-in children first
         if name in self._provisionedChildren:
             cls = self._provisionedChildren[name]
             from twistedcaldav.notifications import NotificationCollectionResource
             if cls is NotificationCollectionResource:
-                return self.createNotificationsCollection()
-            child = self._provisionedChildren[name](self)
+                returnValue((yield self.createNotificationsCollection()))
+            child = yield self._provisionedChildren[name](self)
             self.propagateTransaction(child)
             self.putChild(name, child)
-            return child
-        
+            returnValue(child)
+
         # Try built-in links next
         if name in self._provisionedLinks:
             child = LinkResource(self, self._provisionedLinks[name])
             self.putChild(name, child)
-            return child
-        
+            returnValue(child)
+
         # Try shares next
         if self.canShare():
-            child = self.provisionShare(name)
+            child = yield self.provisionShare(name)
             if child:
-                return child
+                returnValue(child)
 
         # Do normal child types
-        return self.makeRegularChild(name)
+        returnValue((yield self.makeRegularChild(name)))
 
+
+    @inlineCallbacks
     def createNotificationsCollection(self):
-        
         txn = self._associatedTransaction
-        notifications = txn.notificationsWithUID(self._newStoreHome.uid())
+        notifications = yield txn.notificationsWithUID(self._newStoreHome.uid())
 
         from twistedcaldav.storebridge import StoreNotificationCollectionResource
         similar = StoreNotificationCollectionResource(
@@ -2244,21 +2125,25 @@
             principalCollections = self.principalCollections(),
         )
         self.propagateTransaction(similar)
-        return similar
+        returnValue(similar)
 
+
     def makeRegularChild(self, name):
         raise NotImplementedError
 
+
+    @inlineCallbacks
     def listChildren(self):
         """
         @return: a sequence of the names of all known children of this resource.
         """
         children = set(self._provisionedChildren.keys())
         children.update(self._provisionedLinks.keys())
-        children.update(self.allShareNames())
-        children.update(self._newStoreHome.listChildren())
-        return children
+        children.update((yield self.allShareNames()))
+        children.update((yield self._newStoreHome.listChildren()))
+        returnValue(children)
 
+
     @inlineCallbacks
     def readProperty(self, property, request):
         if type(property) is tuple:
@@ -2426,14 +2311,26 @@
 
 class CalendarHomeResource(CommonHomeResource):
     """
-    Calendar home collection resource.
+    Calendar home collection classmethod.
     """
 
+    @classmethod
+    @inlineCallbacks
+    def homeFromTransaction(cls, transaction, uid):
+        storeHome = yield transaction.calendarHomeWithUID(uid)
+        if storeHome is not None:
+            created = False
+        else:
+            storeHome = yield transaction.calendarHomeWithUID(uid, create=True)
+            created = True
+        returnValue((storeHome, created))
+
+
     def _setupProvisions(self):
 
         # Cache children which must be of a specific type
         from twistedcaldav.storebridge import StoreScheduleInboxResource
-        self._provisionedChildren["inbox"] = StoreScheduleInboxResource
+        self._provisionedChildren["inbox"] = StoreScheduleInboxResource.maybeCreateInbox
 
         from twistedcaldav.schedule import ScheduleOutboxResource
         self._provisionedChildren["outbox"] = ScheduleOutboxResource
@@ -2450,32 +2347,24 @@
             from twistedcaldav.notifications import NotificationCollectionResource
             self._provisionedChildren["notification"] = NotificationCollectionResource
 
-    def makeNewStore(self):
-        storeHome = self._associatedTransaction.calendarHomeWithUID(self.name)
-        if storeHome is not None:
-            created = False
-        else:
-            storeHome = self._associatedTransaction.calendarHomeWithUID(
-                self.name, create=True
-            )
-            created = True
 
-        return storeHome, created
-
+    @inlineCallbacks
     def postCreateHome(self):
         # This is a bit of a hack.  Really we ought to be always generating
         # this URL live from a back-end method that tells us what the
         # default calendar is.
-        inbox = self.getChild("inbox")
+        inbox = yield self.getChild("inbox")
         childURL = joinURL(self.url(), "calendar")
         inbox.processFreeBusyCalendar(childURL, True)
 
+
     def canShare(self):
         return config.Sharing.Enabled and config.Sharing.Calendars.Enabled and self.exists()
 
-    def makeRegularChild(self, name):
 
-        newCalendar = self._newStoreHome.calendarWithName(name)
+    @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 (
@@ -2492,8 +2381,9 @@
                 principalCollections=self.principalCollections()
             )
         self.propagateTransaction(similar)
-        return similar
+        returnValue(similar)
 
+
     def defaultAccessControlList(self):
         myPrincipal = self.principalForRecord()
 
@@ -2546,21 +2436,27 @@
 
         return davxml.ACL(*aces)
 
+
+    @inlineCallbacks
     def getSyncToken(self):
         # The newstore implementation supports this directly
-        caltoken = self._newStoreHome.syncToken()
+        caltoken = yield self._newStoreHome.syncToken()
 
         if config.Sharing.Enabled and config.Sharing.Calendars.Enabled:
-            notificationtoken = self.getChild("notification").getSyncToken()
-            
+            notificationtoken = yield (yield self.getChild("notification")).getSyncToken()
+
             # Merge tokens
             caltoken = self._mergeSyncTokens(caltoken, notificationtoken)
-            
-        return caltoken
 
+        returnValue(caltoken)
+
+
+    @inlineCallbacks
     def _indexWhatChanged(self, revision, depth):
         # The newstore implementation supports this directly
-        changed, deleted = self._newStoreHome.resourceNamesSinceToken(revision, depth)
+        changed, deleted = yield self._newStoreHome.resourceNamesSinceToken(
+            revision, depth
+        )
         notallowed = []
 
         # Need to insert some addition items on first sync
@@ -2569,29 +2465,43 @@
 
             if config.FreeBusyURL.Enabled:
                 changed.append("freebusy")
-    
+
             if config.Sharing.Enabled and config.Sharing.Calendars.Enabled:
                 changed.append("notification/")
-        
+
             # Dropbox is never synchronized
             if config.EnableDropBox:
                 notallowed.append("dropbox/")
-    
+
         # Add in notification changes
         if config.Sharing.Enabled and config.Sharing.Calendars.Enabled:
-            noti_changed, noti_deleted, noti_notallowed = self.getChild("notification")._indexWhatChanged(revision, depth)
+            noti_changed, noti_deleted, noti_notallowed = yield (yield self.getChild("notification"))._indexWhatChanged(revision, depth)
 
             changed.extend([joinURL("notification", name) for name in noti_changed])
             deleted.extend([joinURL("notification", name) for name in noti_deleted])
             notallowed.extend([joinURL("notification", name) for name in noti_notallowed])
 
-        return changed, deleted, notallowed
+        returnValue((changed, deleted, notallowed))
 
+
+
 class AddressBookHomeResource (CommonHomeResource):
     """
     Address book home collection resource.
     """
-    
+
+    @classmethod
+    @inlineCallbacks
+    def homeFromTransaction(cls, transaction, uid):
+        storeHome = yield transaction.addressbookHomeWithUID(uid)
+        if storeHome is not None:
+            created = False
+        else:
+            storeHome = yield transaction.addressbookHomeWithUID(uid, create=True)
+            created = True
+        returnValue((storeHome, created))
+
+
     def _setupProvisions(self):
 
         # Cache children which must be of a specific type
@@ -2608,6 +2518,7 @@
     def canShare(self):
         return config.Sharing.Enabled and config.Sharing.AddressBooks.Enabled and self.exists()
 
+    @inlineCallbacks
     def makeRegularChild(self, name):
 
         # Check for public/global path
@@ -2624,7 +2535,7 @@
                 mainCls = GlobalAddressBookCollectionResource
                 protoCls = ProtoGlobalAddressBookCollectionResource
 
-        newAddressBook = self._newStoreHome.addressbookWithName(name)
+        newAddressBook = yield self._newStoreHome.addressbookWithName(name)
         if newAddressBook is None:
             # Local imports.due to circular dependency between modules.
             similar = protoCls(
@@ -2638,51 +2549,58 @@
                 principalCollections=self.principalCollections()
             )
         self.propagateTransaction(similar)
-        return similar
+        returnValue(similar)
 
 
+    @inlineCallbacks
     def getSyncToken(self):
         # The newstore implementation supports this directly
-        adbktoken = self._newStoreHome.syncToken()
+        adbktoken = yield self._newStoreHome.syncToken()
 
         if config.Sharing.Enabled and config.Sharing.AddressBooks.Enabled and not config.Sharing.Calendars.Enabled:
-            notifcationtoken = self.getChild("notification").getSyncToken()
+            notifcationtoken = yield (yield self.getChild("notification")).getSyncToken()
             
             # Merge tokens
             adbkkey, adbkrev = adbktoken.split("#", 1)
             notrev = notifcationtoken.split("#", 1)[1]
             if int(notrev) > int(adbkrev):
                 adbktoken = "%s#%s" % (adbkkey, notrev,)
-            
-        return adbktoken
 
+        returnValue(adbktoken)
+
+
+    @inlineCallbacks
     def _indexWhatChanged(self, revision, depth):
         # The newstore implementation supports this directly
-        changed, deleted = self._newStoreHome.resourceNamesSinceToken(revision, depth)
+        changed, deleted = yield self._newStoreHome.resourceNamesSinceToken(
+            revision, depth
+        )
         notallowed = []
 
         # Need to insert some addition items on first sync
         if revision == 0:
             if config.Sharing.Enabled and config.Sharing.AddressBooks.Enabled and not config.Sharing.Calendars.Enabled:
                 changed.append("notification/")
-        
+
         # Add in notification changes
         if config.Sharing.Enabled and config.Sharing.AddressBooks.Enabled and not config.Sharing.Calendars.Enabled:
-            noti_changed, noti_deleted, noti_notallowed = self.getChild("notification")._indexWhatChanged(revision, depth)
+            noti_changed, noti_deleted, noti_notallowed = yield (yield self.getChild("notification"))._indexWhatChanged(revision, depth)
 
             changed.extend([joinURL("notification", name) for name in noti_changed])
             deleted.extend([joinURL("notification", name) for name in noti_deleted])
             notallowed.extend([joinURL("notification", name) for name in noti_notallowed])
 
-        return changed, deleted, notallowed
+        returnValue((changed, deleted, notallowed))
 
+
+
 class GlobalAddressBookResource (ReadOnlyResourceMixIn, CalDAVResource):
     """
     Global address book. All we care about is making sure permissions are setup.
     """
 
     def resourceType(self):
-        return davxml.ResourceType.sharedaddressbook
+        return davxml.ResourceType.sharedaddressbook #@UndefinedVariable
 
     def defaultAccessControlList(self):
 
@@ -2749,8 +2667,6 @@
 # Utilities
 ##
 
-def isCalendarHomeCollectionResource(resource):
-    return isinstance(resource, CalendarHomeResource)
 
 def isCalendarCollectionResource(resource):
     try:
@@ -2760,6 +2676,7 @@
     else:
         return resource.isCalendarCollection()
 
+
 def isPseudoCalendarCollectionResource(resource):
     try:
         resource = ICalDAVResource(resource)
@@ -2768,8 +2685,6 @@
     else:
         return resource.isPseudoCalendarCollection()
 
-def isAddressBookHomeCollectionResource(resource):
-    return isinstance(resource, AddressBookHomeResource)
 
 def isAddressBookCollectionResource(resource):
     try:

Modified: CalendarServer/trunk/twistedcaldav/schedule.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/schedule.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/schedule.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -154,7 +154,7 @@
             if not self.hasDeadProperty(property):
                 top = self.parent.url()
                 values = []
-                for cal in self.parent._newStoreHome.calendars():
+                for cal in (yield self.parent._newStoreHome.calendars()):
                     prop = cal.properties().get(PropertyName.fromString(ScheduleCalendarTransp.sname())) 
                     if prop == ScheduleCalendarTransp(Opaque()):
                         values.append(HRef(joinURL(top, cal.name())))
@@ -261,7 +261,7 @@
         defaultCalendarURL = joinURL(calendarHomeURL, "calendar")
         defaultCalendar = (yield request.locateResource(defaultCalendarURL))
         if defaultCalendar is None or not defaultCalendar.exists():
-            getter = iter(self.parent._newStoreHome.calendars())
+            getter = iter((yield self.parent._newStoreHome.calendars()))
             # FIXME: the back-end should re-provision a default calendar here.
             # Really, the dead property shouldn't be necessary, and this should
             # be entirely computed by a back-end method like 'defaultCalendar()'

Modified: CalendarServer/trunk/twistedcaldav/scheduling/implicit.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/implicit.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/scheduling/implicit.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -376,7 +376,7 @@
 
         # Get owner's calendar-home
         calendar_owner_principal = (yield self.resource.resourceOwnerPrincipal(self.request))
-        calendar_home = calendar_owner_principal.calendarHome(self.request)
+        calendar_home = yield calendar_owner_principal.calendarHome(self.request)
 
         check_parent_uri = parentForURL(check_uri)[:-1] if check_uri else None
 
@@ -388,7 +388,7 @@
 
         @inlineCallbacks
         def queryCalendarCollection(collection, collection_uri):
-            rname = collection.index().resourceNameForUID(self.uid)
+            rname = yield collection.index().resourceNameForUID(self.uid)
             if rname:
                 child = (yield self.request.locateResource(joinURL(collection_uri, rname)))
                 if child == check_resource:

Modified: CalendarServer/trunk/twistedcaldav/scheduling/processing.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/processing.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/scheduling/processing.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -20,6 +20,7 @@
 
 from vobject.icalendar import dateTimeToString, utc
 
+from twisted.python.log import err as log_traceback
 from twext.python.log import Logger
 
 from twisted.internet import reactor
@@ -94,6 +95,7 @@
                 # We attempt to recover from this. That involves trying to re-write the attendee data
                 # to match that of the organizer assuming we have the organizer's full data available, then
                 # we try the processing operation again.
+                log_traceback()
                 log.error("ImplicitProcessing - originator '%s' to recipient '%s' with UID: '%s' - exception raised will try to fix: %s" % (self.originator.cuaddr, self.recipient.cuaddr, self.uid, e))
                 result = (yield self.doImplicitAttendeeEventFix(e))
                 if result:
@@ -101,6 +103,7 @@
                     try:
                         result = (yield self.doImplicitAttendee())
                     except Exception, e:
+                        log_traceback()
                         log.error("ImplicitProcessing - originator '%s' to recipient '%s' with UID: '%s' - exception raised after fix: %s" % (self.originator.cuaddr, self.recipient.cuaddr, self.uid, e))
                         raise ImplicitProcessorException("5.1;Service unavailable")
                 else:
@@ -422,7 +425,7 @@
 
         @param calendar: calendar data to examine
         @type calendar: L{Component}
-        
+
         @return: L{Component} for the new calendar data to write
         """
         
@@ -444,7 +447,7 @@
             # inNewTransaction wipes out the remembered resource<-> URL mappings in the
             # request object but we need to be able to map the actual reply resource to its
             # URL when doing auto-processing, so we have to sneak that mapping back in here.
-            txn = resource.inNewTransaction(self.request)
+            txn = yield resource.inNewTransaction(self.request)
             self.request._rememberResource(resource, resource._url)
 
             try:
@@ -454,9 +457,9 @@
                 scheduler = ImplicitScheduler()
                 yield scheduler.sendAttendeeReply(self.request, resource, calendar, self.recipient)
             except:
-                txn.abort()
+                yield txn.abort()
             else:
-                txn.commit()
+                yield txn.commit()
         finally:
             yield lock.clean()
 
@@ -653,6 +656,7 @@
     
         returnValue(newchild)
 
+
     @inlineCallbacks
     def deleteCalendarResource(self, collURL, collection, name):
         """
@@ -665,7 +669,7 @@
         @param name: the resource name to write into, or {None} to write a new resource.
         @type name: C{str}
         """
-        delchild = collection.getChild(name)
+        delchild = yield collection.getChild(name)
         childURL = joinURL(collURL, name)
         self.request._rememberResource(delchild, childURL)
         yield delchild.storeRemove(self.request, False, childURL)

Modified: CalendarServer/trunk/twistedcaldav/scheduling/utils.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/utils.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/scheduling/utils.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -14,7 +14,7 @@
 # limitations under the License.
 ##
 
-from twisted.internet.defer import inlineCallbacks, returnValue, succeed
+from twisted.internet.defer import inlineCallbacks, returnValue
 from twistedcaldav.method import report_common
 from twext.web2.dav.util import joinURL
 
@@ -23,7 +23,7 @@
     """
     Get a copy of the event for a principal.
     """
-    
+
     result = {
         "resource": None,
         "resource_name": None,
@@ -33,33 +33,38 @@
 
     if principal and principal.locallyHosted():
         # Get principal's calendar-home
-        calendar_home = principal.calendarHome(request)
-        
-        # FIXME: because of the URL->resource request mapping thing, we have to force the request
-        # to recognize this resource
+        calendar_home = yield principal.calendarHome(request)
+
+        # FIXME: because of the URL->resource request mapping thing, we have to
+        # force the request to recognize this resource.
         request._rememberResource(calendar_home, calendar_home.url())
 
-        # Run a UID query against the UID
+        # Run a UID query against the UID.
+        @inlineCallbacks
         def queryCalendarCollection(collection, uri):
             if not allow_shared:
                 if collection.isVirtualShare():
-                    return succeed(True)
+                    returnValue(True)
 
-            rname = collection.index().resourceNameForUID(uid)
+            rname = yield collection.index().resourceNameForUID(uid)
             if rname:
-                resource = collection.getChild(rname)
+                resource = yield collection.getChild(rname)
                 request._rememberResource(resource, joinURL(uri, rname))
 
                 result["resource"] = resource
                 result["resource_name"] = rname
                 result["calendar_collection"] = collection
                 result["calendar_collection_uri"] = uri
-                return succeed(False)
+                returnValue(False)
             else:
-                return succeed(True)
-        
-        # NB We are by-passing privilege checking here. That should be OK as the data found is not
-        # exposed to the user.
-        yield report_common.applyToCalendarCollections(calendar_home, request, calendar_home.url(), "infinity", queryCalendarCollection, None)
+                returnValue(True)
 
+        # NB We are by-passing privilege checking here. That should be OK as
+        # the data found is not exposed to the user.
+        yield report_common.applyToCalendarCollections(
+            calendar_home, request, calendar_home.url(),
+            "infinity", queryCalendarCollection, None
+        )
+
     returnValue((result["resource"], result["resource_name"], result["calendar_collection"], result["calendar_collection_uri"],))
+

Modified: CalendarServer/trunk/twistedcaldav/sharing.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/sharing.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/sharing.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -54,22 +54,22 @@
 
 class SharedCollectionMixin(object):
 
+    @inlineCallbacks
     def inviteProperty(self, request):
         """
         Calculate the customxml.Invite property (for readProperty) from the
         invites database.
         """
+        isShared = yield self.isShared(request)
+        if config.Sharing.Enabled and isShared:
+            yield self.validateInvites()
+            records = yield self.invitesDB().allRecords()
+            returnValue(customxml.Invite(
+                *[record.makePropertyElement() for record in records]
+            ))
+        else:
+            returnValue(None)
 
-        def sharedOK(isShared):
-            if config.Sharing.Enabled and isShared:
-                self.validateInvites()
-                return customxml.Invite(
-                    *[record.makePropertyElement() for
-                        record in self.invitesDB().allRecords()]
-                )
-            else:
-                return None
-        return self.isShared(request).addCallback(sharedOK)
 
     def upgradeToShare(self):
         """ Upgrade this collection to a shared state """
@@ -92,7 +92,7 @@
         self.writeDeadProperty(rtype)
         
         # Remove all invitees
-        for record in self.invitesDB().allRecords():
+        for record in (yield self.invitesDB().allRecords()):
             yield self.uninviteRecordFromShare(record, request)
 
         # Remove invites database
@@ -101,15 +101,15 @@
     
         returnValue(True)
 
+
+    @inlineCallbacks
     def removeUserFromInvite(self, userid, request):
         """ Remove a user from this shared calendar """
-        self.invitesDB().removeRecordForUserID(userid)            
+        returnValue((yield self.invitesDB().removeRecordForUserID(userid)))
 
-        return succeed(True)
 
     @inlineCallbacks
     def changeUserInviteState(self, request, inviteUID, principalURL, state, summary=None):
-        
         shared = (yield self.isShared(request))
         if not shared:
             raise HTTPError(ErrorResponse(
@@ -118,7 +118,7 @@
                 "invalid share",
             ))
             
-        record = self.invitesDB().recordForInviteUID(inviteUID)
+        record = yield self.invitesDB().recordForInviteUID(inviteUID)
         if record is None or record.principalURL != principalURL:
             raise HTTPError(ErrorResponse(
                 responsecode.FORBIDDEN,
@@ -131,8 +131,9 @@
             record.state = state
             if summary is not None:
                 record.summary = summary
-            self.invitesDB().addOrUpdateRecord(record)
+            yield self.invitesDB().addOrUpdateRecord(record)
 
+
     @inlineCallbacks
     def directShare(self, request):
         """
@@ -174,9 +175,9 @@
         
         # Get the home collection
         if self.isCalendarCollection():
-            home = principal.calendarHome(request)
+            home = yield principal.calendarHome(request)
         elif self.isAddressBookCollection():
-            home = principal.addressBookHome(request)
+            home = yield principal.addressBookHome(request)
         else:
             raise HTTPError(ErrorResponse(
                 responsecode.FORBIDDEN,
@@ -196,38 +197,45 @@
 
         # Accept it
         response = (yield home.acceptDirectShare(request, request.path, self.resourceID(), self.displayName()))
-        
+
         # Return the URL of the shared calendar
         returnValue(response)
 
+
+    @inlineCallbacks
     def isShared(self, request):
         """ Return True if this is an owner shared calendar collection """
-        return succeed(self.isSpecialCollection(customxml.SharedOwner))
+        returnValue((yield self.isSpecialCollection(customxml.SharedOwner)))
 
+
     def setVirtualShare(self, shareePrincipal, share):
         self._isVirtualShare = True
         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())
 
+
     def isVirtualShare(self):
         """ Return True if this is a shared calendar collection """
         return hasattr(self, "_isVirtualShare")
 
+
+    @inlineCallbacks
     def removeVirtualShare(self, request):
         """ Return True if this is a shared calendar collection """
         
         # Remove from sharee's calendar/address book home
         if self.isCalendarCollection():
-            shareeHome = self._shareePrincipal.calendarHome(request)
+            shareeHome = yield self._shareePrincipal.calendarHome(request)
         elif self.isAddressBookCollection():
-            shareeHome = self._shareePrincipal.addressBookHome(request)
-        return shareeHome.removeShare(request, self._share)
+            shareeHome = yield self._shareePrincipal.addressBookHome(request)
+        returnValue((yield shareeHome.removeShare(request, self._share)))
 
+
     def resourceType(self):
         superObject = super(SharedCollectionMixin, self)
         try:
@@ -246,7 +254,8 @@
                 )
             )
         return rtype
-        
+
+
     def sharedResourceType(self):
         """
         Return the DAV:resourcetype stripped of any shared elements.
@@ -259,6 +268,7 @@
         else:
             return ""
 
+
     @inlineCallbacks
     def shareeAccessControlList(self, request, *args, **kwargs):
 
@@ -273,7 +283,9 @@
             # Invite shares use access mode from the invite
     
             # Get the invite for this sharee
-            invite = self.invitesDB().recordForInviteUID(self._share.shareuid)
+            invite = yield self.invitesDB().recordForInviteUID(
+                self._share.shareuid
+            )
             if invite is None:
                 returnValue(davxml.ACL())
             
@@ -289,7 +301,7 @@
                 userprivs.append(davxml.Privilege(davxml.Write()))
             proxyprivs = list(userprivs)
             proxyprivs.remove(davxml.Privilege(davxml.ReadACL()))
-    
+
             aces = (
                 # Inheritable specific access for the resource's associated principal.
                 davxml.ACE(
@@ -299,7 +311,7 @@
                     TwistedACLInheritable(),
                 ),
             )
-            
+
             if self.isCalendarCollection():
                 aces += (
                     # Inheritable CALDAV:read-free-busy access for authenticated users.
@@ -309,13 +321,13 @@
                         TwistedACLInheritable(),
                     ),
                 )
-    
+
             # Give read access to config.ReadPrincipals
             aces += config.ReadACEs
-    
+
             # Give all access to config.AdminPrincipals
             aces += config.AdminACEs
-            
+
             if config.EnableProxyPrincipals:
                 aces += (
                     # DAV:read/DAV:read-current-user-privilege-set access for this principal's calendar-proxy-read users.
@@ -336,7 +348,7 @@
                         TwistedACLInheritable(),
                     ),
                 )
-    
+
             returnValue(davxml.ACL(*aces))
 
     def validUserIDForShare(self, userid):
@@ -389,17 +401,20 @@
         else:
             return None, None, None
 
+
+    @inlineCallbacks
     def validateInvites(self):
         """
         Make sure each userid in an invite is valid - if not re-write status.
         """
         
-        records = self.invitesDB().allRecords()
+        records = yield self.invitesDB().allRecords()
         for record in records:
             if self.validUserIDForShare(record.userid) is None and record.state != "INVALID":
                 record.state = "INVALID"
-                self.invitesDB().addOrUpdateRecord(record)
-                
+                yield self.invitesDB().addOrUpdateRecord(record)
+
+
     def inviteUserToShare(self, userid, cn, ace, summary, request):
         """ Send out in invite first, and then add this user to the share list
             @param userid: 
@@ -455,6 +470,7 @@
         dl = [self.inviteSingleUserUpdateToShare(user, cn, aceOLD, aceNEW, summary, request) for user, cn in zip(userid, cn)]
         return DeferredList(dl).addCallback(_defer)
 
+
     @inlineCallbacks
     def inviteSingleUserToShare(self, userid, cn, ace, summary, request):
         
@@ -466,7 +482,7 @@
             returnValue(False)
 
         # Look for existing invite and update its fields or create new one
-        record = self.invitesDB().recordForPrincipalURL(principalURL)
+        record = yield self.invitesDB().recordForPrincipalURL(principalURL)
         if record:
             record.name = cn
             record.access = inviteAccessMapFromXML[type(ace)]
@@ -478,16 +494,22 @@
         yield self.sendInvite(record, request)
         
         # Add to database
-        self.invitesDB().addOrUpdateRecord(record)
+        yield self.invitesDB().addOrUpdateRecord(record)
         
         returnValue(True)            
 
+
+    @inlineCallbacks
     def uninviteSingleUserFromShare(self, userid, aces, request):
-        
         # Cancel invites - we'll just use whatever userid we are given
-        record = self.invitesDB().recordForUserID(userid)
-        return self.uninviteRecordFromShare(record, request) if record else succeed(True)
-        
+        record = yield self.invitesDB().recordForUserID(userid)
+        if record:
+            result = (yield self.uninviteRecordFromShare(record, request))
+        else:
+            result = True
+        returnValue(result)
+
+
     @inlineCallbacks
     def uninviteRecordFromShare(self, record, request):
         
@@ -495,9 +517,9 @@
         sharee = self.principalForCalendarUserAddress(record.userid)
         if sharee:
             if self.isCalendarCollection():
-                shareeHome = sharee.calendarHome(request)
+                shareeHome = yield sharee.calendarHome(request)
             elif self.isAddressBookCollection():
-                shareeHome = sharee.addressBookHome(request)
+                shareeHome = yield sharee.addressBookHome(request)
             yield shareeHome.removeShareByUID(request, record.inviteuid)
     
             # If current user state is accepted then we send an invite with the new state, otherwise
@@ -509,7 +531,7 @@
                 yield self.sendInvite(record, request)
     
         # Remove from database
-        self.invitesDB().removeRecordForUserID(record.userid)
+        yield self.invitesDB().removeRecordForUserID(record.userid)
         
         returnValue(True)            
 
@@ -530,7 +552,7 @@
         sharee = self.principalForCalendarUserAddress(record.userid)
         if sharee is None:
             raise ValueError("sharee is None but userid was valid before")
-        notifications = self._associatedTransaction.notificationsWithUID(
+        notifications = yield self._associatedTransaction.notificationsWithUID(
             sharee.principalUID()
         )
         
@@ -684,7 +706,7 @@
                         (okusers if result else badusers).add(userid)
 
                 # Do a final validation of the entire set of invites
-                self.validateInvites()
+                yield self.validateInvites()
                 
                 # Create the multistatus response - only needed if some are bad
                 if badusers:
@@ -938,27 +960,35 @@
     A mix-in for calendar/addressbook homes that defines the operations for
     manipulating a sharee's set of shared calendars.
     """
-    
 
+
+    @inlineCallbacks
     def provisionShare(self, name):
-        
         # Try to find a matching share
         child = None
-        shares = self.allShares()
+        shares = yield self.allShares()
         if name in shares:
             from twistedcaldav.sharedcollection import SharedCollectionResource
             child = SharedCollectionResource(self, shares[name])
             self.putChild(name, child)
-        return child
+        returnValue(child)
 
+
+    @inlineCallbacks
     def allShares(self):
         if not hasattr(self, "_allShares"):
-            self._allShares = dict([(share.localname, share) for share in self.sharesDB().allRecords()])
-        return self._allShares
+            allShareRecords = yield self.sharesDB().allRecords()
+            self._allShares = dict([(share.localname, share) for share in
+                                    allShareRecords])
+        returnValue(self._allShares)
 
+
+    @inlineCallbacks
     def allShareNames(self):
-        return tuple(self.allShares().keys())
+        allShares = yield self.allShares()
+        returnValue(tuple(allShares.keys()))
 
+
     @inlineCallbacks
     def acceptInviteShare(self, request, hostUrl, inviteUID, displayname=None):
         
@@ -977,12 +1007,12 @@
     def _acceptShare(self, request, sharetype, hostUrl, shareUID, displayname=None):
 
         # Add or update in DB
-        oldShare = self.sharesDB().recordForShareUID(shareUID)
+        oldShare = yield self.sharesDB().recordForShareUID(shareUID)
         if oldShare:
             share = oldShare
         else:
             share = SharedCollectionRecord(shareUID, sharetype, hostUrl, str(uuid4()), displayname)
-            self.sharesDB().addOrUpdateRecord(share)
+            yield self.sharesDB().addOrUpdateRecord(share)
         
         # Set per-user displayname to whatever was given
         sharedCollection = (yield request.locateResource(hostUrl))
@@ -1019,7 +1049,7 @@
     def removeShareByUID(self, request, shareUID):
         """ Remove a shared collection but do not send a decline back """
 
-        share = self.sharesDB().recordForShareUID(shareUID)
+        share = yield self.sharesDB().recordForShareUID(shareUID)
         if share:
             yield self.removeDirectShare(request, share)
 
@@ -1039,7 +1069,7 @@
                 inbox = (yield request.locateResource(inboxURL))
                 inbox.processFreeBusyCalendar(shareURL, False)
 
-        self.sharesDB().removeRecordForShareUID(share.shareuid)
+        yield self.sharesDB().removeRecordForShareUID(share.shareuid)
  
         # Notify client of changes
         self.notifyChanged()
@@ -1209,12 +1239,8 @@
         
         records = self._db_execute("select * from SHARES order by LOCALNAME")
         return [self._makeRecord(row) for row in (records if records is not None else ())]
-    
-    def recordForLocalName(self, localname):
-        
-        row = self._db_execute("select * from SHARES where LOCALNAME = :1", localname)
-        return self._makeRecord(row[0]) if row else None
-    
+
+
     def recordForShareUID(self, shareUID):
 
         row = self._db_execute("select * from SHARES where SHAREUID = :1", shareUID)

Modified: CalendarServer/trunk/twistedcaldav/storebridge.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/storebridge.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/storebridge.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -24,7 +24,8 @@
 
 from urlparse import urlsplit
 
-from twisted.internet.defer import succeed, inlineCallbacks, returnValue
+from twisted.internet.defer import succeed, inlineCallbacks, returnValue,\
+    maybeDeferred
 from twisted.internet.protocol import Protocol
 from twisted.python.log import err as logDefaultException
 from twisted.python.util import FancyEqMixin
@@ -35,7 +36,7 @@
 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.resource import TwistedACLInheritable
+from twext.web2.dav.resource import TwistedACLInheritable, AccessDeniedError
 from twext.web2.dav.util import parentForURL, allDataFromStream, joinURL, \
     davXMLFromStream
 from twext.web2.http import HTTPError, StatusResponse, Response
@@ -160,45 +161,54 @@
     def name(self):
         return self._newStoreObject.name() if self._newStoreObject is not None else None
 
+
+    @inlineCallbacks
     def etag(self):
         # FIXME: far too slow to be used for real, but I needed something to
         # placate the etag computation in the case where the file doesn't exist
         # yet (an uncommitted transaction creating this calendar file)
 
         if self._newStoreObject is None:
-            return None
+            returnValue(None)
 
         # FIXME: direct tests
         try:
-            md5 = self._newStoreObject.md5()
+            md5 = yield self._newStoreObject.md5()
             if md5:
-                return ETag(md5)
+                returnValue(ETag(md5))
             else:
-                return ETag(
-                    hashlib.new("md5", self.text()).hexdigest(),
+                returnValue(ETag(
+                    hashlib.new("md5", (yield self.text())).hexdigest(),
                     weak=False
-                )
+                ))
         except NoSuchObjectResourceError:
             # FIXME: a workaround for the fact that DELETE still rudely vanishes
             # the calendar object out from underneath the store, and doesn't
             # call storeRemove.
-            return None
+            returnValue(None)
 
+
     def contentType(self):
         return self._newStoreObject.contentType() if self._newStoreObject is not None else None
 
+
     def contentLength(self):
         return self._newStoreObject.size() if self._newStoreObject is not None else None
 
+
     def lastModified(self):
         return self._newStoreObject.modified() if self._newStoreObject is not None else None
 
+
     def creationDate(self):
         return self._newStoreObject.created() if self._newStoreObject is not None else None
 
+
     def newStoreProperties(self):
         return self._newStoreObject.properties() if self._newStoreObject is not None else None
 
+
+
 class _CalendarChildHelper(object):
     """
     Methods for things which are like calendars.
@@ -236,15 +246,21 @@
             self._invitesDB = self._newStoreCalendar.retrieveOldInvites()
         return self._invitesDB
 
+
     def exists(self):
         # FIXME: tests
         return True
 
 
+    @inlineCallbacks
     def _indexWhatChanged(self, revision, depth):
         # The newstore implementation supports this directly
-        return self._newStoreCalendar.resourceNamesSinceToken(revision) + ([],)
+        returnValue(
+            (yield self._newStoreCalendar.resourceNamesSinceToken(revision))
+            + ([],)
+        )
 
+
     @classmethod
     def transform(cls, self, calendar, home):
         """
@@ -254,13 +270,15 @@
         self._initializeWithCalendar(calendar, home)
 
 
+    @inlineCallbacks
     def makeChild(self, name):
         """
-        Create a L{CalendarObjectResource} or L{ProtoCalendarObjectResource} based on a
-        path object.
+        Create a L{CalendarObjectResource} or L{ProtoCalendarObjectResource}
+        based on a calendar object name.
         """
 
-        newStoreObject = self._newStoreCalendar.calendarObjectWithName(name)
+        cal = self._newStoreCalendar
+        newStoreObject = yield cal.calendarObjectWithName(name)
 
         if newStoreObject is not None:
             similar = CalendarObjectResource(
@@ -268,27 +286,23 @@
                 principalCollections=self._principalCollections
             )
         else:
-            # FIXME: creation in http_PUT should talk to a specific resource
-            # type; this is the domain of StoreCalendarObjectResource.
-            # similar = ProtoCalendarObjectFile(self._newStoreCalendar, path)
             similar = ProtoCalendarObjectResource(
-                self._newStoreCalendar,
-                name,
+                cal, 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)
-        return 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(self._newStoreCalendar.listCalendarObjects())
-        return sorted(children)
+        children.update((yield self._newStoreCalendar.listCalendarObjects()))
+        returnValue(sorted(children))
 
 
     def quotaSize(self, request):
@@ -302,37 +316,49 @@
     def __init__(self, *a, **kw):
         super(StoreScheduleInboxResource, self).__init__(*a, **kw)
         self.parent.propagateTransaction(self)
+
+
+    @classmethod
+    @inlineCallbacks
+    def maybeCreateInbox(cls, *a, **kw):
+        self = cls(*a, **kw)
         home = self.parent._newStoreHome
-        storage = home.calendarWithName("inbox")
+        storage = yield home.calendarWithName("inbox")
         if storage is None:
             # raise RuntimeError("backend should be handling this for us")
             # FIXME: spurious error, sanity check, should not be needed;
             # unfortunately, user09's calendar home does not have an inbox, so
             # this is a temporary workaround.
-            home.createCalendarWithName("inbox")
-            storage = home.calendarWithName("inbox")
+            yield home.createCalendarWithName("inbox")
+            storage = yield home.calendarWithName("inbox")
         self._initializeWithCalendar(
             storage,
             self.parent._newStoreHome
         )
+        returnValue(self)
 
 
     def name(self):
         return self._newStoreCalendar.name()
 
+
+    @inlineCallbacks
     def etag(self):
-        return ETag(self._newStoreCalendar.md5())
+        returnValue(ETag((yield 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
 
@@ -397,36 +423,42 @@
         return True
 
 
+    @inlineCallbacks
     def getChild(self, name):
-        calendarObject = self._newStoreHome.calendarObjectWithDropboxID(name)
+        calendarObject = yield self._newStoreHome.calendarObjectWithDropboxID(name)
         if calendarObject is None:
-            return NoDropboxHere()
+            returnValue(NoDropboxHere())
         objectDropbox = CalendarObjectDropbox(
             calendarObject, principalCollections=self.principalCollections()
         )
         self.propagateTransaction(objectDropbox)
-        return objectDropbox
+        returnValue(objectDropbox)
 
 
     def resourceType(self,):
         return davxml.ResourceType.dropboxhome #@UndefinedVariable
 
 
+    @inlineCallbacks
     def listChildren(self):
         l = []
-        for everyCalendar in self._newStoreHome.calendars():
-            for everyObject in everyCalendar.calendarObjects():
-                l.append(everyObject.dropboxID())
-        return l
+        for everyCalendar in (yield self._newStoreHome.calendars()):
+            for everyObject in (yield everyCalendar.calendarObjects()):
+                l.append((yield everyObject.dropboxID()))
+        returnValue(l)
 
+
+
 class NoDropboxHere(_GetChildHelper):
 
     def isCollection(self):
         return False
 
+
     def exists(self):
         return False
 
+
     def http_GET(self, request):
         return NOT_FOUND
 
@@ -459,8 +491,9 @@
         return davxml.ResourceType.dropbox #@UndefinedVariable
 
 
+    @inlineCallbacks
     def getChild(self, name):
-        attachment = self._newStoreCalendarObject.attachmentWithName(name)
+        attachment = yield self._newStoreCalendarObject.attachmentWithName(name)
         if attachment is None:
             result = ProtoCalendarAttachment(
                 self._newStoreCalendarObject,
@@ -471,7 +504,7 @@
                 self._newStoreCalendarObject,
                 attachment, principalCollections=self.principalCollections())
         self.propagateTransaction(result)
-        return result
+        returnValue(result)
 
 
     @inlineCallbacks
@@ -481,7 +514,7 @@
         that refer to permissions not referenced by attendees in the iCalendar
         data.
         """
-        attendees = self._newStoreCalendarObject.component().getAttendees()
+        attendees = (yield self._newStoreCalendarObject.component()).getAttendees()
         attendees = [attendee.split("urn:uuid:")[-1] for attendee in attendees]
         document = yield davXMLFromStream(request.stream)
         for ace in document.root_element.children:
@@ -516,69 +549,73 @@
         return NO_CONTENT
 
 
+    @inlineCallbacks
     def listChildren(self):
         l = []
-        for attachment in self._newStoreCalendarObject.attachments():
+        for attachment in (self._newStoreCalendarObject.attachments()):
             l.append(attachment.name())
-        return l
+        returnValue(l)
 
 
+    @inlineCallbacks
     def accessControlList(self, *a, **kw):
         """
         All principals identified as ATTENDEEs on the event for this dropbox
         may read all its children. Also include proxies of ATTENDEEs. Ignore
         unknown attendees.
         """
-        d = super(CalendarObjectDropbox, self).accessControlList(*a, **kw)
-        def moreACLs(originalACL):
-            othersCanWrite = (
-                self._newStoreCalendarObject.attendeesCanManageAttachments()
+        originalACL = yield super(
+            CalendarObjectDropbox, self).accessControlList(*a, **kw)
+        othersCanWrite = (
+            yield self._newStoreCalendarObject.attendeesCanManageAttachments()
+        )
+        originalACEs = list(originalACL.children)
+        cuas = (yield self._newStoreCalendarObject.component()).getAttendees()
+        newACEs = []
+        for calendarUserAddress in cuas:
+            principal = self.principalForCalendarUserAddress(
+                calendarUserAddress
             )
-            originalACEs = list(originalACL.children)
-            cuas = self._newStoreCalendarObject.component().getAttendees()
-            newACEs = []
-            for calendarUserAddress in cuas:
-                principal = self.principalForCalendarUserAddress(
-                    calendarUserAddress
-                )
-                if principal is None:
-                    continue
-                principalURL = principal.principalURL()
-                writePrivileges = [
-                    davxml.Privilege(davxml.Read()),
-                    davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()),
-                    davxml.Privilege(davxml.Write()),
-                ]
-                readPrivileges = [
-                    davxml.Privilege(davxml.Read()),
-                    davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()),
-                ]
-                privileges = writePrivileges if othersCanWrite else readPrivileges
-                newACEs.append(davxml.ACE(
-                    davxml.Principal(davxml.HRef(principalURL)),
-                    davxml.Grant(*privileges),
-                    davxml.Protected(),
-                    TwistedACLInheritable(),
-                ))
-                newACEs.append(davxml.ACE(
-                    davxml.Principal(davxml.HRef(joinURL(principalURL, "calendar-proxy-write/"))),
-                    davxml.Grant(*privileges),
-                    davxml.Protected(),
-                    TwistedACLInheritable(),
-                ))
-                newACEs.append(davxml.ACE(
-                    davxml.Principal(davxml.HRef(joinURL(principalURL, "calendar-proxy-read/"))),
-                    davxml.Grant(*readPrivileges),
-                    davxml.Protected(),
-                    TwistedACLInheritable(),
-                ))
+            if principal is None:
+                continue
 
-            return davxml.ACL(*tuple(newACEs + originalACEs))
-        d.addCallback(moreACLs)
-        return d
+            principalURL = principal.principalURL()
+            writePrivileges = [
+                davxml.Privilege(davxml.Read()),
+                davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()),
+                davxml.Privilege(davxml.Write()),
+            ]
+            readPrivileges = [
+                davxml.Privilege(davxml.Read()),
+                davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()),
+            ]
+            if othersCanWrite:
+                privileges = writePrivileges
+            else:
+                privileges = readPrivileges
+            newACEs.append(davxml.ACE(
+                davxml.Principal(davxml.HRef(principalURL)),
+                davxml.Grant(*privileges),
+                davxml.Protected(),
+                TwistedACLInheritable(),
+            ))
+            newACEs.append(davxml.ACE(
+                davxml.Principal(davxml.HRef(joinURL(principalURL, "calendar-proxy-write/"))),
+                davxml.Grant(*privileges),
+                davxml.Protected(),
+                TwistedACLInheritable(),
+            ))
+            newACEs.append(davxml.ACE(
+                davxml.Principal(davxml.HRef(joinURL(principalURL, "calendar-proxy-read/"))),
+                davxml.Grant(*readPrivileges),
+                davxml.Protected(),
+                TwistedACLInheritable(),
+            ))
 
+        returnValue(davxml.ACL(*tuple(newACEs + originalACEs)))
 
 
+
 class ProtoCalendarAttachment(_NewStoreFileMetaDataHelper, _GetChildHelper):
 
     def __init__(self, calendarObject, attachmentName, **kw):
@@ -587,6 +624,7 @@
         self.attachmentName = attachmentName
         self._newStoreObject = None
 
+
     def isCollection(self):
         return False
 
@@ -601,28 +639,29 @@
 
 
     @requiresPermissions(fromParent=[davxml.Bind()])
+    @inlineCallbacks
     def http_PUT(self, request):
         # FIXME: direct test
         # FIXME: transformation?
-
         content_type = request.headers.getHeader("content-type")
         if content_type is None:
             content_type = MimeType("application", "octet-stream")
-
-        t = self.calendarObject.createAttachmentWithName(
+        t = yield self.calendarObject.createAttachmentWithName(
             self.attachmentName,
             content_type,
         )
-        def done(ignored):
-            self._newStoreObject = self.calendarObject.attachmentWithName(self.attachmentName)
-            t.loseConnection()
-            return CREATED
-        return readStream(request.stream, t.write).addCallback(done)
+        yield readStream(request.stream, t.write)
+        self._newStoreObject = yield self.calendarObject.attachmentWithName(
+            self.attachmentName
+        )
+        t.loseConnection()
+        returnValue(CREATED)
 
     http_MKCOL = None
     http_MKCALENDAR = None
 
 
+
 class CalendarAttachment(_NewStoreFileMetaDataHelper, _GetChildHelper):
 
     def __init__(self, calendarObject, attachment, **kw):
@@ -631,10 +670,11 @@
         self._newStoreAttachment = self._newStoreObject = attachment
 
 
+    @inlineCallbacks
     def etag(self):
         # FIXME: test
-        md5 = self._newStoreAttachment.md5()
-        return ETag(md5)
+        md5 = yield self._newStoreAttachment.md5()
+        returnValue(ETag(md5))
 
 
     def contentType(self):
@@ -674,15 +714,18 @@
         self._newStoreAttachment.retrieve(StreamProtocol())
         return Response(OK, {"content-type":self.contentType()}, stream)
 
+
     @requiresPermissions(fromParent=[davxml.Unbind()])
+    @inlineCallbacks
     def http_DELETE(self, request):
-        self._newStoreCalendarObject.removeAttachmentWithName(
+        yield self._newStoreCalendarObject.removeAttachmentWithName(
             self._newStoreAttachment.name()
         )
         del self._newStoreCalendarObject
         self.__class__ = ProtoCalendarAttachment
-        return NO_CONTENT
+        returnValue(NO_CONTENT)
 
+
     http_MKCOL = None
     http_MKCALENDAR = None
 
@@ -714,18 +757,24 @@
     def name(self):
         return self._newStoreCalendar.name()
 
+
+    @inlineCallbacks
     def etag(self):
-        return ETag(self._newStoreCalendar.md5())
+        returnValue(ETag((yield 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
 
@@ -737,6 +786,62 @@
         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 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):
@@ -807,7 +912,7 @@
 
         errors = ResponseQueue(where, "DELETE", NO_CONTENT)
 
-        for childname in self.listChildren():
+        for childname in (yield self.listChildren()):
 
             childurl = joinURL(where, childname)
 
@@ -830,7 +935,7 @@
             yield self.downgradeFromShare(request)
 
         # Actually delete it.
-        self._newStoreParentHome.removeCalendarWithName(
+        yield self._newStoreParentHome.removeCalendarWithName(
             self._newStoreCalendar.name()
         )
         self.__class__ = ProtoCalendarCollectionResource
@@ -876,7 +981,7 @@
         # FIXME: should really use something other than 'fp' attribute.
         basename = destination.name()
         calendar = self._newStoreCalendar
-        calendar.rename(basename)
+        yield calendar.rename(basename)
         CalendarCollectionResource.transform(destination, calendar,
                                          self._newStoreParentHome)
         del self._newStoreCalendar
@@ -937,20 +1042,19 @@
         return self.createCalendarCollection()
 
 
+    @inlineCallbacks
     def createCalendarCollection(self):
         """
         Override C{createCalendarCollection} to actually do the work.
         """
-        d = succeed(CREATED)
-
-        self._newStoreParentHome.createCalendarWithName(self._name)
-        newStoreCalendar = self._newStoreParentHome.calendarWithName(
+        yield self._newStoreParentHome.createCalendarWithName(self._name)
+        newStoreCalendar = yield self._newStoreParentHome.calendarWithName(
             self._name
         )
         CalendarCollectionResource.transform(
             self, newStoreCalendar, self._newStoreParentHome
         )
-        return d
+        returnValue(CREATED)
 
 
     def exists(self):
@@ -992,17 +1096,17 @@
         self._initializeWithObject(calendarObject)
 
 
+    @inlineCallbacks
     def inNewTransaction(self, request):
         """
         Implicit auto-replies need to span multiple transactions.  Clean out
         the given request's resource-lookup mapping, transaction, and re-look-
-        up my calendar object in a new transaction.
+        up this L{CalendarObjectResource}'s calendar object in a new
+        transaction.
 
-        @return: the new transaction so it can be committed.
+        @return: a Deferred which fires with the new transaction, so it can be
+            committed.
         """
-        # FIXME: private names from 'file' implementation; maybe there should
-        # be a public way to do this?  or maybe we should just have a real
-        # queue.
         objectName = self._newStoreObject.name()
         calendar = self._newStoreObject.calendar()
         calendarName = calendar.name()
@@ -1010,14 +1114,14 @@
         homeUID = ownerHome.uid()
         txn = ownerHome.transaction().store().newTransaction(
             "new transaction for " + self._newStoreObject.name())
-        newObject = (txn.calendarHomeWithUID(homeUID)
-                        .calendarWithName(calendarName)
-                        .calendarObjectWithName(objectName))
+        newObject = ((yield (yield (yield txn.calendarHomeWithUID(homeUID))
+                             .calendarWithName(calendarName))
+                             .calendarObjectWithName(objectName)))
         request._newStoreTransaction = txn
         request._resourcesByURL.clear()
         request._urlsByResource.clear()
         self._initializeWithObject(newObject)
-        return txn
+        returnValue(txn)
 
 
     def isCollection(self):
@@ -1029,16 +1133,20 @@
         return True
 
 
+    @inlineCallbacks
     def quotaSize(self, request):
         # FIXME: tests
-        return succeed(len(self._newStoreObject.iCalendarText()))
+        returnValue(len((yield self.iCalendarText())))
 
 
-    def iCalendarText(self, ignored=None):
-        assert ignored is None, "This is a calendar object, not a calendar"
+    def iCalendarText(self):
         return self._newStoreObject.iCalendarText()
 
 
+    def iCalendar(self):
+        return self._newStoreObject.component()
+
+
     def text(self):
         return self.iCalendarText()
 
@@ -1057,7 +1165,7 @@
         component = vcomponent.VComponent.fromString(
             (yield allDataFromStream(stream))
         )
-        self._newStoreObject.setComponent(component)
+        yield self._newStoreObject.setComponent(component)
         returnValue(NO_CONTENT)
 
 
@@ -1152,7 +1260,7 @@
 
             # FIXME: public attribute please.  Should ICalendar maybe just have
             # a delete() method?
-            storeCalendar.removeCalendarObjectWithName(
+            yield storeCalendar.removeCalendarObjectWithName(
                 self._newStoreObject.name()
             )
 
@@ -1212,10 +1320,15 @@
         component = vcomponent.VComponent.fromString(
             (yield allDataFromStream(stream))
         )
-        self._newStoreParentCalendar.createCalendarObjectWithName(
+        yield self._newStoreParentCalendar.createCalendarObjectWithName(
             self.name(), component
         )
-        CalendarObjectResource.transform(self, self._newStoreParentCalendar.calendarObjectWithName(self.name()))
+        CalendarObjectResource.transform(
+            self,
+            (yield self._newStoreParentCalendar.calendarObjectWithName(
+                self.name()
+            ))
+        )
         returnValue(CREATED)
 
 
@@ -1277,15 +1390,21 @@
             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
-        return self._newStoreAddressBook.resourceNamesSinceToken(revision) + ([],)
+        returnValue(
+            (yield self._newStoreAddressBook.resourceNamesSinceToken(revision))
+            + ([],)
+        )
 
+
     @classmethod
     def transform(cls, self, addressbook, home):
         """
@@ -1295,12 +1414,13 @@
         self._initializeWithAddressBook(addressbook, home)
 
 
+    @inlineCallbacks
     def makeChild(self, name):
         """
         Create a L{AddressBookObjectResource} or L{ProtoAddressBookObjectResource} based on a
         path object.
         """
-        newStoreObject = self._newStoreAddressBook.addressbookObjectWithName(name)
+        newStoreObject = yield self._newStoreAddressBook.addressbookObjectWithName(name)
 
         if newStoreObject is not None:
             similar = AddressBookObjectResource(
@@ -1320,15 +1440,19 @@
         # FIXME: tests should be failing without this line.
         # Specifically, http_PUT won't be committing its transaction properly.
         self.propagateTransaction(similar)
-        return 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(self._newStoreAddressBook.listAddressbookObjects())
-        return sorted(children)
+        children.update(
+            (yield self._newStoreAddressBook.listAddressbookObjects())
+        )
+        returnValue(sorted(children))
 
 
 
@@ -1355,18 +1479,24 @@
     def name(self):
         return self._newStoreAddressBook.name()
 
+
+    @inlineCallbacks
     def etag(self):
-        return ETag(self._newStoreAddressBook.md5())
+        returnValue(ETag((yield 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
 
@@ -1433,7 +1563,7 @@
 
         errors = ResponseQueue(where, "DELETE", NO_CONTENT)
 
-        for childname in self.listChildren():
+        for childname in (yield self.listChildren()):
 
             childurl = joinURL(where, childname)
 
@@ -1456,7 +1586,7 @@
             yield self.downgradeFromShare(request)
 
         # Actually delete it.
-        self._newStoreParentHome.removeAddressBookWithName(
+        yield self._newStoreParentHome.removeAddressBookWithName(
             self._newStoreAddressBook.name()
         )
         self.__class__ = ProtoAddressBookCollectionResource
@@ -1497,7 +1627,7 @@
         # FIXME: should really use something other than 'fp' attribute.
         basename = destination.name()
         addressbook = self._newStoreAddressBook
-        addressbook.rename(basename)
+        yield addressbook.rename(basename)
         AddressBookCollectionResource.transform(destination, addressbook,
                                          self._newStoreParentHome)
         del self._newStoreAddressBook
@@ -1547,20 +1677,19 @@
         return self.createAddressBookCollection()
 
 
+    @inlineCallbacks
     def createAddressBookCollection(self):
         """
         Override C{createAddressBookCollection} to actually do the work.
         """
-        d = succeed(CREATED)
-
-        self._newStoreParentHome.createAddressBookWithName(self._name)
-        newStoreAddressBook = self._newStoreParentHome.addressbookWithName(
+        yield self._newStoreParentHome.createAddressBookWithName(self._name)
+        newStoreAddressBook = yield self._newStoreParentHome.addressbookWithName(
             self._name
         )
         AddressBookCollectionResource.transform(
             self, newStoreAddressBook, self._newStoreParentHome
         )
-        return d
+        returnValue(CREATED)
 
 
     def exists(self):
@@ -1623,9 +1752,10 @@
         return True
 
 
+    @inlineCallbacks
     def quotaSize(self, request):
         # FIXME: tests
-        return succeed(len(self._newStoreObject.vCardText()))
+        returnValue(len((yield self._newStoreObject.vCardText())))
 
 
     def vCardText(self, ignored=None):
@@ -1637,13 +1767,15 @@
         return self.vCardText()
 
 
+    @inlineCallbacks
     def render(self, request):
-        output = self.vCardText()
+        output = yield self.vCardText()
 
         response = Response(200, {}, output)
         response.headers.setHeader("content-type", self.contentType())
-        return response
+        returnValue(response)
 
+
     @requiresPermissions(fromParent=[davxml.Unbind()])
     def http_DELETE(self, request):
         """
@@ -1658,7 +1790,7 @@
         component = VCard.fromString(
             (yield allDataFromStream(stream))
         )
-        self._newStoreObject.setComponent(component)
+        yield self._newStoreObject.setComponent(component)
         returnValue(NO_CONTENT)
 
 
@@ -1681,7 +1813,9 @@
             # Do delete
 
             # FIXME: public attribute please
-            storeAddressBook.removeAddressBookObjectWithName(self._newStoreObject.name())
+            yield storeAddressBook.removeAddressBookObjectWithName(
+                self._newStoreObject.name()
+            )
 
             # FIXME: clean this up with a 'transform' method
             self._newStoreParentAddressBook = storeAddressBook
@@ -1721,16 +1855,22 @@
         self._newStoreParentAddressBook = parentAddressBook
         self._name = name
 
+
     @inlineCallbacks
     def storeStream(self, stream):
         # FIXME: direct tests 
         component = VCard.fromString(
             (yield allDataFromStream(stream))
         )
-        self._newStoreParentAddressBook.createAddressBookObjectWithName(
+        yield self._newStoreParentAddressBook.createAddressBookObjectWithName(
             self.name(), component
         )
-        AddressBookObjectResource.transform(self, self._newStoreParentAddressBook.addressbookObjectWithName(self.name()))
+        AddressBookObjectResource.transform(
+            self,
+            (yield self._newStoreParentAddressBook.addressbookObjectWithName(
+                self.name())
+            )
+        )
         returnValue(CREATED)
 
 
@@ -1806,12 +1946,15 @@
         self._initializeWithNotifications(notifications, home)
 
 
+    @inlineCallbacks
     def makeChild(self, name):
         """
-        Create a L{NotificationObjectFile} or L{ProtoNotificationObjectFile} based on a
-        path object.
+        Create a L{NotificationObjectFile} or L{ProtoNotificationObjectFile}
+        based on the name of a notification.
         """
-        newStoreObject = self._newStoreNotifications.notificationObjectWithName(name)
+        newStoreObject = (
+            yield self._newStoreNotifications.notificationObjectWithName(name)
+        )
 
         if newStoreObject is not None:
             similar = StoreNotificationObjectFile(newStoreObject, self)
@@ -1824,18 +1967,19 @@
         # FIXME: tests should be failing without this line.
         # Specifically, http_PUT won't be committing its transaction properly.
         self.propagateTransaction(similar)
-        return 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(self._newStoreNotifications.listNotificationObjects())
-        return children
+        returnValue(children)
 
 
-
     def quotaSize(self, request):
         # FIXME: tests, workingness
         return succeed(0)
@@ -1860,30 +2004,44 @@
     def name(self):
         return "notification"
 
+    @inlineCallbacks
     def listChildren(self):
         l = []
-        for notification in self._newStoreNotifications.notificationObjects():
+        for notification in (yield self._newStoreNotifications.notificationObjects()):
             l.append(notification.name())
-        return l
+        returnValue(l)
 
     def isCollection(self):
         return True
 
+
     def getSyncToken(self):
         return self._newStoreNotifications.syncToken()
 
+
+    @inlineCallbacks
     def _indexWhatChanged(self, revision, depth):
-        return self._newStoreNotifications.resourceNamesSinceToken(revision) + ([],)
+        # The newstore implementation supports this directly
+        returnValue(
+            (yield self._newStoreNotifications.resourceNamesSinceToken(revision))
+            + ([],)
+        )
 
+
     def addNotification(self, request, uid, xmltype, xmldata):
+        return maybeDeferred(
+            self._newStoreNotifications.writeNotificationObject,
+            uid, xmltype, xmldata
+        )
 
-        self._newStoreNotifications.writeNotificationObject(uid, xmltype, xmldata)
-        return succeed(None)
 
     def deleteNotification(self, request, record):
-        self._newStoreNotifications.removeNotificationObjectWithName(record.name)
-        return succeed(None)
+        return maybeDeferred(
+            self._newStoreNotifications.removeNotificationObjectWithName,
+            record.name
+        )
 
+
 class StoreProtoNotificationCollectionResource(NotificationCollectionResource):
     """
     A resource representing a notification collection which hasn't yet been created.
@@ -1982,6 +2140,7 @@
         return True
 
 
+    @inlineCallbacks
     def etag(self):
         # FIXME: far too slow to be used for real, but I needed something to
         # placate the etag computation in the case where the file doesn't exist
@@ -1989,20 +2148,21 @@
 
         # FIXME: direct tests
         try:
-            md5 = self._newStoreObject.md5()
+            md5 = yield self._newStoreObject.md5()
             if md5:
-                return ETag(md5)
+                returnValue(ETag(md5))
             else:
-                return ETag(
-                    hashlib.new("md5", self.text()).hexdigest(),
+                returnValue(ETag(
+                    hashlib.new("md5", (yield self.text())).hexdigest(),
                     weak=False
-                )
+                ))
         except NoSuchObjectResourceError:
             # FIXME: a workaround for the fact that DELETE still rudely vanishes
             # the calendar object out from underneath the store, and doesn't
             # call storeRemove.
-            return None
+            returnValue(None)
 
+
     def contentType(self):
         return self._newStoreObject.contentType()
 
@@ -2015,13 +2175,15 @@
     def creationDate(self):
         return self._newStoreObject.created()
 
+
     def newStoreProperties(self):
         return self._newStoreObject.properties()
 
 
+    @inlineCallbacks
     def quotaSize(self, request):
         # FIXME: tests
-        return succeed(len(self._newStoreObject.xmldata()))
+        returnValue(len((yield self._newStoreObject.xmldata())))
 
 
     def text(self, ignored=None):
@@ -2030,9 +2192,14 @@
 
 
     @requiresPermissions(davxml.Read())
+    @inlineCallbacks
     def http_GET(self, request):
-        return Response(OK, {"content-type":self.contentType()}, MemoryStream(self.text()))
+        returnValue(
+            Response(OK, {"content-type":self.contentType()},
+                     MemoryStream((yield self.text())))
+        )
 
+
     @requiresPermissions(fromParent=[davxml.Unbind()])
     def http_DELETE(self, request):
         """
@@ -2060,7 +2227,9 @@
             # Do delete
 
             # FIXME: public attribute please
-            storeNotifications.removeNotificationObjectWithName(self._newStoreObject.name())
+            yield storeNotifications.removeNotificationObjectWithName(
+                self._newStoreObject.name()
+            )
 
             # FIXME: clean this up with a 'transform' method
             self._newStoreParentNotifications = storeNotifications

Modified: CalendarServer/trunk/twistedcaldav/test/test_addressbookmultiget.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_addressbookmultiget.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/test/test_addressbookmultiget.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -27,7 +27,11 @@
 
 from twistedcaldav import carddavxml
 from twistedcaldav import vcard
-from twistedcaldav.index import db_basename
+
+# FIXME: remove this, we should not be importing this module, we should be
+# testing the public API.  See comments below about cheating.
+from txdav.carddav.datastore.index_file import db_basename
+
 from twistedcaldav.config import config
 from twistedcaldav.test.util import AddressBookHomeTestCase
 

Modified: CalendarServer/trunk/twistedcaldav/test/test_addressbookquery.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_addressbookquery.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/test/test_addressbookquery.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -25,8 +25,11 @@
 from twext.web2.dav.util import davXMLFromStream
 from twext.web2.test.test_server import SimpleRequest
 
+# FIXME: remove this, we should not be importing this module, we should be
+# testing the public API.  See comments below about cheating.
+from txdav.carddav.datastore.index_file import db_basename
+
 from twistedcaldav import carddavxml, vcard
-from twistedcaldav.index import db_basename
 from twistedcaldav.config import config
 from twistedcaldav.test.util import AddressBookHomeTestCase
 

Modified: CalendarServer/trunk/twistedcaldav/test/test_calendarquery.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_calendarquery.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/test/test_calendarquery.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -34,8 +34,7 @@
 from twistedcaldav.config import config
 from twistedcaldav.test.util import HomeTestCase
 from twisted.internet.defer import inlineCallbacks, returnValue
-from txdav.common.datastore.test.util import buildStore
-from txdav.caldav.datastore.test.common import StubNotifierFactory
+from txdav.common.datastore.test.util import buildStore, StubNotifierFactory
 
 
 @inlineCallbacks

Modified: CalendarServer/trunk/twistedcaldav/test/test_extensions.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_extensions.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/test/test_extensions.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -21,11 +21,11 @@
 from twext.web2.http_headers import MimeType
 from twext.web2.static import MetaDataMixin
 
-from twisted.internet.defer import inlineCallbacks, succeed
+from twisted.internet.defer import inlineCallbacks, Deferred, succeed
 from twisted.trial.unittest import TestCase
 from twisted.web.microdom import parseString
 
-from twistedcaldav.extensions import DAVFile
+from twistedcaldav.extensions import DAVFile, DAVResourceWithChildrenMixin
 
 from xml.etree.cElementTree import XML
 
@@ -184,3 +184,30 @@
         yield self.doDirectoryTest([nonASCIIFilename], addUnicodeChild,
                                    [nonASCIIFilename.encode("utf-8")])
 
+
+
+class ChildTraversalTests(TestCase):
+    def test_makeChildDeferred(self):
+        """
+        If L{DAVResourceWithChildrenMixin.makeChild} returns a L{Deferred},
+        L{DAVResourceWithChildrenMixin.locateChild} will return a L{Deferred}.
+        """
+        class FakeChild(object):
+            def __init__(self, name):
+                self.name = name
+        class SmellsLikeDAVResource(object):
+            def __init__(self, **kw):
+                pass
+        class ResourceWithCheese(DAVResourceWithChildrenMixin,
+                                 SmellsLikeDAVResource):
+            def makeChild(self, name):
+                return succeed(FakeChild(name))
+        d = ResourceWithCheese().locateChild(None, ['cheese', 'burger'])
+        self.assertIsInstance(d, Deferred)
+        x = []
+        d.addCallback(x.append)
+        self.assertEquals(len(x), 1)
+        [result] = x
+        self.assertEquals(len(result), 2)
+        self.assertEquals(result[0].name, 'cheese')
+        self.assertEquals(result[1], ['burger'])

Deleted: CalendarServer/trunk/twistedcaldav/test/test_index.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_index.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/test/test_index.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -1,936 +0,0 @@
-##
-# Copyright (c) 2010 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-
-from twisted.internet import reactor
-from twisted.internet.task import deferLater
-
-from twistedcaldav import caldavxml
-from twistedcaldav.caldavxml import TimeRange
-from twistedcaldav.ical import Component
-from twistedcaldav.index import Index
-from twistedcaldav.index import ReservationError, MemcachedUIDReserver
-from twistedcaldav.instance import InvalidOverriddenInstanceError
-from twistedcaldav.query import calendarqueryfilter
-from twistedcaldav.test.util import InMemoryMemcacheProtocol
-import twistedcaldav.test.util
-
-import datetime
-import os
-
-
-class MinimalResourceReplacement(object):
-    """
-    Provide the minimal set of attributes and methods from CalDAVFile required
-    by L{Index}.
-    """
-
-    def __init__(self, filePath):
-        self.fp = filePath
-
-
-    def isCalendarCollection(self):
-        return True
-
-
-    def getChild(self, name):
-        # FIXME: this should really return something with a child method
-        return self.fp.child(name)
-
-
-    def initSyncToken(self):
-        pass
-
-
-
-class SQLIndexTests (twistedcaldav.test.util.TestCase):
-    """
-    Test abstract SQL DB class
-    """
-
-    def setUp(self):
-        super(SQLIndexTests, self).setUp()
-        self.site.resource.isCalendarCollection = lambda: True
-        self.indexDirPath = self.site.resource.fp
-        # FIXME: since this resource lies about isCalendarCollection, it doesn't
-        # have all the associated backend machinery to actually get children.
-        self.db = Index(MinimalResourceReplacement(self.indexDirPath))
-
-
-    def test_reserve_uid_ok(self):
-        uid = "test-test-test"
-        d = self.db.isReservedUID(uid)
-        d.addCallback(self.assertFalse)
-        d.addCallback(lambda _: self.db.reserveUID(uid))
-        d.addCallback(lambda _: self.db.isReservedUID(uid))
-        d.addCallback(self.assertTrue)
-        d.addCallback(lambda _: self.db.unreserveUID(uid))
-        d.addCallback(lambda _: self.db.isReservedUID(uid))
-        d.addCallback(self.assertFalse)
-
-        return d
-
-
-    def test_reserve_uid_twice(self):
-        uid = "test-test-test"
-        d = self.db.reserveUID(uid)
-        d.addCallback(lambda _: self.db.isReservedUID(uid))
-        d.addCallback(self.assertTrue)
-        d.addCallback(lambda _:
-                      self.assertFailure(self.db.reserveUID(uid),
-                                         ReservationError))
-        return d
-
-
-    def test_unreserve_unreserved(self):
-        uid = "test-test-test"
-        return self.assertFailure(self.db.unreserveUID(uid),
-                                  ReservationError)
-
-
-    def test_reserve_uid_timeout(self):
-        # WARNING: This test is fundamentally flawed and will fail
-        # intermittently because it uses the real clock.
-        uid = "test-test-test"
-        from twistedcaldav.config import config
-        old_timeout = config.UIDReservationTimeOut
-        config.UIDReservationTimeOut = 1
-
-        def _finally():
-            config.UIDReservationTimeOut = old_timeout
-
-        d = self.db.isReservedUID(uid)
-        d.addCallback(self.assertFalse)
-        d.addCallback(lambda _: self.db.reserveUID(uid))
-        d.addCallback(lambda _: self.db.isReservedUID(uid))
-        d.addCallback(self.assertTrue)
-        d.addCallback(lambda _: deferLater(reactor, 2, lambda: None))
-        d.addCallback(lambda _: self.db.isReservedUID(uid))
-        d.addCallback(self.assertFalse)
-        self.addCleanup(_finally)
-
-        return d
-
-
-    def test_index(self):
-        data = (
-            (
-                "#1.1 Simple component",
-                "1.1",
-                """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890-1.1
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-END:VEVENT
-END:VCALENDAR
-""",
-                False,
-                True,
-            ),
-            (
-                "#2.1 Recurring component",
-                "2.1",
-                """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890-2.1
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-RRULE:FREQ=WEEKLY;COUNT=2
-END:VEVENT
-END:VCALENDAR
-""",
-                False,
-                True,
-            ),
-            (
-                "#2.2 Recurring component with override",
-                "2.2",
-                """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890-2.2
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-RRULE:FREQ=WEEKLY;COUNT=2
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890-2.2
-RECURRENCE-ID:20080608T120000Z
-DTSTART:20080608T120000Z
-DTEND:20080608T130000Z
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-END:VEVENT
-END:VCALENDAR
-""",
-                False,
-                True,
-            ),
-            (
-                "#2.3 Recurring component with broken override - new",
-                "2.3",
-                """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890-2.3
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-RRULE:FREQ=WEEKLY;COUNT=2
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890-2.3
-RECURRENCE-ID:20080609T120000Z
-DTSTART:20080608T120000Z
-DTEND:20080608T130000Z
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-END:VEVENT
-END:VCALENDAR
-""",
-                False,
-                False,
-            ),
-            (
-                "#2.4 Recurring component with broken override - existing",
-                "2.4",
-                """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890-2.4
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-RRULE:FREQ=WEEKLY;COUNT=2
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890-2.4
-RECURRENCE-ID:20080609T120000Z
-DTSTART:20080608T120000Z
-DTEND:20080608T130000Z
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-END:VEVENT
-END:VCALENDAR
-""",
-                True,
-                True,
-            ),
-        )
-
-        for description, name, calendar_txt, reCreate, ok in data:
-            calendar = Component.fromString(calendar_txt)
-            if ok:
-                f = open(os.path.join(self.indexDirPath.path, name), "w")
-                f.write(calendar_txt)
-                del f
-
-                self.db.addResource(name, calendar, reCreate=reCreate)
-                self.assertTrue(self.db.resourceExists(name), msg=description)
-            else:
-                self.assertRaises(InvalidOverriddenInstanceError, self.db.addResource, name, calendar)
-                self.assertFalse(self.db.resourceExists(name), msg=description)
-
-        self.db._db_recreate()
-        for description, name, calendar_txt, reCreate, ok in data:
-            if ok:
-                self.assertTrue(self.db.resourceExists(name), msg=description)
-            else:
-                self.assertFalse(self.db.resourceExists(name), msg=description)
-
-        self.db.testAndUpdateIndex(datetime.date(2020, 1, 1))
-        for description, name, calendar_txt, reCreate, ok in data:
-            if ok:
-                self.assertTrue(self.db.resourceExists(name), msg=description)
-            else:
-                self.assertFalse(self.db.resourceExists(name), msg=description)
-
-    def test_index_timespan(self):
-        data = (
-            (
-                "#1.1 Simple component - busy",
-                "1.1",
-                """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890-1.1
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-END:VEVENT
-END:VCALENDAR
-""",
-                "20080601T000000Z", "20080602T000000Z",
-                "mailto:user1 at example.com",
-                (('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'F'),),
-            ),
-            (
-                "#1.2 Simple component - transparent",
-                "1.2",
-                """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890-1.2
-DTSTART:20080602T120000Z
-DTEND:20080602T130000Z
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-TRANSP:TRANSPARENT
-END:VEVENT
-END:VCALENDAR
-""",
-                "20080602T000000Z", "20080603T000000Z",
-                "mailto:user1 at example.com",
-                (('N', "2008-06-02 12:00:00+00:00", "2008-06-02 13:00:00+00:00", 'B', 'T'),),
-            ),
-            (
-                "#1.3 Simple component - canceled",
-                "1.3",
-                """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890-1.3
-DTSTART:20080603T120000Z
-DTEND:20080603T130000Z
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-STATUS:CANCELLED
-END:VEVENT
-END:VCALENDAR
-""",
-                "20080603T000000Z", "20080604T000000Z",
-                "mailto:user1 at example.com",
-                (('N', "2008-06-03 12:00:00+00:00", "2008-06-03 13:00:00+00:00", 'F', 'F'),),
-            ),
-            (
-                "#1.4 Simple component - tentative",
-                "1.4",
-                """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890-1.4
-DTSTART:20080604T120000Z
-DTEND:20080604T130000Z
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-STATUS:TENTATIVE
-END:VEVENT
-END:VCALENDAR
-""",
-                "20080604T000000Z", "20080605T000000Z",
-                "mailto:user1 at example.com",
-                (('N', "2008-06-04 12:00:00+00:00", "2008-06-04 13:00:00+00:00", 'T', 'F'),),
-            ),
-            (
-                "#2.1 Recurring component - busy",
-                "2.1",
-                """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890-2.1
-DTSTART:20080605T120000Z
-DTEND:20080605T130000Z
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-RRULE:FREQ=DAILY;COUNT=2
-END:VEVENT
-END:VCALENDAR
-""",
-                "20080605T000000Z", "20080607T000000Z",
-                "mailto:user1 at example.com",
-                (
-                    ('N', "2008-06-05 12:00:00+00:00", "2008-06-05 13:00:00+00:00", 'B', 'F'),
-                    ('N', "2008-06-06 12:00:00+00:00", "2008-06-06 13:00:00+00:00", 'B', 'F'),
-                ),
-            ),
-            (
-                "#2.2 Recurring component - busy",
-                "2.2",
-                """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890-2.2
-DTSTART:20080607T120000Z
-DTEND:20080607T130000Z
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-RRULE:FREQ=DAILY;COUNT=2
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890-2.2
-RECURRENCE-ID:20080608T120000Z
-DTSTART:20080608T140000Z
-DTEND:20080608T150000Z
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-TRANSP:TRANSPARENT
-END:VEVENT
-END:VCALENDAR
-""",
-                "20080607T000000Z", "20080609T000000Z",
-                "mailto:user1 at example.com",
-                (
-                    ('N', "2008-06-07 12:00:00+00:00", "2008-06-07 13:00:00+00:00", 'B', 'F'),
-                    ('N', "2008-06-08 14:00:00+00:00", "2008-06-08 15:00:00+00:00", 'B', 'T'),
-                ),
-            ),
-        )
-
-        for description, name, calendar_txt, trstart, trend, organizer, instances in data:
-            calendar = Component.fromString(calendar_txt)
-
-            f = open(os.path.join(self.indexDirPath.path, name), "w")
-            f.write(calendar_txt)
-            del f
-
-            self.db.addResource(name, calendar)
-            self.assertTrue(self.db.resourceExists(name), msg=description)
-
-            # Create fake filter element to match time-range
-            filter =  caldavxml.Filter(
-                  caldavxml.ComponentFilter(
-                      caldavxml.ComponentFilter(
-                          TimeRange(
-                              start=trstart,
-                              end=trend,
-                          ),
-                          name=("VEVENT", "VFREEBUSY", "VAVAILABILITY"),
-                      ),
-                      name="VCALENDAR",
-                   )
-              )
-            filter = calendarqueryfilter.Filter(filter)
-
-            resources = self.db.indexedSearch(filter, fbtype=True)
-            index_results = set()
-            for _ignore_name, _ignore_uid, type, test_organizer, float, start, end, fbtype, transp in resources:
-                self.assertEqual(test_organizer, organizer, msg=description)
-                index_results.add((float, start, end, fbtype, transp,))
-
-            self.assertEqual(set(instances), index_results, msg=description)
-
-    def test_index_timespan_per_user(self):
-        data = (
-            (
-                "#1.1 Single per-user non-recurring component",
-                "1.1",
-                """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890-1.1
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890-1.1
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-TRANSP:TRANSPARENT
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""",
-                "20080601T000000Z", "20080602T000000Z",
-                "mailto:user1 at example.com",
-                (
-                    (
-                        "user01",
-                        (('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'T'),),
-                    ),
-                    (
-                        "user02",
-                        (('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'F'),),
-                    ),
-                ),
-            ),
-            (
-                "#1.2 Two per-user non-recurring component",
-                "1.2",
-                """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890-1.2
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890-1.2
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-TRANSP:TRANSPARENT
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890-1.2
-X-CALENDARSERVER-PERUSER-UID:user02
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""",
-                "20080601T000000Z", "20080602T000000Z",
-                "mailto:user1 at example.com",
-                (
-                    (
-                        "user01",
-                        (('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'T'),),
-                    ),
-                    (
-                        "user02",
-                        (('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'F'),),
-                    ),
-                    (
-                        "user03",
-                        (('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'F'),),
-                    ),
-                ),
-            ),
-            (
-                "#2.1 Single per-user simple recurring component",
-                "2.1",
-                """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890-1.1
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-RRULE:FREQ=DAILY;COUNT=10
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890-1.1
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-TRANSP:TRANSPARENT
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""",
-                "20080601T000000Z", "20080603T000000Z",
-                "mailto:user1 at example.com",
-                (
-                    (
-                        "user01",
-                        (
-                            ('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'T'),
-                            ('N', "2008-06-02 12:00:00+00:00", "2008-06-02 13:00:00+00:00", 'B', 'T'),
-                        ),
-                    ),
-                    (
-                        "user02",
-                        (
-                            ('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'F'),
-                            ('N', "2008-06-02 12:00:00+00:00", "2008-06-02 13:00:00+00:00", 'B', 'F'),
-                        ),
-                    ),
-                ),
-            ),
-            (
-                "#2.2 Two per-user simple recurring component",
-                "2.2",
-                """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890-1.2
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-RRULE:FREQ=DAILY;COUNT=10
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890-1.2
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-TRANSP:TRANSPARENT
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890-1.2
-X-CALENDARSERVER-PERUSER-UID:user02
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""",
-                "20080601T000000Z", "20080603T000000Z",
-                "mailto:user1 at example.com",
-                (
-                    (
-                        "user01",
-                        (
-                            ('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'T'),
-                            ('N', "2008-06-02 12:00:00+00:00", "2008-06-02 13:00:00+00:00", 'B', 'T'),
-                        ),
-                    ),
-                    (
-                        "user02",
-                        (
-                            ('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'F'),
-                            ('N', "2008-06-02 12:00:00+00:00", "2008-06-02 13:00:00+00:00", 'B', 'F'),
-                        ),
-                    ),
-                    (
-                        "user03",
-                        (
-                            ('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'F'),
-                            ('N', "2008-06-02 12:00:00+00:00", "2008-06-02 13:00:00+00:00", 'B', 'F'),
-                        ),
-                    ),
-                ),
-            ),
-            (
-                "#3.1 Single per-user complex recurring component",
-                "3.1",
-                """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890-1.1
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-RRULE:FREQ=DAILY;COUNT=10
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890-1.1
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890-1.1
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-TRANSP:TRANSPARENT
-END:X-CALENDARSERVER-PERINSTANCE
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-RECURRENCE-ID:20080602T120000Z
-TRANSP:OPAQUE
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""",
-                "20080601T000000Z", "20080604T000000Z",
-                "mailto:user1 at example.com",
-                (
-                    (
-                        "user01",
-                        (
-                            ('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'T'),
-                            ('N', "2008-06-02 13:00:00+00:00", "2008-06-02 14:00:00+00:00", 'B', 'F'),
-                            ('N', "2008-06-03 12:00:00+00:00", "2008-06-03 13:00:00+00:00", 'B', 'T'),
-                        ),
-                    ),
-                    (
-                        "user02",
-                        (
-                            ('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'F'),
-                            ('N', "2008-06-02 13:00:00+00:00", "2008-06-02 14:00:00+00:00", 'B', 'F'),
-                            ('N', "2008-06-03 12:00:00+00:00", "2008-06-03 13:00:00+00:00", 'B', 'F'),
-                        ),
-                    ),
-                ),
-            ),
-            (
-                "#3.2 Two per-user complex recurring component",
-                "3.2",
-                """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890-1.2
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-RRULE:FREQ=DAILY;COUNT=10
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890-1.2
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T130000Z
-DTEND:20080602T140000Z
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890-1.2
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-TRANSP:TRANSPARENT
-END:X-CALENDARSERVER-PERINSTANCE
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-RECURRENCE-ID:20080602T120000Z
-TRANSP:OPAQUE
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890-1.2
-X-CALENDARSERVER-PERUSER-UID:user02
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:X-CALENDARSERVER-PERINSTANCE
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-RECURRENCE-ID:20080603T120000Z
-TRANSP:TRANSPARENT
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
-END:VCALENDAR
-""",
-                "20080601T000000Z", "20080604T000000Z",
-                "mailto:user1 at example.com",
-                (
-                    (
-                        "user01",
-                        (
-                            ('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'T'),
-                            ('N', "2008-06-02 13:00:00+00:00", "2008-06-02 14:00:00+00:00", 'B', 'F'),
-                            ('N', "2008-06-03 12:00:00+00:00", "2008-06-03 13:00:00+00:00", 'B', 'T'),
-                        ),
-                    ),
-                    (
-                        "user02",
-                        (
-                            ('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'F'),
-                            ('N', "2008-06-02 13:00:00+00:00", "2008-06-02 14:00:00+00:00", 'B', 'F'),
-                            ('N', "2008-06-03 12:00:00+00:00", "2008-06-03 13:00:00+00:00", 'B', 'T'),
-                        ),
-                    ),
-                    (
-                        "user03",
-                        (
-                            ('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'F'),
-                            ('N', "2008-06-02 13:00:00+00:00", "2008-06-02 14:00:00+00:00", 'B', 'F'),
-                            ('N', "2008-06-03 12:00:00+00:00", "2008-06-03 13:00:00+00:00", 'B', 'F'),
-                        ),
-                    ),
-                ),
-            ),
-        )
-
-        for description, name, calendar_txt, trstart, trend, organizer, peruserinstances in data:
-            calendar = Component.fromString(calendar_txt)
-
-            f = open(os.path.join(self.indexDirPath.path, name), "w")
-            f.write(calendar_txt)
-            del f
-
-            self.db.addResource(name, calendar)
-            self.assertTrue(self.db.resourceExists(name), msg=description)
-
-            # Create fake filter element to match time-range
-            filter =  caldavxml.Filter(
-                  caldavxml.ComponentFilter(
-                      caldavxml.ComponentFilter(
-                          TimeRange(
-                              start=trstart,
-                              end=trend,
-                          ),
-                          name=("VEVENT", "VFREEBUSY", "VAVAILABILITY"),
-                      ),
-                      name="VCALENDAR",
-                   )
-              )
-            filter = calendarqueryfilter.Filter(filter)
-
-            for useruid, instances in peruserinstances:
-                resources = self.db.indexedSearch(filter, useruid=useruid, fbtype=True)
-                index_results = set()
-                for _ignore_name, _ignore_uid, type, test_organizer, float, start, end, fbtype, transp in resources:
-                    self.assertEqual(test_organizer, organizer, msg=description)
-                    index_results.add((str(float), str(start), str(end), str(fbtype), str(transp),))
-    
-                self.assertEqual(set(instances), index_results, msg="%s, user:%s" % (description, useruid,))
-
-            self.db.deleteResource(name)
-
-    def test_index_revisions(self):
-        data1 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890-1.1
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-END:VEVENT
-END:VCALENDAR
-"""
-        data2 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890-2.1
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-RRULE:FREQ=WEEKLY;COUNT=2
-END:VEVENT
-END:VCALENDAR
-"""
-        data3 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890-2.3
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-RRULE:FREQ=WEEKLY;COUNT=2
-END:VEVENT
-END:VCALENDAR
-"""
-
-        calendar = Component.fromString(data1)
-        self.db.addResource("data1.ics", calendar)
-        calendar = Component.fromString(data2)
-        self.db.addResource("data2.ics", calendar)
-        calendar = Component.fromString(data3)
-        self.db.addResource("data3.ics", calendar)
-        self.db.deleteResource("data3.ics")
-
-        tests = (
-            (0, (["data1.ics", "data2.ics",], [],)),
-            (1, (["data2.ics",], ["data3.ics",],)),
-            (2, ([], ["data3.ics",],)),
-            (3, ([], ["data3.ics",],)),
-            (4, ([], [],)),
-            (5, ([], [],)),
-        )
-        
-        for revision, results in tests:
-            for depth in ("1", "infinity"):
-                self.assertEquals(self.db.whatchanged(revision, depth), results, "Mismatched results for whatchanged with revision %d" % (revision,))
-
-class MemcacheTests(SQLIndexTests):
-    def setUp(self):
-        super(MemcacheTests, self).setUp()
-        self.memcache = InMemoryMemcacheProtocol()
-        self.db.reserver = MemcachedUIDReserver(self.db, self.memcache)
-
-
-    def tearDown(self):
-        for _ignore_k, v in self.memcache._timeouts.iteritems():
-            if v.active():
-                v.cancel()

Modified: CalendarServer/trunk/twistedcaldav/test/test_sharing.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_sharing.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/test/test_sharing.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -25,8 +25,7 @@
 from twistedcaldav.config import config
 from twistedcaldav.test.util import HomeTestCase, norequest
 from twistedcaldav.resource import CalDAVResource
-from txdav.common.datastore.test.util import buildStore
-from txdav.caldav.datastore.test.common import StubNotifierFactory
+from txdav.common.datastore.test.util import buildStore, StubNotifierFactory
 
 sharedOwnerType = davxml.ResourceType.sharedownercalendar #@UndefinedVariable
 regularCalendarType = davxml.ResourceType.calendar #@UndefinedVariable

Modified: CalendarServer/trunk/twistedcaldav/test/test_sql.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_sql.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/test/test_sql.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -17,7 +17,7 @@
 from twistedcaldav.sql import AbstractSQLDatabase
 
 import twistedcaldav.test.util
-from twistedcaldav.sql import db_prefix
+
 from threading import Thread
 import time
 import os

Deleted: CalendarServer/trunk/twistedcaldav/test/test_vcardindex.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_vcardindex.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/test/test_vcardindex.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -1,210 +0,0 @@
-##
-# Copyright (c) 2010 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-
-from twisted.internet import reactor
-from twisted.internet.task import deferLater
-
-from twistedcaldav.test.util import InMemoryMemcacheProtocol
-from twistedcaldav.vcard import Component
-from twistedcaldav.vcardindex import AddressBookIndex, MemcachedUIDReserver, ReservationError
-import twistedcaldav.test.util
-
-import os
-
-class MinimalResourceReplacement(object):
-    """
-    Provide the minimal set of attributes and methods from CalDAVFile required
-    by L{Index}.
-    """
-
-    def __init__(self, filePath):
-        self.fp = filePath
-
-
-    def isAddressBookCollection(self):
-        return True
-
-
-    def getChild(self, name):
-        # FIXME: this should really return something with a child method
-        return self.fp.child(name)
-
-
-    def initSyncToken(self):
-        pass
-
-
-
-class SQLIndexTests (twistedcaldav.test.util.TestCase):
-    """
-    Test abstract SQL DB class
-    """
-
-    def setUp(self):
-        super(SQLIndexTests, self).setUp()
-        self.site.resource.isAddressBookCollection = lambda: True
-        self.indexDirPath = self.site.resource.fp
-        # FIXME: since this resource lies about isCalendarCollection, it doesn't
-        # have all the associated backend machinery to actually get children.
-        self.db = AddressBookIndex(MinimalResourceReplacement(self.indexDirPath))
-
-
-    def test_reserve_uid_ok(self):
-        uid = "test-test-test"
-        d = self.db.isReservedUID(uid)
-        d.addCallback(self.assertFalse)
-        d.addCallback(lambda _: self.db.reserveUID(uid))
-        d.addCallback(lambda _: self.db.isReservedUID(uid))
-        d.addCallback(self.assertTrue)
-        d.addCallback(lambda _: self.db.unreserveUID(uid))
-        d.addCallback(lambda _: self.db.isReservedUID(uid))
-        d.addCallback(self.assertFalse)
-
-        return d
-
-
-    def test_reserve_uid_twice(self):
-        uid = "test-test-test"
-        d = self.db.reserveUID(uid)
-        d.addCallback(lambda _: self.db.isReservedUID(uid))
-        d.addCallback(self.assertTrue)
-        d.addCallback(lambda _:
-                      self.assertFailure(self.db.reserveUID(uid),
-                                         ReservationError))
-        return d
-
-
-    def test_unreserve_unreserved(self):
-        uid = "test-test-test"
-        return self.assertFailure(self.db.unreserveUID(uid),
-                                  ReservationError)
-
-
-    def test_reserve_uid_timeout(self):
-        # WARNING: This test is fundamentally flawed and will fail
-        # intermittently because it uses the real clock.
-        uid = "test-test-test"
-        from twistedcaldav.config import config
-        old_timeout = config.UIDReservationTimeOut
-        config.UIDReservationTimeOut = 1
-
-        def _finally():
-            config.UIDReservationTimeOut = old_timeout
-
-        d = self.db.isReservedUID(uid)
-        d.addCallback(self.assertFalse)
-        d.addCallback(lambda _: self.db.reserveUID(uid))
-        d.addCallback(lambda _: self.db.isReservedUID(uid))
-        d.addCallback(self.assertTrue)
-        d.addCallback(lambda _: deferLater(reactor, 2, lambda: None))
-        d.addCallback(lambda _: self.db.isReservedUID(uid))
-        d.addCallback(self.assertFalse)
-        self.addCleanup(_finally)
-
-        return d
-
-
-    def test_index(self):
-        data = (
-            (
-                "#1.1 Simple component",
-                "1.1",
-                """BEGIN:VCARD
-VERSION:3.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-UID:12345-67890-1.1
-FN:Cyrus Daboo
-N:Daboo;Cyrus
-EMAIL;TYPE=INTERNET,PREF:cyrus at example.com
-END:VCARD
-""",
-            ),
-        )
-
-        for description, name, vcard_txt in data:
-            calendar = Component.fromString(vcard_txt)
-            f = open(os.path.join(self.site.resource.fp.path, name), "w")
-            f.write(vcard_txt)
-            del f
-
-            self.db.addResource(name, calendar)
-            self.assertTrue(self.db.resourceExists(name), msg=description)
-
-        self.db._db_recreate()
-        for description, name, vcard_txt in data:
-            self.assertTrue(self.db.resourceExists(name), msg=description)
-
-    def test_index_revisions(self):
-        data1 = """BEGIN:VCARD
-VERSION:3.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-UID:12345-67890-1-1.1
-FN:Cyrus Daboo
-N:Daboo;Cyrus
-EMAIL;TYPE=INTERNET,PREF:cyrus at example.com
-END:VCARD
-"""
-        data2 = """BEGIN:VCARD
-VERSION:3.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-UID:12345-67890-2-1.1
-FN:Wilfredo Sanchez
-N:Sanchez;Wilfredo
-EMAIL;TYPE=INTERNET,PREF:wsanchez at example.com
-END:VCARD
-"""
-        data3 = """BEGIN:VCARD
-VERSION:3.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-UID:12345-67890-3-1.1
-FN:Bruce Gaya
-N:Gaya;Bruce
-EMAIL;TYPE=INTERNET,PREF:bruce at example.com
-END:VCARD
-"""
-
-        vcard = Component.fromString(data1)
-        self.db.addResource("data1.vcf", vcard)
-        vcard = Component.fromString(data2)
-        self.db.addResource("data2.vcf", vcard)
-        vcard = Component.fromString(data3)
-        self.db.addResource("data3.vcf", vcard)
-        self.db.deleteResource("data3.vcf")
-
-        tests = (
-            (0, (["data1.vcf", "data2.vcf",], [],)),
-            (1, (["data2.vcf",], ["data3.vcf",],)),
-            (2, ([], ["data3.vcf",],)),
-            (3, ([], ["data3.vcf",],)),
-            (4, ([], [],)),
-            (5, ([], [],)),
-        )
-        
-        for revision, results in tests:
-            for depth in ("1", "infinity"):
-                self.assertEquals(self.db.whatchanged(revision, depth), results, "Mismatched results for whatchanged with revision %d" % (revision,))
-
-class MemcacheTests(SQLIndexTests):
-    def setUp(self):
-        super(MemcacheTests, self).setUp()
-        self.memcache = InMemoryMemcacheProtocol()
-        self.db.reserver = MemcachedUIDReserver(self.db, self.memcache)
-
-
-    def tearDown(self):
-        for _ignore_k, v in self.memcache._timeouts.iteritems():
-            if v.active():
-                v.cancel()

Modified: CalendarServer/trunk/twistedcaldav/test/test_wrapping.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_wrapping.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/test/test_wrapping.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -41,15 +41,18 @@
 
 from txdav.carddav.datastore.test.test_file import vcard4_text
 
-from txdav.common.datastore.test.util import buildStore
-from txdav.caldav.datastore.test.common import StubNotifierFactory, \
-    assertProvides
+from txdav.common.datastore.test.util import buildStore, assertProvides,\
+    StubNotifierFactory
+
+
 from txdav.caldav.icalendarstore import ICalendarHome
 from txdav.carddav.iaddressbookstore import IAddressBookHome
 
 
 
 class FakeChanRequest(object):
+    code = 'request-not-finished'
+
     def writeHeaders(self, code, headers):
         self.code = code
         self.headers = headers
@@ -89,6 +92,7 @@
         self.setupCalendars()
 
 
+    @inlineCallbacks
     def populateOneObject(self, objectName, objectText):
         """
         Populate one calendar object in the test user's calendar.
@@ -107,12 +111,13 @@
         except:
             pass
         txn = self.calendarCollection._newStore.newTransaction()
-        home = txn.calendarHomeWithUID(uid, True)
-        cal = home.calendarWithName("calendar")
+        home = yield txn.calendarHomeWithUID(uid, True)
+        cal = yield home.calendarWithName("calendar")
         cal.createCalendarObjectWithName(objectName, VComponent.fromString(objectText))
-        txn.commit()
+        yield txn.commit()
 
 
+    @inlineCallbacks
     def populateOneAddressBookObject(self, objectName, objectText):
         """
         Populate one addressbook object in the test user's addressbook.
@@ -131,13 +136,13 @@
         except:
             pass
         txn = self.addressbookCollection._newStore.newTransaction()
-        home = txn.addressbookHomeWithUID(uid, True)
-        adbk = home.addressbookWithName("addressbook")
+        home = yield txn.addressbookHomeWithUID(uid, True)
+        adbk = yield home.addressbookWithName("addressbook")
         if adbk is None:
-            home.createAddressBookWithName("addressbook")
-            adbk = home.addressbookWithName("addressbook")
+            yield home.createAddressBookWithName("addressbook")
+            adbk = yield home.addressbookWithName("addressbook")
         adbk.createAddressBookObjectWithName(objectName, VCComponent.fromString(objectText))
-        txn.commit()
+        yield txn.commit()
 
 
     requestUnderTest = None
@@ -169,7 +174,7 @@
         an associated transaction.  Commit that transaction to bring the
         filesystem into a consistent state.
         """
-        self.requestUnderTest._newStoreTransaction.commit()
+        return self.requestUnderTest._newStoreTransaction.commit()
 
 
     def requestForPath(self, path):
@@ -241,7 +246,7 @@
         L{CalendarHome} via C{newTransaction().calendarHomeWithUID}.
         """
         calDavFile = yield self.getResource("calendars/users/wsanchez/")
-        self.commit()
+        yield self.commit()
         assertProvides(self, ICalendarHome, calDavFile._newStoreHome)
 
 
@@ -256,7 +261,7 @@
         dropBoxResource = yield self.getResource(
             "calendars/users/wsanchez/dropbox"
         )
-        self.commit()
+        yield self.commit()
         self.assertIsInstance(dropBoxResource, DropboxCollection)
         dropboxHomeType = davxml.ResourceType.dropboxhome #@UndefinedVariable
         self.assertEquals(dropBoxResource.resourceType(),
@@ -275,7 +280,7 @@
         regularCalendarType = davxml.ResourceType.calendar #@UndefinedVariable
         self.assertEquals(calDavFile.resourceType(),
                           regularCalendarType)
-        self.commit()
+        yield self.commit()
 
 
     @inlineCallbacks
@@ -290,7 +295,7 @@
         calDavFile = yield self.getResource("calendars/users/wsanchez/frobozz")
         self.assertIsInstance(calDavFile, ProtoCalendarCollectionResource)
         calDavFile.createCalendarCollection()
-        self.commit()
+        yield self.commit()
 
 
     @inlineCallbacks
@@ -308,7 +313,7 @@
             self.assertIdentical(
                 getattr(calDavFile, "_newStoreCalendar", None), None
             )
-        self.commit()
+        yield self.commit()
 
 
     @inlineCallbacks
@@ -336,17 +341,21 @@
     @inlineCallbacks
     def test_lookupCalendarObject(self):
         """
-        When a L{CalDAVResource} representing an existing calendar object is looked
-        up on a L{CalDAVResource} representing a calendar collection, a parallel
-        L{CalendarObject} will be created (with a matching FilePath).
+        When a L{CalDAVResource} representing an existing calendar object is
+        looked up on a L{CalDAVResource} representing a calendar collection, a
+        parallel L{CalendarObject} will be created.  Its principal collections
+        and transaction should match.
         """
-        self.populateOneObject("1.ics", event4_text)
+        yield self.populateOneObject("1.ics", event4_text)
+        calendarHome = yield self.getResource("calendars/users/wsanchez")
         calDavFileCalendar = yield self.getResource(
             "calendars/users/wsanchez/calendar/1.ics"
         )
-        self.commit()
+        yield self.commit()
         self.assertEquals(calDavFileCalendar._principalCollections,
                           frozenset([self.principalsResource]))
+        self.assertEquals(calDavFileCalendar._associatedTransaction,
+                          calendarHome._associatedTransaction)
 
 
     @inlineCallbacks
@@ -359,7 +368,7 @@
         calDavFileCalendar = yield self.getResource(
             "calendars/users/wsanchez/calendar/xyzzy.ics"
         )
-        self.commit()
+        yield self.commit()
         self.assertEquals(calDavFileCalendar._principalCollections,
                           frozenset([self.principalsResource]))
 
@@ -380,7 +389,7 @@
         via C{newTransaction().addressbookHomeWithUID}.
         """
         calDavFile = yield self.getResource("addressbooks/users/wsanchez/")
-        self.commit()
+        yield self.commit()
         assertProvides(self, IAddressBookHome, calDavFile._newStoreHome)
 
 
@@ -392,7 +401,7 @@
         create a corresponding L{AddressBook} via C{AddressBookHome.addressbookWithName}.
         """
         calDavFile = yield self.getResource("addressbooks/users/wsanchez/addressbook")
-        self.commit()
+        yield self.commit()
         self.assertEquals(calDavFile._principalCollections,
                           frozenset([self.principalsResource]))
 
@@ -409,7 +418,7 @@
         calDavFile = yield self.getResource("addressbooks/users/wsanchez/frobozz")
         self.assertIsInstance(calDavFile, ProtoAddressBookCollectionResource)
         calDavFile.createAddressBookCollection()
-        self.commit()
+        yield self.commit()
         self.assertEquals(calDavFile._principalCollections,
                           frozenset([self.principalsResource]))
 
@@ -421,11 +430,11 @@
         up on a L{CalDAVResource} representing a addressbook collection, a parallel
         L{AddressBookObject} will be created (with a matching FilePath).
         """
-        self.populateOneAddressBookObject("1.vcf", vcard4_text)
+        yield self.populateOneAddressBookObject("1.vcf", vcard4_text)
         calDavFileAddressBook = yield self.getResource(
             "addressbooks/users/wsanchez/addressbook/1.vcf"
         )
-        self.commit()
+        yield self.commit()
         self.assertEquals(calDavFileAddressBook._principalCollections,
                           frozenset([self.principalsResource]))
 
@@ -440,7 +449,7 @@
         calDavFileAddressBook = yield self.getResource(
             "addressbooks/users/wsanchez/addressbook/xyzzy.ics"
         )
-        self.commit()
+        yield self.commit()
         self.assertEquals(calDavFileAddressBook._principalCollections,
                           frozenset([self.principalsResource]))
 

Modified: CalendarServer/trunk/twistedcaldav/test/util.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/util.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/test/util.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -352,7 +352,7 @@
         """
         if not self.committed:
             self.committed = True
-            self.site.resource._associatedTransaction.commit()
+            return self.site.resource._associatedTransaction.commit()
 
 
     @inlineCallbacks
@@ -361,10 +361,10 @@
         Refresh the user resource positioned at the root of this site, to give
         it a new transaction.
         """
-        self.noRenderCommit()
+        yield self.noRenderCommit()
         if request is None:
             request = norequest()
-        users = self.homeProvisioner.getChild("users")
+        users = yield self.homeProvisioner.getChild("users")
 
         user, ignored = (yield users.locateChild(request, ["wsanchez"]))
 
@@ -387,7 +387,7 @@
         Override C{send} in order to refresh the 'user' resource each time, to
         get a new transaction to associate with the calendar home.
         """
-        self.noRenderCommit()
+        yield self.noRenderCommit()
         yield self._refreshRoot(request)
         result = (yield super(HomeTestCase, self).send(request))
         self.committed = True
@@ -424,12 +424,13 @@
             self.directoryService, "/addressbooks/",
             _newStore
         )
-        
+
+        @inlineCallbacks
         def _defer(user):
             # Commit the transaction
-            self.site.resource._associatedTransaction.commit()
+            yield self.site.resource._associatedTransaction.commit()
             self.docroot = user._newStoreHome._path.path
-            
+
         return self._refreshRoot().addCallback(_defer)
 
     @inlineCallbacks

Modified: CalendarServer/trunk/twistedcaldav/upgrade.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/upgrade.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/upgrade.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -35,7 +35,7 @@
 from twistedcaldav.ical import Component
 from twistedcaldav import caldavxml
 
-from twisted.internet.defer import inlineCallbacks, succeed, returnValue
+from twisted.internet.defer import inlineCallbacks, succeed
 
 from calendarserver.tools.util import getDirectory
 from calendarserver.tools.resources import migrateResources

Deleted: CalendarServer/trunk/twistedcaldav/vcardindex.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/vcardindex.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/twistedcaldav/vcardindex.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -1,701 +0,0 @@
-##
-# Copyright (c) 2005-2009 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-
-"""
-CardDAV Index.
-
-This API is considered private to static.py and is therefore subject to
-change.
-"""
-
-__all__ = [
-    "AddressBookIndex",
-]
-
-import datetime
-import os
-import time
-import hashlib
-
-try:
-    import sqlite3 as sqlite
-except ImportError:
-    from pysqlite2 import dbapi2 as sqlite
-
-from twisted.internet.defer import maybeDeferred
-
-from twistedcaldav import carddavxml
-from twistedcaldav.index import SyncTokenValidException
-from twistedcaldav.query import addressbookquery
-from twistedcaldav.sql import AbstractSQLDatabase
-from twistedcaldav.sql import db_prefix
-from twistedcaldav.vcard import Component
-
-from twext.python.log import Logger, LoggingMixIn
-from twistedcaldav.config import config
-from twistedcaldav.memcachepool import CachePoolUserMixIn
-
-log = Logger()
-
-db_basename = db_prefix + "sqlite"
-schema_version = "2"
-
-class ReservationError(LookupError):
-    """
-    Attempt to reserve a UID which is already reserved or to unreverse a UID
-    which is not reserved.
-    """
-
-def wrapInDeferred(f):
-    def _(*args, **kwargs):
-        return maybeDeferred(f, *args, **kwargs)
-
-    return _
-
-
-class MemcachedUIDReserver(CachePoolUserMixIn, LoggingMixIn):
-    def __init__(self, index, cachePool=None):
-        self.index = index
-        self._cachePool = cachePool
-
-    def _key(self, uid):
-        return 'reservation:%s' % (
-            hashlib.md5('%s:%s' % (uid,
-                                   self.index.resource.fp.path)).hexdigest())
-
-    def reserveUID(self, uid):
-        uid = uid.encode('utf-8')
-        self.log_debug("Reserving UID %r @ %r" % (
-                uid,
-                self.index.resource.fp.path))
-
-        def _handleFalse(result):
-            if result is False:
-                raise ReservationError(
-                    "UID %s already reserved for address book collection %s."
-                    % (uid, self.index.resource)
-                    )
-
-        d = self.getCachePool().add(self._key(uid),
-                                    'reserved',
-                                    expireTime=config.UIDReservationTimeOut)
-        d.addCallback(_handleFalse)
-        return d
-
-
-    def unreserveUID(self, uid):
-        uid = uid.encode('utf-8')
-        self.log_debug("Unreserving UID %r @ %r" % (
-                uid,
-                self.index.resource.fp.path))
-
-        def _handleFalse(result):
-            if result is False:
-                raise ReservationError(
-                    "UID %s is not reserved for address book collection %s."
-                    % (uid, self.index.resource)
-                    )
-
-        d =self.getCachePool().delete(self._key(uid))
-        d.addCallback(_handleFalse)
-        return d
-
-
-    def isReservedUID(self, uid):
-        uid = uid.encode('utf-8')
-        self.log_debug("Is reserved UID %r @ %r" % (
-                uid,
-                self.index.resource.fp.path))
-
-        def _checkValue((flags, value)):
-            if value is None:
-                return False
-            else:
-                return True
-
-        d = self.getCachePool().get(self._key(uid))
-        d.addCallback(_checkValue)
-        return d
-
-
-
-class SQLUIDReserver(object):
-    def __init__(self, index):
-        self.index = index
-
-    @wrapInDeferred
-    def reserveUID(self, uid):
-        """
-        Reserve a UID for this index's resource.
-        @param uid: the UID to reserve
-        @raise ReservationError: if C{uid} is already reserved
-        """
-
-        try:
-            self.index._db_execute("insert into RESERVED (UID, TIME) values (:1, :2)", uid, datetime.datetime.now())
-            self.index._db_commit()
-        except sqlite.IntegrityError:
-            self.index._db_rollback()
-            raise ReservationError(
-                "UID %s already reserved for address book collection %s."
-                % (uid, self.index.resource)
-            )
-        except sqlite.Error, e:
-            log.err("Unable to reserve UID: %s", (e,))
-            self.index._db_rollback()
-            raise
-
-    def unreserveUID(self, uid):
-        """
-        Unreserve a UID for this index's resource.
-        @param uid: the UID to reserve
-        @raise ReservationError: if C{uid} is not reserved
-        """
-
-        def _cb(result):
-            if result == False:
-                raise ReservationError(
-                    "UID %s is not reserved for address book collection %s."
-                    % (uid, self.index.resource)
-                    )
-            else:
-                try:
-                    self.index._db_execute(
-                        "delete from RESERVED where UID = :1", uid)
-                    self.index._db_commit()
-                except sqlite.Error, e:
-                    log.err("Unable to unreserve UID: %s", (e,))
-                    self.index._db_rollback()
-                    raise
-
-        d = self.isReservedUID(uid)
-        d.addCallback(_cb)
-        return d
-
-
-    @wrapInDeferred
-    def isReservedUID(self, uid):
-        """
-        Check to see whether a UID is reserved.
-        @param uid: the UID to check
-        @return: True if C{uid} is reserved, False otherwise.
-        """
-
-        rowiter = self.index._db_execute("select UID, TIME from RESERVED where UID = :1", uid)
-        for uid, attime in rowiter:
-            # Double check that the time is within a reasonable period of now
-            # otherwise we probably have a stale reservation
-            tm = time.strptime(attime[:19], "%Y-%m-%d %H:%M:%S")
-            dt = datetime.datetime(year=tm.tm_year, month=tm.tm_mon, day=tm.tm_mday, hour=tm.tm_hour, minute=tm.tm_min, second = tm.tm_sec)
-            if datetime.datetime.now() - dt > datetime.timedelta(seconds=config.UIDReservationTimeOut):
-                try:
-                    self.index._db_execute("delete from RESERVED where UID = :1", uid)
-                    self.index._db_commit()
-                except sqlite.Error, e:
-                    log.err("Unable to unreserve UID: %s", (e,))
-                    self.index._db_rollback()
-                    raise
-                return False
-            else:
-                return True
-
-        return False
-
-class AddressBookIndex(AbstractSQLDatabase):
-    """
-    AddressBook collection index abstract base class that defines the apis for the index.
-    """
-
-    def __init__(self, resource):
-        """
-        @param resource: the L{CalDAVResource} resource to
-            index. C{resource} must be an addressbook collection (ie.
-            C{resource.isAddressBookCollection()} returns C{True}.)
-        """
-        assert resource.isAddressBookCollection(), "non-addressbook collection resource %s has no index." % (resource,)
-        self.resource = resource
-        db_filename = os.path.join(self.resource.fp.path, db_basename)
-        super(AddressBookIndex, self).__init__(db_filename, False)
-
-        if (
-            hasattr(config, "Memcached") and
-            config.Memcached.Pools.Default.ClientEnabled
-        ):
-            self.reserver = MemcachedUIDReserver(self)
-        else:
-            self.reserver = SQLUIDReserver(self)
-
-    def create(self):
-        """
-        Create the index and initialize it.
-        """
-        self._db()
-
-    def recreate(self):
-        """
-        Delete the database and re-create it
-        """
-        try:
-            os.remove(self.dbpath)
-        except OSError:
-            pass
-        self.create()
-
-    #
-    # A dict of sets. The dict keys are address book collection paths,
-    # and the sets contains reserved UIDs for each path.
-    #
-    
-    def reserveUID(self, uid):
-        return self.reserver.reserveUID(uid)
-    
-    def unreserveUID(self, uid):
-        return self.reserver.unreserveUID(uid)
-    
-    def isReservedUID(self, uid):
-        return self.reserver.isReservedUID(uid)
-        
-    def isAllowedUID(self, uid, *names):
-        """
-        Checks to see whether to allow an operation which would add the
-        specified UID to the index.  Specifically, the operation may not
-        violate the constraint that UIDs must be unique.
-        @param uid: the UID to check
-        @param names: the names of resources being replaced or deleted by the
-            operation; UIDs associated with these resources are not checked.
-        @return: True if the UID is not in the index and is not reserved,
-            False otherwise.
-        """
-        rname = self.resourceNameForUID(uid)
-        return (rname is None or rname in names)
- 
-    def resourceNamesForUID(self, uid):
-        """
-        Looks up the names of the resources with the given UID.
-        @param uid: the UID of the resources to look up.
-        @return: a list of resource names
-        """
-        names = self._db_values_for_sql("select NAME from RESOURCE where UID = :1", uid)
-
-        #
-        # Check that each name exists as a child of self.resource.  If not, the
-        # resource record is stale.
-        #
-        resources = []
-        for name in names:
-            name_utf8 = name.encode("utf-8")
-            if name is not None and self.resource.getChild(name_utf8) is None:
-                # Clean up
-                log.err("Stale resource record found for child %s with UID %s in %s" % (name, uid, self.resource))
-                self._delete_from_db(name, uid, False)
-                self._db_commit()
-            else:
-                resources.append(name_utf8)
-
-        return resources
-
-    def resourceNameForUID(self, uid):
-        """
-        Looks up the name of the resource with the given UID.
-        @param uid: the UID of the resource to look up.
-        @return: If the resource is found, its name; C{None} otherwise.
-        """
-        result = None
-
-        for name in self.resourceNamesForUID(uid):
-            assert result is None, "More than one resource with UID %s in address book collection %r" % (uid, self)
-            result = name
-            
-        return result
-
-    def resourceUIDForName(self, name):
-        """
-        Looks up the UID of the resource with the given name.
-        @param name: the name of the resource to look up.
-        @return: If the resource is found, the UID of the resource; C{None}
-            otherwise.
-        """
-        uid = self._db_value_for_sql("select UID from RESOURCE where NAME = :1", name)
-
-        return uid
-
-    def addResource(self, name, vcard, fast=False):
-        """
-        Adding or updating an existing resource.
-        To check for an update we attempt to get an existing UID
-        for the resource name. If present, then the index entries for
-        that UID are removed. After that the new index entries are added.
-        @param name: the name of the resource to add.
-        @param vCard: a L{Component} object representing the resource
-            contents.
-        @param fast: if C{True} do not do commit, otherwise do commit.
-        """
-        oldUID = self.resourceUIDForName(name)
-        if oldUID is not None:
-            self._delete_from_db(name, oldUID, False)
-        self._add_to_db(name, vcard)
-        if not fast:
-            self._db_commit()
-
-    def deleteResource(self, name):
-        """
-        Remove this resource from the index.
-        @param name: the name of the resource to add.
-        @param uid: the UID of the vcard component in the resource.
-        """
-        uid = self.resourceUIDForName(name)
-        if uid is not None:
-            self._delete_from_db(name, uid)
-            self._db_commit()
-    
-    def resourceExists(self, name):
-        """
-        Determines whether the specified resource name exists in the index.
-        @param name: the name of the resource to test
-        @return: True if the resource exists, False if not
-        """
-        uid = self._db_value_for_sql("select UID from RESOURCE where NAME = :1", name)
-        return uid is not None
-    
-    def resourcesExist(self, names):
-        """
-        Determines whether the specified resource name exists in the index.
-        @param names: a C{list} containing the names of the resources to test
-        @return: a C{list} of all names that exist
-        """
-        statement = "select NAME from RESOURCE where NAME in ("
-        for ctr, ignore_name in enumerate(names):
-            if ctr != 0:
-                statement += ", "
-            statement += ":%s" % (ctr,)
-        statement += ")"
-        results = self._db_values_for_sql(statement, *names)
-        return results
-    
-    def whatchanged(self, revision, depth):
-
-        results = [(name.encode("utf-8"), deleted) for name, deleted in self._db_execute("select NAME, DELETED from REVISIONS where REVISION > :1", revision)]
-        results.sort(key=lambda x:x[1])
-        
-        changed = []
-        deleted = []
-        for name, wasdeleted in results:
-            if name:
-                if wasdeleted == 'Y':
-                    if revision:
-                        deleted.append(name)
-                else:
-                    changed.append(name)
-            else:
-                raise SyncTokenValidException
-        
-        return changed, deleted,
-
-    def lastRevision(self):
-        return self._db_value_for_sql(
-            "select REVISION from REVISION_SEQUENCE"
-        )
-
-    def bumpRevision(self, fast=False):
-        self._db_execute(
-            """
-            update REVISION_SEQUENCE set REVISION = REVISION + 1
-            """,
-        )
-        if not fast:
-            self._db_commit()
-        return self._db_value_for_sql(
-            """
-            select REVISION from REVISION_SEQUENCE
-            """,
-        )
-
-    def searchValid(self, filter):
-        if isinstance(filter, carddavxml.Filter):
-            qualifiers = addressbookquery.sqladdressbookquery(filter)
-        else:
-            qualifiers = None
-            
-        return qualifiers is not None
-
-    def search(self, filter):
-        """
-        Finds resources matching the given qualifiers.
-        @param filter: the L{Filter} for the addressbook-query to execute.
-        @return: an interable iterable of tuples for each resource matching the
-            given C{qualifiers}. The tuples are C{(name, uid, type)}, where
-            C{name} is the resource name, C{uid} is the resource UID, and
-            C{type} is the resource iCalendar component type.x
-        """
-        # FIXME: Don't forget to use maximum_future_expansion_duration when we
-        # start caching...
-        
-        # Make sure we have a proper Filter element and get the partial SQL statement to use.
-        if isinstance(filter, carddavxml.Filter):
-            qualifiers = addressbookquery.sqladdressbookquery(filter)
-        else:
-            qualifiers = None
-        if qualifiers is not None:
-            rowiter = self._db_execute("select DISTINCT RESOURCE.NAME, RESOURCE.UID" + qualifiers[0], *qualifiers[1])
-        else:
-            rowiter = self._db_execute("select NAME, UID from RESOURCE")
-            
-        for row in rowiter:
-            name = row[0]
-            if self.resource.getChild(name.encode("utf-8")):
-                yield row
-            else:
-                log.err("vCard resource %s is missing from %s. Removing from index."
-                        % (name, self.resource))
-                self.deleteResource(name, None)
-
-    def bruteForceSearch(self):
-        """
-        List the whole index and tests for existence, updating the index
-        @return: all resources in the index
-        """
-        # List all resources
-        rowiter = self._db_execute("select NAME, UID from RESOURCE")
-
-        # Check result for missing resources:
-
-        for row in rowiter:
-            name = row[0]
-            if self.resource.getChild(name.encode("utf-8")):
-                yield row
-            else:
-                log.err("AddressBook resource %s is missing from %s. Removing from index."
-                        % (name, self.resource))
-                self.deleteResource(name)
-
-
-    def _db_version(self):
-        """
-        @return: the schema version assigned to this index.
-        """
-        return schema_version
-        
-    def _db_type(self):
-        """
-        @return: the collection type assigned to this index.
-        """
-        return "AddressBook"
-        
-    def _db_init_data_tables(self, q):
-        """
-        Initialise the underlying database tables.
-        @param q:           a database cursor to use.
-        """
-        
-        # Create database where the RESOURCE table has unique UID column.
-        self._db_init_data_tables_base(q, True)
-
-    def _db_init_data_tables_base(self, q, uidunique):
-        """
-        Initialise the underlying database tables.
-        @param q:           a database cursor to use.
-        """
-        #
-        # RESOURCE table is the primary index table
-        #   NAME: Last URI component (eg. <uid>.ics, RESOURCE primary key)
-        #   UID: iCalendar UID (may or may not be unique)
-        #
-        q.execute(
-            """
-            create table RESOURCE (
-                NAME           text unique,
-                UID            text unique
-            )
-            """
-        )
-
-        #
-        # REVISIONS table tracks changes
-        #   NAME: Last URI component (eg. <uid>.ics, RESOURCE primary key)
-        #   REVISION: revision number
-        #   WASDELETED: Y if revision deleted, N if added or changed
-        #
-        q.execute(
-            """
-            create table REVISION_SEQUENCE (
-                REVISION        integer
-            )
-            """
-        )
-        q.execute(
-            """
-            insert into REVISION_SEQUENCE (REVISION) values (0)
-            """
-        )
-        q.execute(
-            """
-            create table REVISIONS (
-                NAME            text unique,
-                REVISION        integer default 0,
-                DELETED         text(1) default "N"
-            )
-            """
-        )
-        q.execute(
-            """
-            create index REVISION on REVISIONS (REVISION)
-            """
-        )
-
-        #
-        # RESERVED table tracks reserved UIDs
-        #   UID: The UID being reserved
-        #   TIME: When the reservation was made
-        #
-        q.execute(
-            """
-            create table RESERVED (
-                UID  text unique,
-                TIME date
-            )
-            """
-        )
-
-    def _db_recreate(self, do_commit=True):
-        """
-        Re-create the database tables from existing address book data.
-        """
-        
-        #
-        # Populate the DB with data from already existing resources.
-        # This allows for index recovery if the DB file gets
-        # deleted.
-        #
-        fp = self.resource.fp
-        for name in fp.listdir():
-            if name.startswith("."):
-                continue
-
-            try:
-                stream = fp.child(name).open()
-            except (IOError, OSError), e:
-                log.err("Unable to open resource %s: %s" % (name, e))
-                continue
-
-            try:
-                # FIXME: This is blocking I/O
-                try:
-                    vcard = Component.fromStream(stream)
-                    vcard.validForCardDAV()
-                except ValueError:
-                    log.err("Non-addressbook resource: %s" % (name,))
-                else:
-                    #log.msg("Indexing resource: %s" % (name,))
-                    self.addResource(name, vcard, True)
-            finally:
-                stream.close()
-        
-        # Do commit outside of the loop for better performance
-        if do_commit:
-            self._db_commit()
-
-    def _db_can_upgrade(self, old_version):
-        """
-        Can we do an in-place upgrade
-        """
-        
-        # v2 is a minor change
-        return True
-
-    def _db_upgrade_data_tables(self, q, old_version):
-        """
-        Upgrade the data from an older version of the DB.
-        """
-
-        # When going to version 2+ all we need to do is add revision table and index
-        if old_version < 2:
-            q.execute(
-                """
-                create table REVISION_SEQUENCE (
-                    REVISION        integer
-                )
-                """
-            )
-            q.execute(
-                """
-                insert into REVISION_SEQUENCE (REVISION) values (0)
-                """
-            )
-            q.execute(
-                """
-                create table REVISIONS (
-                    NAME            text unique,
-                    REVISION        integer default 0,
-                    CREATEDREVISION integer default 0,
-                    WASDELETED      text(1) default "N"
-                )
-                """
-            )
-            q.execute(
-                """
-                create index REVISION on REVISIONS (REVISION)
-                """
-            )
-            
-            self._db_execute(
-                """
-                insert into REVISIONS (NAME)
-                select NAME from RESOURCE
-                """
-            )
-                
-
-    def _add_to_db(self, name, vcard, cursor = None):
-        """
-        Records the given address book resource in the index with the given name.
-        Resource names and UIDs must both be unique; only one resource name may
-        be associated with any given UID and vice versa.
-        NB This method does not commit the changes to the db - the caller
-        MUST take care of that
-        @param name: the name of the resource to add.
-        @param vcard: a L{AddressBook} object representing the resource
-            contents.
-        """
-        uid = vcard.resourceUID()
-
-        self._db_execute(
-            """
-            insert into RESOURCE (NAME, UID)
-            values (:1, :2)
-            """, name, uid,
-        )
-
-        self._db_execute(
-            """
-            insert or replace into REVISIONS (NAME, REVISION, DELETED)
-            values (:1, :2, :3)
-            """, name, self.bumpRevision(fast=True), 'N',
-        )
-    
-    def _delete_from_db(self, name, uid, dorevision=True):
-        """
-        Deletes the specified entry from all dbs.
-        @param name: the name of the resource to delete.
-        @param uid: the uid of the resource to delete.
-        """
-        self._db_execute("delete from RESOURCE where NAME = :1", name)
-        if dorevision:
-            self._db_execute(
-                """
-                update REVISIONS SET REVISION = :1, DELETED = :2
-                where NAME = :3
-                """, self.bumpRevision(fast=True), 'Y', name
-            )

Modified: CalendarServer/trunk/txdav/__init__.py
===================================================================
--- CalendarServer/trunk/txdav/__init__.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/txdav/__init__.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -1,3 +1,4 @@
+# -*- test-case-name: txdav -*-
 ##
 # Copyright (c) 2010 Apple Inc. All rights reserved.
 #

Modified: CalendarServer/trunk/txdav/base/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/base/datastore/sql.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/txdav/base/datastore/sql.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -18,6 +18,7 @@
 Logic common to SQL implementations.
 """
 
+from twisted.internet.defer import Deferred
 from inspect import getargspec
 
 def _getarg(argname, argspec, args, kw):
@@ -68,16 +69,37 @@
     @type memoAttribute: C{str}
     """
     def decorate(thunk):
-        spec = getargspec(thunk)
+        # cheater move to try to get the right argspec from inlineCallbacks.
+        # This could probably be more robust, but the 'cell_contents' thing
+        # probably can't (that's the only real reference to the underlying
+        # function).
+        if thunk.func_code.co_name == 'unwindGenerator':
+            specTarget = thunk.func_closure[0].cell_contents
+        else:
+            specTarget = thunk
+        spec = getargspec(specTarget)
         def outer(*a, **kw):
             self = a[0]
             memo = getattr(self, memoAttribute)
             key = _getarg(keyArgument, spec, a, kw)
             if key in memo:
-                return memo[key]
-            result = thunk(*a, **kw)
-            if result is not None:
-                memo[key] = result
+                result = memo[key]
+            else:
+                result = thunk(*a, **kw)
+                if result is not None:
+                    memo[key] = result
+            if isinstance(result, Deferred):
+                # clone the Deferred so that the old one keeps its result.
+                # FIXME: cancellation?
+                returnResult = Deferred()
+                def relayAndPreserve(innerResult):
+                    if innerResult is None and key in memo and memo[key] is result:
+                        # The result was None, call it again.
+                        del memo[key]
+                    returnResult.callback(innerResult)
+                    return innerResult
+                result.addBoth(relayAndPreserve)
+                return returnResult
             return result
         return outer
     return decorate

Modified: CalendarServer/trunk/txdav/base/datastore/subpostgres.py
===================================================================
--- CalendarServer/trunk/txdav/base/datastore/subpostgres.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/txdav/base/datastore/subpostgres.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -14,12 +14,16 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 ##
+import sys
 
 """
 Run and manage PostgreSQL as a subprocess.
 """
 import os
 import pwd
+import thread
+
+
 from hashlib import md5
 
 from twisted.python.procutils import which
@@ -67,6 +71,10 @@
 
     def execute(self, sql, args=()):
         self.connectionWrapper.state = 'executing %r' % (sql,)
+#        sys.stdout.write(
+#            "Really executing SQL %r in thread %r\n" %
+#            ((sql % tuple(args)), thread.get_ident())
+#        )
         self.realCursor.execute(sql, args)
 
 
@@ -75,7 +83,12 @@
 
 
     def fetchall(self):
-        return self.realCursor.fetchall()
+        results = self.realCursor.fetchall()
+#        sys.stdout.write(
+#            "Really fetching results %r thread %r\n" %
+#            (results, thread.get_ident())
+#        )
+        return results
 
 
 

Modified: CalendarServer/trunk/txdav/base/propertystore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/base/propertystore/sql.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/txdav/base/propertystore/sql.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -14,6 +14,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 ##
+from twisted.internet.defer import inlineCallbacks, returnValue
 
 """
 PostgreSQL data store.
@@ -30,31 +31,35 @@
 
 class PropertyStore(AbstractPropertyStore):
 
-    def __init__(self, defaultuser, txn, resourceID):
+    def __init__(self, *a, **kw):
+        raise NotImplementedError(
+            "do not construct directly, call PropertyStore.load()"
+        )
+
+
+    @classmethod
+    @inlineCallbacks
+    def load(cls, defaultuser, txn, resourceID):
+        self = cls.__new__(cls)
         super(PropertyStore, self).__init__(defaultuser)
         self._txn = txn
         self._resourceID = resourceID
+        self._cached = {}
+        rows = yield self._txn.execSQL(
+            """
+            select NAME, VIEWER_UID, VALUE from RESOURCE_PROPERTY
+            where RESOURCE_ID = %s
+            """,
+            [self._resourceID]
+        )
+        for name, uid, value in rows:
+            self._cached[(name, uid)] = value
+        returnValue(self)
 
-    @property
-    def _cached(self):
-        
-        if not hasattr(self, "_cached_properties"):
-            self._cached_properties = {}
-            rows = self._txn.execSQL(
-                """
-                select NAME, VIEWER_UID, VALUE from RESOURCE_PROPERTY
-                where RESOURCE_ID = %s
-                """,
-                [self._resourceID]
-            )
-            for name, uid, value in rows:
-                self._cached_properties[(name, uid)] = value
-        
-        return self._cached_properties
 
     def _getitem_uid(self, key, uid):
         validKey(key)
-        
+
         try:
             value = self._cached[(key.toString(), uid)]
         except KeyError:

Modified: CalendarServer/trunk/txdav/base/propertystore/test/base.py
===================================================================
--- CalendarServer/trunk/txdav/base/propertystore/test/base.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/txdav/base/propertystore/test/base.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -27,6 +27,7 @@
 
 from zope.interface.verify import verifyObject, BrokenMethodImplementation
 
+from twisted.internet.defer import inlineCallbacks
 from twisted.trial import unittest
 
 from twext.web2.dav import davxml
@@ -38,10 +39,6 @@
 class PropertyStoreTest(unittest.TestCase):
     # Subclass must define self.propertyStore in setUp().
 
-    def _preTest(self):
-        self.addCleanup(self._postTest)
-    def _postTest(self):
-        pass
     def _changed(self, store):
         store.flush()
     def _abort(self, store):
@@ -49,21 +46,20 @@
 
     def test_interface(self):
         try:
-            self._preTest()
             verifyObject(IPropertyStore, self.propertyStore)
         except BrokenMethodImplementation, e:
             self.fail(e)
 
+
+    @inlineCallbacks
     def test_set_get_contains(self):
-        
-        self._preTest()
 
         name = propertyName("test")
         value = propertyValue("Hello, World!")
 
         # Test with commit after change
         self.propertyStore[name] = value
-        self._changed(self.propertyStore)
+        yield self._changed(self.propertyStore)
         self.assertEquals(self.propertyStore.get(name, None), value)
         self.failUnless(name in self.propertyStore)
 
@@ -73,19 +69,19 @@
         self.assertEquals(self.propertyStore.get(name, None), value)
         self.failUnless(name in self.propertyStore)
 
+
+    @inlineCallbacks
     def test_delete_get_contains(self):
 
-        self._preTest()
-
         # Test with commit after change
         name = propertyName("test")
         value = propertyValue("Hello, World!")
 
         self.propertyStore[name] = value
-        self._changed(self.propertyStore)
+        yield self._changed(self.propertyStore)
 
         del self.propertyStore[name]
-        self._changed(self.propertyStore)
+        yield self._changed(self.propertyStore)
 
         self.assertEquals(self.propertyStore.get(name, None), None)
         self.failIf(name in self.propertyStore)
@@ -95,53 +91,53 @@
         value = propertyValue("Hello, Universe!")
 
         self.propertyStore[name] = value
-        self._changed(self.propertyStore)
+        yield self._changed(self.propertyStore)
 
         del self.propertyStore[name]
 
         self.assertEquals(self.propertyStore.get(name, None), None)
         self.failIf(name in self.propertyStore)
 
+
+    @inlineCallbacks
     def test_peruser(self):
 
-        self._preTest()
-
         name = propertyName("test")
         value1 = propertyValue("Hello, World1!")
         value2 = propertyValue("Hello, World2!")
 
         self.propertyStore1[name] = value1
-        self._changed(self.propertyStore1)
+        yield self._changed(self.propertyStore1)
         self.assertEquals(self.propertyStore1.get(name, None), value1)
         self.assertEquals(self.propertyStore2.get(name, None), None)
         self.failUnless(name in self.propertyStore1)
         self.failIf(name in self.propertyStore2)
-        
+
         self.propertyStore2[name] = value2
-        self._changed(self.propertyStore2)
+        yield self._changed(self.propertyStore2)
         self.assertEquals(self.propertyStore1.get(name, None), value1)
         self.assertEquals(self.propertyStore2.get(name, None), value2)
         self.failUnless(name in self.propertyStore1)
         self.failUnless(name in self.propertyStore2)
-        
+
         del self.propertyStore2[name]
-        self._changed(self.propertyStore2)
+        yield self._changed(self.propertyStore2)
         self.assertEquals(self.propertyStore1.get(name, None), value1)
         self.assertEquals(self.propertyStore2.get(name, None), None)
         self.failUnless(name in self.propertyStore1)
         self.failIf(name in self.propertyStore2)
-        
+
         del self.propertyStore1[name]
-        self._changed(self.propertyStore1)
+        yield self._changed(self.propertyStore1)
         self.assertEquals(self.propertyStore1.get(name, None), None)
         self.assertEquals(self.propertyStore2.get(name, None), None)
         self.failIf(name in self.propertyStore1)
         self.failIf(name in self.propertyStore2)
-        
-    def test_peruser_shadow(self):
 
-        self._preTest()
 
+    @inlineCallbacks
+    def test_peruserShadow(self):
+
         name = propertyName("shadow")
 
         self.propertyStore1.setSpecialProperties((name,), ())
@@ -151,38 +147,37 @@
         value2 = propertyValue("Hello, World2!")
 
         self.propertyStore1[name] = value1
-        self._changed(self.propertyStore1)
+        yield self._changed(self.propertyStore1)
         self.assertEquals(self.propertyStore1.get(name, None), value1)
         self.assertEquals(self.propertyStore2.get(name, None), value1)
         self.failUnless(name in self.propertyStore1)
         self.failUnless(name in self.propertyStore2)
-        
+
         self.propertyStore2[name] = value2
-        self._changed(self.propertyStore2)
+        yield self._changed(self.propertyStore2)
         self.assertEquals(self.propertyStore1.get(name, None), value1)
         self.assertEquals(self.propertyStore2.get(name, None), value2)
         self.failUnless(name in self.propertyStore1)
         self.failUnless(name in self.propertyStore2)
-        
+
         del self.propertyStore2[name]
-        self._changed(self.propertyStore2)
+        yield self._changed(self.propertyStore2)
         self.assertEquals(self.propertyStore1.get(name, None), value1)
         self.assertEquals(self.propertyStore2.get(name, None), value1)
         self.failUnless(name in self.propertyStore1)
         self.failUnless(name in self.propertyStore2)
-        
+
         del self.propertyStore1[name]
-        self._changed(self.propertyStore1)
+        yield self._changed(self.propertyStore1)
         self.assertEquals(self.propertyStore1.get(name, None), None)
         self.assertEquals(self.propertyStore2.get(name, None), None)
         self.failIf(name in self.propertyStore1)
         self.failIf(name in self.propertyStore2)
 
 
+    @inlineCallbacks
     def test_peruser_global(self):
 
-        self._preTest()
-
         name = propertyName("global")
 
         self.propertyStore1.setSpecialProperties((), (name,))
@@ -192,34 +187,32 @@
         value2 = propertyValue("Hello, World2!")
 
         self.propertyStore1[name] = value1
-        self._changed(self.propertyStore1)
+        yield self._changed(self.propertyStore1)
         self.assertEquals(self.propertyStore1.get(name, None), value1)
         self.assertEquals(self.propertyStore2.get(name, None), value1)
         self.failUnless(name in self.propertyStore1)
         self.failUnless(name in self.propertyStore2)
-        
+
         self.propertyStore2[name] = value2
-        self._changed(self.propertyStore2)
+        yield self._changed(self.propertyStore2)
         self.assertEquals(self.propertyStore1.get(name, None), value2)
         self.assertEquals(self.propertyStore2.get(name, None), value2)
         self.failUnless(name in self.propertyStore1)
         self.failUnless(name in self.propertyStore2)
-        
+
         del self.propertyStore2[name]
-        self._changed(self.propertyStore2)
+        yield self._changed(self.propertyStore2)
         self.assertEquals(self.propertyStore1.get(name, None), None)
         self.assertEquals(self.propertyStore2.get(name, None), None)
         self.failIf(name in self.propertyStore1)
         self.failIf(name in self.propertyStore2)
-        
 
+
     def test_iteration(self):
 
-        self._preTest()
-
         value = propertyValue("Hello, World!")
 
-        names = set(propertyName(str(i)) for i in (1,2,3,4))
+        names = set(propertyName(str(i)) for i in (1, 2, 3, 4))
 
         for name in names:
             self.propertyStore[name] = value
@@ -227,18 +220,17 @@
         self.assertEquals(set(self.propertyStore.keys()), names)
         self.assertEquals(len(self.propertyStore), len(names))
 
+
     def test_delete_none(self):
 
-        self._preTest()
         def doDelete():
             del self.propertyStore[propertyName("xyzzy")]
 
         self.assertRaises(KeyError, doDelete)
 
+
     def test_keyInPropertyName(self):
 
-        self._preTest()
-
         def doGet():
             self.propertyStore["xyzzy"]
 
@@ -256,10 +248,10 @@
         self.assertRaises(TypeError, doDelete)
         self.assertRaises(TypeError, doContains)
 
+
+    @inlineCallbacks
     def test_flush(self):
 
-        self._preTest()
-
         name = propertyName("test")
         value = propertyValue("Hello, World!")
 
@@ -268,8 +260,8 @@
         #
         self.propertyStore[name] = value
 
-        self._changed(self.propertyStore)
-        self._abort(self.propertyStore)
+        yield self._changed(self.propertyStore)
+        yield self._abort(self.propertyStore)
 
         self.assertEquals(self.propertyStore.get(name, None), value)
         self.assertEquals(len(self.propertyStore), 1)
@@ -279,29 +271,29 @@
         #
         del self.propertyStore[name]
 
-        self._changed(self.propertyStore)
-        self._abort(self.propertyStore)
+        yield self._changed(self.propertyStore)
+        yield self._abort(self.propertyStore)
 
         self.assertEquals(self.propertyStore.get(name, None), None)
         self.assertEquals(len(self.propertyStore), 0)
 
-    def test_abort(self):
 
-        self._preTest()
-
+    @inlineCallbacks
+    def test_abort(self):
         name = propertyName("test")
         value = propertyValue("Hello, World!")
 
         self.propertyStore[name] = value
 
-        self._abort(self.propertyStore)
+        yield self._abort(self.propertyStore)
 
         self.assertEquals(self.propertyStore.get(name, None), None)
         self.assertEquals(len(self.propertyStore), 0)
 
+
+    @inlineCallbacks
     def test_peruser_keys(self):
 
-        self._preTest()
         name = propertyName("shadow")
 
         self.propertyStore1.setSpecialProperties((name,), ())
@@ -310,7 +302,7 @@
         value1 = propertyValue("Hello, World1!")
 
         self.propertyStore1[name] = value1
-        self._changed(self.propertyStore1)
+        yield self._changed(self.propertyStore1)
 
         self.failUnless(name in self.propertyStore2.keys())
 

Modified: CalendarServer/trunk/txdav/base/propertystore/test/test_sql.py
===================================================================
--- CalendarServer/trunk/txdav/base/propertystore/test/test_sql.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/txdav/base/propertystore/test/test_sql.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -19,12 +19,10 @@
 L{txdav.caldav.datastore.test.common}.
 """
 
-from twisted.internet.defer import inlineCallbacks
+from twisted.internet.defer import inlineCallbacks, returnValue
 
-from txdav.caldav.datastore.test.common import StubNotifierFactory
+from txdav.common.datastore.test.util import buildStore, StubNotifierFactory
 
-from txdav.common.datastore.test.util import buildStore
-
 from txdav.base.propertystore.base import PropertyName
 from txdav.base.propertystore.test import base
 
@@ -38,66 +36,74 @@
 
 class PropertyStoreTest(base.PropertyStoreTest):
 
-    def _preTest(self):
+
+    @inlineCallbacks
+    def setUp(self):
+        self.notifierFactory = StubNotifierFactory()
+        self.store = yield buildStore(self, self.notifierFactory)
         self._txn = self.store.newTransaction()
-        self.propertyStore = self.propertyStore1 = PropertyStore(
+        self.propertyStore = self.propertyStore1 = yield PropertyStore.load(
             "user01", self._txn, 1
         )
-        self.propertyStore2 = PropertyStore("user01", self._txn, 1)
+        self.propertyStore2 = yield PropertyStore.load("user01", self._txn, 1)
         self.propertyStore2._setPerUserUID("user02")
-        
-        self.addCleanup(self._postTest)
 
-    def _postTest(self):
+
+    @inlineCallbacks
+    def tearDown(self):
         if hasattr(self, "_txn"):
-            self._txn.commit()
+            result = yield self._txn.commit()
             delattr(self, "_txn")
+        else:
+            result = None
         self.propertyStore = self.propertyStore1 = self.propertyStore2 = None
+        returnValue(result)
 
+
+    @inlineCallbacks
     def _changed(self, store):
         if hasattr(self, "_txn"):
-            self._txn.commit()
+            yield self._txn.commit()
             delattr(self, "_txn")
         self._txn = self.store.newTransaction()
         
         store = self.propertyStore1
-        self.propertyStore = self.propertyStore1 = PropertyStore(
+        self.propertyStore = self.propertyStore1 = yield PropertyStore.load(
             "user01", self._txn, 1
         )
         self.propertyStore1._shadowableKeys = store._shadowableKeys
         self.propertyStore1._globalKeys = store._globalKeys
 
         store = self.propertyStore2
-        self.propertyStore2 = PropertyStore("user01", self._txn, 1)
+        self.propertyStore2 = yield PropertyStore.load("user01", self._txn, 1)
         self.propertyStore2._setPerUserUID("user02")
         self.propertyStore2._shadowableKeys = store._shadowableKeys
         self.propertyStore2._globalKeys = store._globalKeys
 
+
+    @inlineCallbacks
     def _abort(self, store):
         if hasattr(self, "_txn"):
-            self._txn.abort()
+            yield self._txn.abort()
             delattr(self, "_txn")
 
         self._txn = self.store.newTransaction()
 
         store = self.propertyStore1
-        self.propertyStore = self.propertyStore1 = PropertyStore(
+        self.propertyStore = self.propertyStore1 = yield PropertyStore.load(
             "user01", self._txn, 1
         )
         self.propertyStore1._shadowableKeys = store._shadowableKeys
         self.propertyStore1._globalKeys = store._globalKeys
 
         store = self.propertyStore2
-        self.propertyStore2 = PropertyStore("user01", self._txn, 1)
+        self.propertyStore2 = yield PropertyStore.load("user01", self._txn, 1)
         self.propertyStore2._setPerUserUID("user02")
         self.propertyStore2._shadowableKeys = store._shadowableKeys
         self.propertyStore2._globalKeys = store._globalKeys
 
-    @inlineCallbacks
-    def setUp(self):
-        self.notifierFactory = StubNotifierFactory()
-        self.store = yield buildStore(self, self.notifierFactory)
 
+
 if PropertyStore is None:
     PropertyStoreTest.skip = importErrorMessage
 

Modified: CalendarServer/trunk/txdav/caldav/datastore/file.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/file.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/txdav/caldav/datastore/file.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -14,6 +14,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 ##
+from twisted.internet.defer import inlineCallbacks, returnValue
 
 """
 File calendar store.
@@ -44,13 +45,14 @@
 
 from twistedcaldav import caldavxml, customxml
 from twistedcaldav.caldavxml import ScheduleCalendarTransp, Opaque
-from twistedcaldav.index import Index as OldIndex, IndexSchedule as OldInboxIndex
 from twistedcaldav.sharing import InvitesDatabase
 
 from txdav.caldav.icalendarstore import IAttachment
 from txdav.caldav.icalendarstore import ICalendar, ICalendarObject
 from txdav.caldav.icalendarstore import ICalendarHome
 
+from txdav.caldav.datastore.index_file import Index as OldIndex,\
+    IndexSchedule as OldInboxIndex
 from txdav.caldav.datastore.util import (
     validateCalendarComponent, dropboxIDFromCalendarObject
 )
@@ -100,6 +102,7 @@
         for child in self.listCalendars():
             yield self.calendarWithName(child)
 
+
     def listCalendars(self):
         """
         Return a generator of the child resource names.
@@ -110,14 +113,16 @@
             yield name
 
 
+
+    @inlineCallbacks
     def calendarObjectWithDropboxID(self, dropboxID):
         """
         Implement lookup with brute-force scanning.
         """
         for calendar in self.calendars():
             for calendarObject in calendar.calendarObjects():
-                if dropboxID == calendarObject.dropboxID():
-                    return calendarObject
+                if dropboxID == (yield calendarObject.dropboxID()):
+                    returnValue(calendarObject)
 
 
     @property
@@ -332,26 +337,30 @@
         return self.component().getOrganizer()
 
 
+    @inlineCallbacks
     def createAttachmentWithName(self, name, contentType):
         """
         Implement L{ICalendarObject.removeAttachmentWithName}.
         """
         # Make a (FIXME: temp, remember rollbacks) file in dropbox-land
-        attachment = Attachment(self, name)
+        dropboxPath = yield self._dropboxPath()
+        attachment = Attachment(self, name, dropboxPath)
         self._attachments[name] = attachment
-        return attachment.store(contentType)
+        returnValue(attachment.store(contentType))
 
 
+    @inlineCallbacks
     def removeAttachmentWithName(self, name):
         """
         Implement L{ICalendarObject.removeAttachmentWithName}.
         """
         # FIXME: rollback, tests for rollback
-        self._dropboxPath().child(name).remove()
+        (yield self._dropboxPath()).child(name).remove()
         if name in self._attachments:
             del self._attachments[name]
 
 
+    @inlineCallbacks
     def attachmentWithName(self, name):
         # Attachments can be local or remote, but right now we only care about
         # local.  So we're going to base this on the listing of files in the
@@ -359,37 +368,46 @@
         # 'attach' properties.
 
         if name in self._attachments:
-            return self._attachments[name]
+            returnValue(self._attachments[name])
         # FIXME: cache consistently (put it in self._attachments)
-        if self._dropboxPath().child(name).exists():
-            return Attachment(self, name)
+        dbp = yield self._dropboxPath()
+        if dbp.child(name).exists():
+            returnValue(Attachment(self, name, dbp))
         else:
             # FIXME: test for non-existent attachment.
-            return None
+            returnValue(None)
 
 
+    @inlineCallbacks
     def attendeesCanManageAttachments(self):
-        return self.component().hasPropertyInAnyComponent("X-APPLE-DROPBOX")
+        returnValue((yield self.component()).hasPropertyInAnyComponent("X-APPLE-DROPBOX"))
 
 
     def dropboxID(self):
+        # NB: Deferred
         return dropboxIDFromCalendarObject(self)
 
 
+    @inlineCallbacks
     def _dropboxPath(self):
         dropboxPath = self._parentCollection._home._path.child(
             "dropbox"
-        ).child(self.dropboxID())
+        ).child((yield self.dropboxID()))
         if not dropboxPath.isdir():
             dropboxPath.makedirs()
-        return dropboxPath
+        returnValue(dropboxPath)
 
 
+    @inlineCallbacks
     def attachments(self):
         # See comment on attachmentWithName.
-        return [Attachment(self, name)
-                for name in self._dropboxPath().listdir()]
+        dropboxPath = (yield self._dropboxPath())
+        returnValue(
+            [Attachment(self, name, dropboxPath)
+            for name in dropboxPath.listdir()]
+        )
 
+
     def initPropertyStore(self, props):
         # Setup peruser special properties
         props.setSpecialProperties(
@@ -408,6 +426,7 @@
         )
 
 
+
 contentTypeKey = PropertyName.fromElement(GETContentType)
 md5key = PropertyName.fromElement(TwistedGETContentMD5)
 
@@ -457,9 +476,10 @@
 
     implements(IAttachment)
 
-    def __init__(self, calendarObject, name):
+    def __init__(self, calendarObject, name, dropboxPath):
         self._calendarObject = calendarObject
         self._name = name
+        self._dropboxPath = dropboxPath
 
 
     def name(self):
@@ -474,6 +494,7 @@
     def store(self, contentType):
         return AttachmentStorageTransport(self, contentType)
 
+
     def retrieve(self, protocol):
         # FIXME: makeConnection
         # FIXME: actually stream
@@ -482,10 +503,10 @@
         # FIXME: ConnectionDone, not NotImplementedError
         protocol.connectionLost(Failure(NotImplementedError()))
 
+
     @property
     def _path(self):
-        dropboxPath = self._calendarObject._dropboxPath()
-        return dropboxPath.child(self.name())
+        return self._dropboxPath.child(self.name())
 
 
 

Copied: CalendarServer/trunk/txdav/caldav/datastore/index_file.py (from rev 6445, CalendarServer/branches/users/glyph/more-deferreds-7/txdav/caldav/datastore/index_file.py)
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/index_file.py	                        (rev 0)
+++ CalendarServer/trunk/txdav/caldav/datastore/index_file.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -0,0 +1,1148 @@
+# -*- test-case-name: twistedcaldav.test.test_index -*-
+##
+# Copyright (c) 2005-2010 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+"""
+CalDAV Index.
+
+This API is considered private to static.py and is therefore subject to
+change.
+"""
+
+__all__ = [
+    "db_basename",
+    "ReservationError",
+    "MemcachedUIDReserver",
+    "Index",
+    "IndexSchedule",
+    "IndexedSearchException",
+]
+
+import datetime
+import time
+import hashlib
+
+try:
+    import sqlite3 as sqlite
+except ImportError:
+    from pysqlite2 import dbapi2 as sqlite
+
+from vobject.icalendar import utc
+
+from twisted.internet.defer import maybeDeferred, succeed, inlineCallbacks
+
+from twext.python.log import Logger, LoggingMixIn
+
+from txdav.common.icommondatastore import SyncTokenValidException,\
+    ReservationError, IndexedSearchException
+
+from twistedcaldav.ical import Component
+from twistedcaldav.query import calendarquery, calendarqueryfilter
+from twistedcaldav.sql import AbstractSQLDatabase
+from twistedcaldav.sql import db_prefix
+from twistedcaldav.instance import InvalidOverriddenInstanceError
+from twistedcaldav.config import config
+from twistedcaldav.memcachepool import CachePoolUserMixIn
+
+log = Logger()
+
+db_basename = db_prefix + "sqlite"
+schema_version = "10"
+collection_types = {"Calendar": "Regular Calendar Collection", "iTIP": "iTIP Calendar Collection"}
+
+icalfbtype_to_indexfbtype = {
+    "FREE"            : 'F',
+    "BUSY"            : 'B',
+    "BUSY-UNAVAILABLE": 'U',
+    "BUSY-TENTATIVE"  : 'T',
+}
+indexfbtype_to_icalfbtype = dict([(v, k) for k,v in icalfbtype_to_indexfbtype.iteritems()])
+
+#
+# Duration into the future through which recurrences are expanded in the index
+# by default.  This is a caching parameter which affects the size of the index;
+# it does not affect search results beyond this period, but it may affect
+# performance of such a search.
+#
+default_future_expansion_duration = datetime.timedelta(days=365*1)
+
+#
+# Maximum duration into the future through which recurrences are expanded in the
+# index.  This is a caching parameter which affects the size of the index; it
+# does not affect search results beyond this period, but it may affect
+# performance of such a search.
+#
+# When a search is performed on a time span that goes beyond that which is
+# expanded in the index, we have to open each resource which may have data in
+# that time period.  In order to avoid doing that multiple times, we want to
+# cache those results.  However, we don't necessarily want to cache all
+# occurrences into some obscenely far-in-the-future date, so we cap the caching
+# period.  Searches beyond this period will always be relatively expensive for
+# resources with occurrences beyond this period.
+#
+maximum_future_expansion_duration = datetime.timedelta(days=365*5)
+
+
+class AbstractCalendarIndex(AbstractSQLDatabase, LoggingMixIn):
+    """
+    Calendar collection index abstract base class that defines the apis for the index.
+    This will be subclassed for the two types of index behaviour we need: one for
+    regular calendar collections, one for schedule calendar collections.
+    """
+
+    def __init__(self, resource):
+        """
+        @param resource: the L{CalDAVResource} resource to
+            index. C{resource} must be a calendar collection (ie.
+            C{resource.isPseudoCalendarCollection()} returns C{True}.)
+        """
+        self.resource = resource
+        db_filename = self.resource.fp.child(db_basename).path
+        super(AbstractCalendarIndex, self).__init__(db_filename, False)
+
+    def create(self):
+        """
+        Create the index and initialize it.
+        """
+        self._db()
+
+    def reserveUID(self, uid):
+        """
+        Reserve a UID for this index's resource.
+        @param uid: the UID to reserve
+        @raise ReservationError: if C{uid} is already reserved
+        """
+        raise NotImplementedError
+
+    def unreserveUID(self, uid):
+        """
+        Unreserve a UID for this index's resource.
+        @param uid: the UID to reserve
+        @raise ReservationError: if C{uid} is not reserved
+        """
+        raise NotImplementedError
+
+    def isReservedUID(self, uid):
+        """
+        Check to see whether a UID is reserved.
+        @param uid: the UID to check
+        @return: True if C{uid} is reserved, False otherwise.
+        """
+        raise NotImplementedError
+
+    def isAllowedUID(self, uid, *names):
+        """
+        Checks to see whether to allow an operation with adds the the specified
+        UID is allowed to the index.  Specifically, the operation may not
+        violate the constraint that UIDs must be unique, and the UID must not
+        be reserved.
+        @param uid: the UID to check
+        @param names: the names of resources being replaced or deleted by the
+            operation; UIDs associated with these resources are not checked.
+        @return: True if the UID is not in the index and is not reserved,
+            False otherwise.
+        """
+        raise NotImplementedError
+
+    def resourceNamesForUID(self, uid):
+        """
+        Looks up the names of the resources with the given UID.
+        @param uid: the UID of the resources to look up.
+        @return: a list of resource names
+        """
+        names = self._db_values_for_sql("select NAME from RESOURCE where UID = :1", uid)
+
+        #
+        # Check that each name exists as a child of self.resource.  If not, the
+        # resource record is stale.
+        #
+        resources = []
+        for name in names:
+            name_utf8 = name.encode("utf-8")
+            if name is not None and self.resource.getChild(name_utf8) is None:
+                # Clean up
+                log.err("Stale resource record found for child %s with UID %s in %s" % (name, uid, self.resource))
+                self._delete_from_db(name, uid, False)
+                self._db_commit()
+            else:
+                resources.append(name_utf8)
+
+        return resources
+
+    def resourceNameForUID(self, uid):
+        """
+        Looks up the name of the resource with the given UID.
+        @param uid: the UID of the resource to look up.
+        @return: If the resource is found, its name; C{None} otherwise.
+        """
+        result = None
+
+        for name in self.resourceNamesForUID(uid):
+            assert result is None, "More than one resource with UID %s in calendar collection %r" % (uid, self)
+            result = name
+
+        return result
+
+    def resourceUIDForName(self, name):
+        """
+        Looks up the UID of the resource with the given name.
+        @param name: the name of the resource to look up.
+        @return: If the resource is found, the UID of the resource; C{None}
+            otherwise.
+        """
+        uid = self._db_value_for_sql("select UID from RESOURCE where NAME = :1", name)
+
+        return uid
+
+    def addResource(self, name, calendar, fast=False, reCreate=False):
+        """
+        Adding or updating an existing resource.
+        To check for an update we attempt to get an existing UID
+        for the resource name. If present, then the index entries for
+        that UID are removed. After that the new index entries are added.
+        @param name: the name of the resource to add.
+        @param calendar: a L{Calendar} object representing the resource
+            contents.
+        @param fast: if C{True} do not do commit, otherwise do commit.
+        """
+        oldUID = self.resourceUIDForName(name)
+        if oldUID is not None:
+            self._delete_from_db(name, oldUID, False)
+        self._add_to_db(name, calendar, reCreate=reCreate)
+        if not fast:
+            self._db_commit()
+
+    def deleteResource(self, name):
+        """
+        Remove this resource from the index.
+        @param name: the name of the resource to add.
+        @param uid: the UID of the calendar component in the resource.
+        """
+        uid = self.resourceUIDForName(name)
+        if uid is not None:
+            self._delete_from_db(name, uid)
+            self._db_commit()
+
+    def resourceExists(self, name):
+        """
+        Determines whether the specified resource name exists in the index.
+        @param name: the name of the resource to test
+        @return: True if the resource exists, False if not
+        """
+        uid = self._db_value_for_sql("select UID from RESOURCE where NAME = :1", name)
+        return uid is not None
+
+    def resourcesExist(self, names):
+        """
+        Determines whether the specified resource name exists in the index.
+        @param names: a C{list} containing the names of the resources to test
+        @return: a C{list} of all names that exist
+        """
+        statement = "select NAME from RESOURCE where NAME in ("
+        for ctr in (item[0] for item in enumerate(names)):
+            if ctr != 0:
+                statement += ", "
+            statement += ":%s" % (ctr,)
+        statement += ")"
+        results = self._db_values_for_sql(statement, *names)
+        return results
+
+
+    def testAndUpdateIndex(self, minDate):
+        # Find out if the index is expanded far enough
+        names = self.notExpandedBeyond(minDate)
+        # Actually expand recurrence max
+        for name in names:
+            self.log_info("Search falls outside range of index for %s %s" % (name, minDate))
+            self.reExpandResource(name, minDate)
+
+    def whatchanged(self, revision, depth):
+
+        results = [(name.encode("utf-8"), deleted) for name, deleted in self._db_execute("select NAME, DELETED from REVISIONS where REVISION > :1", revision)]
+        results.sort(key=lambda x:x[1])
+        
+        changed = []
+        deleted = []
+        for name, wasdeleted in results:
+            if name:
+                if wasdeleted == 'Y':
+                    if revision:
+                        deleted.append(name)
+                else:
+                    changed.append(name)
+            else:
+                raise SyncTokenValidException
+        
+        return changed, deleted,
+
+    def lastRevision(self):
+        return self._db_value_for_sql(
+            "select REVISION from REVISION_SEQUENCE"
+        )
+
+    def bumpRevision(self, fast=False):
+        self._db_execute(
+            """
+            update REVISION_SEQUENCE set REVISION = REVISION + 1
+            """,
+        )
+        self._db_commit()
+        return self._db_value_for_sql(
+            """
+            select REVISION from REVISION_SEQUENCE
+            """,
+        )
+
+    def indexedSearch(self, filter, useruid="", fbtype=False):
+        """
+        Finds resources matching the given qualifiers.
+        @param filter: the L{Filter} for the calendar-query to execute.
+        @return: an iterable of tuples for each resource matching the
+            given C{qualifiers}. The tuples are C{(name, uid, type)}, where
+            C{name} is the resource name, C{uid} is the resource UID, and
+            C{type} is the resource iCalendar component type.
+        """
+
+        # Make sure we have a proper Filter element and get the partial SQL
+        # statement to use.
+        if isinstance(filter, calendarqueryfilter.Filter):
+            if fbtype:
+                # Lookup the useruid - try the empty (default) one if needed
+                dbuseruid = self._db_value_for_sql(
+                    "select PERUSERID from PERUSER where USERUID == :1",
+                    useruid,
+                )
+            else:
+                dbuseruid = ""
+
+            qualifiers = calendarquery.sqlcalendarquery(filter, None, dbuseruid)
+            if qualifiers is not None:
+                # Determine how far we need to extend the current expansion of
+                # events. If we have an open-ended time-range we will expand one
+                # year past the start. That should catch bounded recurrences - unbounded
+                # will have been indexed with an "infinite" value always included.
+                maxDate, isStartDate = filter.getmaxtimerange()
+                if maxDate:
+                    maxDate = maxDate.date()
+                    if isStartDate:
+                        maxDate += datetime.timedelta(days=365)
+                    self.testAndUpdateIndex(maxDate)
+            else:
+                # We cannot handler this filter in an indexed search
+                raise IndexedSearchException()
+
+        else:
+            qualifiers = None
+
+        # Perform the search
+        if qualifiers is None:
+            rowiter = self._db_execute("select NAME, UID, TYPE from RESOURCE")
+        else:
+            if fbtype:
+                # Lookup the useruid - try the empty (default) one if needed
+                dbuseruid = self._db_value_for_sql(
+                    "select PERUSERID from PERUSER where USERUID == :1",
+                    useruid,
+                )
+
+                # For a free-busy time-range query we return all instances
+                rowiter = self._db_execute(
+                    "select DISTINCT RESOURCE.NAME, RESOURCE.UID, RESOURCE.TYPE, RESOURCE.ORGANIZER, TIMESPAN.FLOAT, TIMESPAN.START, TIMESPAN.END, TIMESPAN.FBTYPE, TIMESPAN.TRANSPARENT, TRANSPARENCY.TRANSPARENT" + 
+                    qualifiers[0],
+                    *qualifiers[1]
+                )
+            else:
+                rowiter = self._db_execute("select DISTINCT RESOURCE.NAME, RESOURCE.UID, RESOURCE.TYPE" + qualifiers[0], *qualifiers[1])
+
+        # Check result for missing resources
+
+        for row in rowiter:
+            name = row[0]
+            if self.resource.getChild(name.encode("utf-8")):
+                if fbtype:
+                    row = list(row)
+                    if row[9]:
+                        row[8] = row[9]
+                    del row[9]
+                yield row
+            else:
+                log.err("Calendar resource %s is missing from %s. Removing from index."
+                        % (name, self.resource))
+                self.deleteResource(name)
+
+    def bruteForceSearch(self):
+        """
+        List the whole index and tests for existence, updating the index
+        @return: all resources in the index
+        """
+        # List all resources
+        rowiter = self._db_execute("select NAME, UID, TYPE from RESOURCE")
+
+        # Check result for missing resources:
+
+        for row in rowiter:
+            name = row[0]
+            if self.resource.getChild(name.encode("utf-8")):
+                yield row
+            else:
+                log.err("Calendar resource %s is missing from %s. Removing from index."
+                        % (name, self.resource))
+                self.deleteResource(name)
+
+
+    def _db_version(self):
+        """
+        @return: the schema version assigned to this index.
+        """
+        return schema_version
+
+    def _add_to_db(self, name, calendar, cursor=None, expand_until=None, reCreate=False):
+        """
+        Records the given calendar resource in the index with the given name.
+        Resource names and UIDs must both be unique; only one resource name may
+        be associated with any given UID and vice versa.
+        NB This method does not commit the changes to the db - the caller
+        MUST take care of that
+        @param name: the name of the resource to add.
+        @param calendar: a L{Calendar} object representing the resource
+            contents.
+        """
+        raise NotImplementedError
+
+    def _delete_from_db(self, name, uid, dorevision=True):
+        """
+        Deletes the specified entry from all dbs.
+        @param name: the name of the resource to delete.
+        @param uid: the uid of the resource to delete.
+        """
+        raise NotImplementedError
+
+class CalendarIndex (AbstractCalendarIndex):
+    """
+    Calendar index - abstract class for indexer that indexes calendar objects in a collection.
+    """
+
+    def __init__(self, resource):
+        """
+        @param resource: the L{CalDAVResource} resource to
+            index.
+        """
+        super(CalendarIndex, self).__init__(resource)
+
+    def _db_init_data_tables_base(self, q, uidunique):
+        """
+        Initialise the underlying database tables.
+        @param q:           a database cursor to use.
+        """
+        #
+        # RESOURCE table is the primary index table
+        #   NAME: Last URI component (eg. <uid>.ics, RESOURCE primary key)
+        #   UID: iCalendar UID (may or may not be unique)
+        #   TYPE: iCalendar component type
+        #   RECURRANCE_MAX: Highest date of recurrence expansion
+        #   ORGANIZER: cu-address of the Organizer of the event
+        #
+        q.execute(
+            """
+            create table RESOURCE (
+                RESOURCEID     integer primary key autoincrement,
+                NAME           text unique,
+                UID            text%s,
+                TYPE           text,
+                RECURRANCE_MAX date,
+                ORGANIZER      text
+            )
+            """ % (" unique" if uidunique else "",)
+        )
+
+        #
+        # TIMESPAN table tracks (expanded) time spans for resources
+        #   NAME: Related resource (RESOURCE foreign key)
+        #   FLOAT: 'Y' if start/end are floating, 'N' otherwise
+        #   START: Start date
+        #   END: End date
+        #   FBTYPE: FBTYPE value:
+        #     '?' - unknown
+        #     'F' - free
+        #     'B' - busy
+        #     'U' - busy-unavailable
+        #     'T' - busy-tentative
+        #   TRANSPARENT: Y if transparent, N if opaque (default non-per-user value)
+        #
+        q.execute(
+            """
+            create table TIMESPAN (
+                INSTANCEID   integer primary key autoincrement,
+                RESOURCEID   integer,
+                FLOAT        text(1),
+                START        date,
+                END          date,
+                FBTYPE       text(1),
+                TRANSPARENT  text(1)
+            )
+            """
+        )
+        q.execute(
+            """
+            create index STARTENDFLOAT on TIMESPAN (START, END, FLOAT)
+            """
+        )
+
+        #
+        # PERUSER table tracks per-user ids
+        #   PERUSERID: autoincrement primary key
+        #   UID: User ID used in calendar data
+        #
+        q.execute(
+            """
+            create table PERUSER (
+                PERUSERID       integer primary key autoincrement,
+                USERUID         text
+            )
+            """
+        )
+        q.execute(
+            """
+            create index PERUSER_UID on PERUSER (USERUID)
+            """
+        )
+
+        #
+        # TRANSPARENCY table tracks per-user per-instance transparency
+        #   PERUSERID: user id key
+        #   INSTANCEID: instance id key
+        #   TRANSPARENT: Y if transparent, N if opaque
+        #
+        q.execute(
+            """
+            create table TRANSPARENCY (
+                PERUSERID       integer,
+                INSTANCEID      integer,
+                TRANSPARENT     text(1)
+            )
+            """
+        )
+
+        #
+        # REVISIONS table tracks changes
+        #   NAME: Last URI component (eg. <uid>.ics, RESOURCE primary key)
+        #   REVISION: revision number
+        #   WASDELETED: Y if revision deleted, N if added or changed
+        #
+        q.execute(
+            """
+            create table REVISION_SEQUENCE (
+                REVISION        integer
+            )
+            """
+        )
+        q.execute(
+            """
+            insert into REVISION_SEQUENCE (REVISION) values (0)
+            """
+        )
+        q.execute(
+            """
+            create table REVISIONS (
+                NAME            text unique,
+                REVISION        integer,
+                DELETED         text(1)
+            )
+            """
+        )
+        q.execute(
+            """
+            create index REVISION on REVISIONS (REVISION)
+            """
+        )
+
+        if uidunique:
+            #
+            # RESERVED table tracks reserved UIDs
+            #   UID: The UID being reserved
+            #   TIME: When the reservation was made
+            #
+            q.execute(
+                """
+                create table RESERVED (
+                    UID  text unique,
+                    TIME date
+                )
+                """
+            )
+
+        # Cascading triggers to help on delete
+        q.execute(
+            """
+            create trigger resourceDelete after delete on RESOURCE
+            for each row
+            begin
+                delete from TIMESPAN where TIMESPAN.RESOURCEID = OLD.RESOURCEID;
+            end
+            """
+        )
+        q.execute(
+            """
+            create trigger timespanDelete after delete on TIMESPAN
+            for each row
+            begin
+                delete from TRANSPARENCY where INSTANCEID = OLD.INSTANCEID;
+            end
+            """
+        )
+        
+    def _db_can_upgrade(self, old_version):
+        """
+        Can we do an in-place upgrade
+        """
+        
+        # v10 is a big change - no upgrade possible
+        return False
+
+    def _db_upgrade_data_tables(self, q, old_version):
+        """
+        Upgrade the data from an older version of the DB.
+        """
+
+        # v10 is a big change - no upgrade possible
+        pass
+
+    def notExpandedBeyond(self, minDate):
+        """
+        Gives all resources which have not been expanded beyond a given date
+        in the index
+        """
+        return self._db_values_for_sql("select NAME from RESOURCE where RECURRANCE_MAX < :1", minDate)
+
+    def reExpandResource(self, name, expand_until):
+        """
+        Given a resource name, remove it from the database and re-add it
+        with a longer expansion.
+        """
+        calendar = self.resource.getChild(name).iCalendar()
+        self._add_to_db(name, calendar, expand_until=expand_until, reCreate=True)
+        self._db_commit()
+
+    def _add_to_db(self, name, calendar, cursor = None, expand_until=None, reCreate=False):
+        """
+        Records the given calendar resource in the index with the given name.
+        Resource names and UIDs must both be unique; only one resource name may
+        be associated with any given UID and vice versa.
+        NB This method does not commit the changes to the db - the caller
+        MUST take care of that
+        @param name: the name of the resource to add.
+        @param calendar: a L{Calendar} object representing the resource
+            contents.
+        """
+        uid = calendar.resourceUID()
+        organizer = calendar.getOrganizer()
+        if not organizer:
+            organizer = ""
+
+        # Decide how far to expand based on the component
+        master = calendar.masterComponent()
+        if master is None or not calendar.isRecurring() and not calendar.isRecurringUnbounded():
+            # When there is no master we have a set of overridden components - index them all.
+            # When there is one instance - index it.
+            # When bounded - index all.
+            expand = datetime.datetime(2100, 1, 1, 0, 0, 0, tzinfo=utc)
+        else:
+            if expand_until:
+                expand = expand_until
+            else:
+                expand = datetime.date.today() + default_future_expansion_duration
+    
+            if expand > (datetime.date.today() + maximum_future_expansion_duration):
+                raise IndexedSearchException
+
+        try:
+            instances = calendar.expandTimeRanges(expand, ignoreInvalidInstances=reCreate)
+        except InvalidOverriddenInstanceError, e:
+            log.err("Invalid instance %s when indexing %s in %s" % (e.rid, name, self.resource,))
+            raise
+
+        self._delete_from_db(name, uid, False)
+
+        # Add RESOURCE item
+        self._db_execute(
+            """
+            insert into RESOURCE (NAME, UID, TYPE, RECURRANCE_MAX, ORGANIZER)
+            values (:1, :2, :3, :4, :5)
+            """, name, uid, calendar.resourceType(), instances.limit, organizer
+        )
+        resourceid = self.lastrowid
+
+        # Get a set of all referenced per-user UIDs and map those to entries already
+        # in the DB and add new ones as needed
+        useruids = calendar.allPerUserUIDs()
+        useruids.add("")
+        useruidmap = {}
+        for useruid in useruids:
+            peruserid = self._db_value_for_sql(
+                "select PERUSERID from PERUSER where USERUID = :1",
+                useruid
+            )
+            if peruserid is None:
+                self._db_execute(
+                    """
+                    insert into PERUSER (USERUID)
+                    values (:1)
+                    """, useruid
+                )
+                peruserid = self.lastrowid
+            useruidmap[useruid] = peruserid
+            
+        for key in instances:
+            instance = instances[key]
+            start = instance.start.replace(tzinfo=utc)
+            end = instance.end.replace(tzinfo=utc)
+            float = 'Y' if instance.start.tzinfo is None else 'N'
+            transp = 'T' if instance.component.propertyValue("TRANSP") == "TRANSPARENT" else 'F'
+            self._db_execute(
+                """
+                insert into TIMESPAN (RESOURCEID, FLOAT, START, END, FBTYPE, TRANSPARENT)
+                values (:1, :2, :3, :4, :5, :6)
+                """,
+                resourceid,
+                float,
+                start,
+                end,
+                icalfbtype_to_indexfbtype.get(instance.component.getFBType(), 'F'),
+                transp
+            )
+            instanceid = self.lastrowid
+            peruserdata = calendar.perUserTransparency(instance.rid)
+            for useruid, transp in peruserdata:
+                peruserid = useruidmap[useruid]
+                self._db_execute(
+                    """
+                    insert into TRANSPARENCY (PERUSERID, INSTANCEID, TRANSPARENT)
+                    values (:1, :2, :3)
+                    """, peruserid, instanceid, 'T' if transp else 'F'
+                )
+                    
+
+        # Special - for unbounded recurrence we insert a value for "infinity"
+        # that will allow an open-ended time-range to always match it.
+        if calendar.isRecurringUnbounded():
+            start = datetime.datetime(2100, 1, 1, 0, 0, 0, tzinfo=utc)
+            end = datetime.datetime(2100, 1, 1, 1, 0, 0, tzinfo=utc)
+            float = 'N'
+            self._db_execute(
+                """
+                insert into TIMESPAN (RESOURCEID, FLOAT, START, END, FBTYPE, TRANSPARENT)
+                values (:1, :2, :3, :4, :5, :6)
+                """, resourceid, float, start, end, '?', '?'
+            )
+            instanceid = self.lastrowid
+            peruserdata = calendar.perUserTransparency(None)
+            for useruid, transp in peruserdata:
+                peruserid = useruidmap[useruid]
+                self._db_execute(
+                    """
+                    insert into TRANSPARENCY (PERUSERID, INSTANCEID, TRANSPARENT)
+                    values (:1, :2, :3)
+                    """, peruserid, instanceid, 'T' if transp else 'F'
+                )
+            
+        self._db_execute(
+            """
+            insert or replace into REVISIONS (NAME, REVISION, DELETED)
+            values (:1, :2, :3)
+            """, name, self.bumpRevision(fast=True), 'N',
+        )
+
+    def _delete_from_db(self, name, uid, dorevision=True):
+        """
+        Deletes the specified entry from all dbs.
+        @param name: the name of the resource to delete.
+        @param uid: the uid of the resource to delete.
+        """
+        self._db_execute("delete from RESOURCE where NAME = :1", name)
+        if dorevision:
+            self._db_execute(
+                """
+                update REVISIONS SET REVISION = :1, DELETED = :2
+                where NAME = :3
+                """, self.bumpRevision(fast=True), 'Y', name
+            )
+
+
+def wrapInDeferred(f):
+    def _(*args, **kwargs):
+        return maybeDeferred(f, *args, **kwargs)
+
+    return _
+
+
+class MemcachedUIDReserver(CachePoolUserMixIn, LoggingMixIn):
+    def __init__(self, index, cachePool=None):
+        self.index = index
+        self._cachePool = cachePool
+
+    def _key(self, uid):
+        return 'reservation:%s' % (
+            hashlib.md5('%s:%s' % (uid,
+                                   self.index.resource.fp.path)).hexdigest())
+
+    def reserveUID(self, uid):
+        uid = uid.encode('utf-8')
+        self.log_debug("Reserving UID %r @ %r" % (
+                uid,
+                self.index.resource.fp.path))
+
+        def _handleFalse(result):
+            if result is False:
+                raise ReservationError(
+                    "UID %s already reserved for calendar collection %s."
+                    % (uid, self.index.resource)
+                    )
+
+        d = self.getCachePool().add(self._key(uid),
+                                    'reserved',
+                                    expireTime=config.UIDReservationTimeOut)
+        d.addCallback(_handleFalse)
+        return d
+
+
+    def unreserveUID(self, uid):
+        uid = uid.encode('utf-8')
+        self.log_debug("Unreserving UID %r @ %r" % (
+                uid,
+                self.index.resource.fp.path))
+
+        def _handleFalse(result):
+            if result is False:
+                raise ReservationError(
+                    "UID %s is not reserved for calendar collection %s."
+                    % (uid, self.index.resource)
+                    )
+
+        d =self.getCachePool().delete(self._key(uid))
+        d.addCallback(_handleFalse)
+        return d
+
+
+    def isReservedUID(self, uid):
+        uid = uid.encode('utf-8')
+        self.log_debug("Is reserved UID %r @ %r" % (
+                uid,
+                self.index.resource.fp.path))
+
+        def _checkValue((flags, value)):
+            if value is None:
+                return False
+            else:
+                return True
+
+        d = self.getCachePool().get(self._key(uid))
+        d.addCallback(_checkValue)
+        return d
+
+
+
+class SQLUIDReserver(object):
+    def __init__(self, index):
+        self.index = index
+
+    @wrapInDeferred
+    def reserveUID(self, uid):
+        """
+        Reserve a UID for this index's resource.
+        @param uid: the UID to reserve
+        @raise ReservationError: if C{uid} is already reserved
+        """
+
+        try:
+            self.index._db_execute("insert into RESERVED (UID, TIME) values (:1, :2)", uid, datetime.datetime.now())
+            self.index._db_commit()
+        except sqlite.IntegrityError:
+            self.index._db_rollback()
+            raise ReservationError(
+                "UID %s already reserved for calendar collection %s."
+                % (uid, self.index.resource)
+            )
+        except sqlite.Error, e:
+            log.err("Unable to reserve UID: %s", (e,))
+            self.index._db_rollback()
+            raise
+
+    def unreserveUID(self, uid):
+        """
+        Unreserve a UID for this index's resource.
+        @param uid: the UID to reserve
+        @raise ReservationError: if C{uid} is not reserved
+        """
+
+        def _cb(result):
+            if result == False:
+                raise ReservationError(
+                    "UID %s is not reserved for calendar collection %s."
+                    % (uid, self.index.resource)
+                    )
+            else:
+                try:
+                    self.index._db_execute(
+                        "delete from RESERVED where UID = :1", uid)
+                    self.index._db_commit()
+                except sqlite.Error, e:
+                    log.err("Unable to unreserve UID: %s", (e,))
+                    self.index._db_rollback()
+                    raise
+
+        d = self.isReservedUID(uid)
+        d.addCallback(_cb)
+        return d
+
+
+    @wrapInDeferred
+    def isReservedUID(self, uid):
+        """
+        Check to see whether a UID is reserved.
+        @param uid: the UID to check
+        @return: True if C{uid} is reserved, False otherwise.
+        """
+
+        rowiter = self.index._db_execute("select UID, TIME from RESERVED where UID = :1", uid)
+        for uid, attime in rowiter:
+            # Double check that the time is within a reasonable period of now
+            # otherwise we probably have a stale reservation
+            tm = time.strptime(attime[:19], "%Y-%m-%d %H:%M:%S")
+            dt = datetime.datetime(year=tm.tm_year, month=tm.tm_mon, day=tm.tm_mday, hour=tm.tm_hour, minute=tm.tm_min, second = tm.tm_sec)
+            if datetime.datetime.now() - dt > datetime.timedelta(seconds=config.UIDReservationTimeOut):
+                try:
+                    self.index._db_execute("delete from RESERVED where UID = :1", uid)
+                    self.index._db_commit()
+                except sqlite.Error, e:
+                    log.err("Unable to unreserve UID: %s", (e,))
+                    self.index._db_rollback()
+                    raise
+                return False
+            else:
+                return True
+
+        return False
+
+
+
+class Index (CalendarIndex):
+    """
+    Calendar collection index - regular collection that enforces CalDAV UID uniqueness requirement.
+    """
+
+    def __init__(self, resource):
+        """
+        @param resource: the L{CalDAVResource} resource to
+            index. C{resource} must be a calendar collection (i.e.
+            C{resource.isPseudoCalendarCollection()} returns C{True}.)
+        """
+        assert resource.isCalendarCollection(), "non-calendar collection resource %s has no index." % (resource,)
+        super(Index, self).__init__(resource)
+
+        if (
+            hasattr(config, "Memcached") and
+            config.Memcached.Pools.Default.ClientEnabled
+        ):
+            self.reserver = MemcachedUIDReserver(self)
+        else:
+            self.reserver = SQLUIDReserver(self)
+
+    #
+    # A dict of sets. The dict keys are calendar collection paths,
+    # and the sets contains reserved UIDs for each path.
+    #
+
+    def reserveUID(self, uid):
+        return self.reserver.reserveUID(uid)
+
+
+    def unreserveUID(self, uid):
+        return self.reserver.unreserveUID(uid)
+
+
+    def isReservedUID(self, uid):
+        return self.reserver.isReservedUID(uid)
+
+
+    def isAllowedUID(self, uid, *names):
+        """
+        Checks to see whether to allow an operation which would add the
+        specified UID to the index.  Specifically, the operation may not
+        violate the constraint that UIDs must be unique.
+        @param uid: the UID to check
+        @param names: the names of resources being replaced or deleted by the
+            operation; UIDs associated with these resources are not checked.
+        @return: True if the UID is not in the index and is not reserved,
+            False otherwise.
+        """
+        rname = self.resourceNameForUID(uid)
+        return (rname is None or rname in names)
+
+    def _db_type(self):
+        """
+        @return: the collection type assigned to this index.
+        """
+        return collection_types["Calendar"]
+
+    def _db_init_data_tables(self, q):
+        """
+        Initialise the underlying database tables.
+        @param q:           a database cursor to use.
+        """
+
+        # Create database where the RESOURCE table has unique UID column.
+        self._db_init_data_tables_base(q, True)
+
+    def _db_recreate(self, do_commit=True):
+        """
+        Re-create the database tables from existing calendar data.
+        """
+
+        #
+        # Populate the DB with data from already existing resources.
+        # This allows for index recovery if the DB file gets
+        # deleted.
+        #
+        fp = self.resource.fp
+        for name in fp.listdir():
+            if name.startswith("."):
+                continue
+
+            try:
+                stream = fp.child(name).open()
+            except (IOError, OSError), e:
+                log.err("Unable to open resource %s: %s" % (name, e))
+                continue
+
+            # FIXME: This is blocking I/O
+            try:
+                calendar = Component.fromStream(stream)
+                calendar.validateForCalDAV()
+            except ValueError:
+                log.err("Non-calendar resource: %s" % (name,))
+            else:
+                #log.msg("Indexing resource: %s" % (name,))
+                self.addResource(name, calendar, True, reCreate=True)
+            finally:
+                stream.close()
+
+        # Do commit outside of the loop for better performance
+        if do_commit:
+            self._db_commit()
+
+class IndexSchedule (CalendarIndex):
+    """
+    Schedule collection index - does not require UID uniqueness.
+    """
+
+    def reserveUID(self, uid): #@UnusedVariable
+        """
+        Reserve a UID for this index's resource.
+        @param uid: the UID to reserve
+        @raise ReservationError: if C{uid} is already reserved
+        """
+
+        # iTIP does not require unique UIDs
+        return succeed(None)
+
+    def unreserveUID(self, uid): #@UnusedVariable
+        """
+        Unreserve a UID for this index's resource.
+        @param uid: the UID to reserve
+        @raise ReservationError: if C{uid} is not reserved
+        """
+
+        # iTIP does not require unique UIDs
+        return succeed(None)
+
+    def isReservedUID(self, uid): #@UnusedVariable
+        """
+        Check to see whether a UID is reserved.
+        @param uid: the UID to check
+        @return: True if C{uid} is reserved, False otherwise.
+        """
+
+        # iTIP does not require unique UIDs
+        return succeed(False)
+
+    def isAllowedUID(self, uid, *names): #@UnusedVariable
+        """
+        Checks to see whether to allow an operation with adds the the specified
+        UID is allowed to the index.  Specifically, the operation may not
+        violate the constraint that UIDs must be unique, and the UID must not
+        be reserved.
+        @param uid: the UID to check
+        @param names: the names of resources being replaced or deleted by the
+            operation; UIDs associated with these resources are not checked.
+        @return: True if the UID is not in the index and is not reserved,
+            False otherwise.
+        """
+
+        # iTIP does not require unique UIDs
+        return True
+
+    def _db_type(self):
+        """
+        @return: the collection type assigned to this index.
+        """
+        return collection_types["iTIP"]
+
+    def _db_init_data_tables(self, q):
+        """
+        Initialise the underlying database tables.
+        @param q:           a database cursor to use.
+        """
+
+        # Create database where the RESOURCE table has a UID column that is not unique.
+        self._db_init_data_tables_base(q, False)
+
+    def _db_recreate(self, do_commit=True):
+        """
+        Re-create the database tables from existing calendar data.
+        """
+
+        #
+        # Populate the DB with data from already existing resources.
+        # This allows for index recovery if the DB file gets
+        # deleted.
+        #
+        fp = self.resource.fp
+        for name in fp.listdir():
+            if name.startswith("."):
+                continue
+
+            try:
+                stream = fp.child(name).open()
+            except (IOError, OSError), e:
+                log.err("Unable to open resource %s: %s" % (name, e))
+                continue
+
+            # FIXME: This is blocking I/O
+            try:
+                calendar = Component.fromStream(stream)
+                calendar.validCalendarForCalDAV()
+                calendar.validateComponentsForCalDAV(True)
+            except ValueError:
+                log.err("Non-calendar resource: %s" % (name,))
+            else:
+                #log.msg("Indexing resource: %s" % (name,))
+                self.addResource(name, calendar, True, reCreate=True)
+            finally:
+                stream.close()
+
+        # Do commit outside of the loop for better performance
+        if do_commit:
+            self._db_commit()


Property changes on: CalendarServer/trunk/txdav/caldav/datastore/index_file.py
___________________________________________________________________
Added: svn:mergeinfo
   + /CalendarServer/branches/config-separation/txdav/caldav/datastore/index_file.py:4379-4443
/CalendarServer/branches/egg-info-351/txdav/caldav/datastore/index_file.py:4589-4625
/CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/index_file.py:6167-6191
/CalendarServer/branches/new-store/txdav/caldav/datastore/index_file.py:5594-5934
/CalendarServer/branches/new-store-no-caldavfile/txdav/caldav/datastore/index_file.py:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2/txdav/caldav/datastore/index_file.py:5936-5981
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/txdav/caldav/datastore/index_file.py:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/txdav/caldav/datastore/index_file.py:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591/txdav/caldav/datastore/index_file.py:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/txdav/caldav/datastore/index_file.py:4465-4957
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/txdav/caldav/datastore/index_file.py:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/txdav/caldav/datastore/index_file.py:5188-5440
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/caldav/datastore/index_file.py:4971-5080
/CalendarServer/branches/users/glyph/more-deferreds-6/txdav/caldav/datastore/index_file.py:6322-6368
/CalendarServer/branches/users/glyph/more-deferreds-7/txdav/caldav/datastore/index_file.py:6369-6445
/CalendarServer/branches/users/glyph/sendfdport/txdav/caldav/datastore/index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sql-store/txdav/caldav/datastore/index_file.py:5929-6073
/CalendarServer/branches/users/glyph/use-system-twisted/txdav/caldav/datastore/index_file.py:5084-5149
/CalendarServer/branches/users/sagen/locations-resources/txdav/caldav/datastore/index_file.py:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2/txdav/caldav/datastore/index_file.py:5052-5061
/CalendarServer/branches/users/sagen/resource-delegates-4038/txdav/caldav/datastore/index_file.py:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/txdav/caldav/datastore/index_file.py:4068-4075
/CalendarServer/branches/users/sagen/resources-2/txdav/caldav/datastore/index_file.py:5084-5093
/CalendarServer/branches/users/wsanchez/transations/txdav/caldav/datastore/index_file.py:5515-5593
/CalendarServer/trunk/twistedcaldav/index.py:6322-6394

Modified: CalendarServer/trunk/txdav/caldav/datastore/scheduling.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/scheduling.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/txdav/caldav/datastore/scheduling.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -20,6 +20,7 @@
 
 from twisted.python.util import FancyEqMixin
 from twisted.python.components import proxyForInterface
+from twisted.internet.defer import inlineCallbacks, returnValue
 
 
 
@@ -39,16 +40,17 @@
         self._transaction = transaction
 
 
+    @inlineCallbacks
     def calendarHomeWithUID(self, uid, create=False):
         # FIXME: 'create' flag
-        newHome = super(ImplicitTransaction, self
+        newHome = yield super(ImplicitTransaction, self
             ).calendarHomeWithUID(uid, create)
 #        return ImplicitCalendarHome(newHome, self)
         if newHome is None:
-            return None
+            returnValue(None)
         else:
             # FIXME: relay transaction
-            return ImplicitCalendarHome(newHome, None)
+            returnValue(ImplicitCalendarHome(newHome, None))
 
 
 
@@ -71,23 +73,30 @@
 #        # FIXME: wrap?
 #        return self._calendarHome.properties()
 
+    @inlineCallbacks
     def calendars(self):
-        for calendar in super(ImplicitCalendarHome, self).calendars():
-            yield ImplicitCalendar(self, calendar)
+        superCalendars = (yield super(ImplicitCalendarHome, self).calendars())
+        wrapped = []
+        for calendar in superCalendars:
+            wrapped.append(ImplicitCalendar(self, calendar))
+        returnValue(wrapped)
 
+
     def createCalendarWithName(self, name):
         self._calendarHome.createCalendarWithName(name)
 
+
     def removeCalendarWithName(self, name):
         self._calendarHome.removeCalendarWithName(name)
 
 
+    @inlineCallbacks
     def calendarWithName(self, name):
-        calendar = self._calendarHome.calendarWithName(name)
+        calendar = yield self._calendarHome.calendarWithName(name)
         if calendar is not None:
-            return ImplicitCalendar(self, calendar)
+            returnValue(ImplicitCalendar(self, calendar))
         else:
-            return None
+            returnValue(None)
 
 
 

Modified: CalendarServer/trunk/txdav/caldav/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/sql.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/txdav/caldav/datastore/sql.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -15,6 +15,10 @@
 # limitations under the License.
 ##
 
+"""
+SQL backend for CalDAV storage.
+"""
+
 __all__ = [
     "CalendarHome",
     "Calendar",
@@ -25,6 +29,7 @@
 from twext.web2.dav.element.rfc2518 import ResourceType
 from twext.web2.http_headers import MimeType, generateContentType
 
+from twisted.internet.defer import inlineCallbacks, returnValue
 from twisted.internet.error import ConnectionLost
 from twisted.internet.interfaces import ITransport
 from twisted.python import hashlib
@@ -33,7 +38,7 @@
 from twistedcaldav import caldavxml, customxml
 from twistedcaldav.caldavxml import ScheduleCalendarTransp, Opaque
 from twistedcaldav.dateops import normalizeForIndex, datetimeMktime
-from twistedcaldav.index import IndexedSearchException
+from txdav.common.icommondatastore import IndexedSearchException
 from twistedcaldav.instance import InvalidOverriddenInstanceError
 
 from txdav.caldav.datastore.util import validateCalendarComponent,\
@@ -45,7 +50,7 @@
     CommonObjectResource
 from txdav.common.datastore.sql_legacy import \
     PostgresLegacyIndexEmulator, SQLLegacyCalendarInvites,\
-    SQLLegacyCalendarShares
+    SQLLegacyCalendarShares, PostgresLegacyInboxIndexEmulator
 from txdav.common.datastore.sql_tables import CALENDAR_TABLE,\
     CALENDAR_BIND_TABLE, CALENDAR_OBJECT_REVISIONS_TABLE, CALENDAR_OBJECT_TABLE,\
     _ATTACHMENTS_MODE_WRITE
@@ -77,24 +82,30 @@
     calendars = CommonHome.children
     listCalendars = CommonHome.listChildren
 
+
+    @inlineCallbacks
     def calendarObjectWithDropboxID(self, dropboxID):
         """
         Implement lookup with brute-force scanning.
         """
-        for calendar in self.calendars():
-            for calendarObject in calendar.calendarObjects():
-                if dropboxID == calendarObject.dropboxID():
-                    return calendarObject
+        for calendar in (yield self.calendars()):
+            for calendarObject in (yield calendar.calendarObjects()):
+                dbid = yield calendarObject.dropboxID()
+                if dropboxID == dbid:
+                    returnValue(calendarObject)
 
 
+    @inlineCallbacks
     def createdHome(self):
-        self.createCalendarWithName("calendar")
-        defaultCal = self.calendarWithName("calendar")
+        yield self.createCalendarWithName("calendar")
+        defaultCal = yield self.calendarWithName("calendar")
         props = defaultCal.properties()
         props[PropertyName(*ScheduleCalendarTransp.qname())] = ScheduleCalendarTransp(
             Opaque())
-        self.createCalendarWithName("inbox")
+        yield self.createCalendarWithName("inbox")
 
+
+
 class Calendar(CommonHomeChild):
     """
     File-based implementation of L{ICalendar}.
@@ -103,22 +114,20 @@
 
     def __init__(self, home, name, resourceID, notifier):
         """
-        Initialize a calendar pointing at a path on disk.
+        Initialize a calendar pointing at a record in a database.
 
-        @param name: the subdirectory of calendarHome where this calendar
-            resides.
+        @param name: the name of the calendar resource.
         @type name: C{str}
 
-        @param calendarHome: the home containing this calendar.
-        @type calendarHome: L{CalendarHome}
-
-        @param realName: If this calendar was just created, the name which it
-        will eventually have on disk.
-        @type realName: C{str}
+        @param home: the home containing this calendar.
+        @type home: L{CalendarHome}
         """
         super(Calendar, self).__init__(home, name, resourceID, notifier)
 
-        self._index = PostgresLegacyIndexEmulator(self)
+        if name == 'inbox':
+            self._index = PostgresLegacyInboxIndexEmulator(self)
+        else:
+            self._index = PostgresLegacyIndexEmulator(self)
         self._invites = SQLLegacyCalendarInvites(self)
         self._objectResourceClass = CalendarObject
         self._bindTable = CALENDAR_BIND_TABLE
@@ -213,32 +222,36 @@
 def _pathToName(path):
     return path.rsplit(".", 1)[0]
 
+
+
 class CalendarObject(CommonObjectResource):
     implements(ICalendarObject)
 
-    def __init__(self, name, calendar, resid):
-        super(CalendarObject, self).__init__(name, calendar, resid)
+    _objectTable = CALENDAR_OBJECT_TABLE
 
-        self._objectTable = CALENDAR_OBJECT_TABLE
-
     @property
     def _calendar(self):
         return self._parentCollection
 
+
     def calendar(self):
         return self._calendar
 
+
+    @inlineCallbacks
     def setComponent(self, component, inserting=False):
         validateCalendarComponent(self, self._calendar, component, inserting)
 
-        self.updateDatabase(component, inserting=inserting)
+        yield self.updateDatabase(component, inserting=inserting)
         if inserting:
-            self._calendar._insertRevision(self._name)
+            yield self._calendar._insertRevision(self._name)
         else:
-            self._calendar._updateRevision(self._name)
+            yield self._calendar._updateRevision(self._name)
 
         self._calendar.notifyChanged()
 
+
+    @inlineCallbacks
     def updateDatabase(self, component, expand_until=None, reCreate=False, inserting=False):
         """
         Update the database tables for the new data being written.
@@ -282,7 +295,7 @@
 
         # CALENDAR_OBJECT table update
         if inserting:
-            self._resourceID = self._txn.execSQL(
+            self._resourceID = (yield self._txn.execSQL(
                 """
                 insert into CALENDAR_OBJECT
                 (CALENDAR_RESOURCE_ID, RESOURCE_NAME, ICALENDAR_TEXT, ICALENDAR_UID, ICALENDAR_TYPE, ATTACHMENTS_MODE, ORGANIZER, RECURRANCE_MAX)
@@ -302,9 +315,9 @@
                     organizer,
                     normalizeForIndex(instances.limit) if instances.limit else None,
                 ]
-            )[0][0]
+            ))[0][0]
         else:
-            self._txn.execSQL(
+            yield self._txn.execSQL(
                 """
                 update CALENDAR_OBJECT set
                 (ICALENDAR_TEXT, ICALENDAR_UID, ICALENDAR_TYPE, ATTACHMENTS_MODE, ORGANIZER, RECURRANCE_MAX, MODIFIED)
@@ -327,7 +340,7 @@
             )
 
             # Need to wipe the existing time-range for this and rebuild
-            self._txn.execSQL(
+            yield self._txn.execSQL(
                 """
                 delete from TIME_RANGE where CALENDAR_OBJECT_RESOURCE_ID = %s
                 """,
@@ -335,6 +348,7 @@
                     self._resourceID,
                 ],
             )
+        self._uid = component.resourceUID()
 
 
         # CALENDAR_OBJECT table update
@@ -344,7 +358,7 @@
             end = instance.end.replace(tzinfo=utc)
             float = instance.start.tzinfo is None
             transp = instance.component.propertyValue("TRANSP") == "TRANSPARENT"
-            instanceid = self._txn.execSQL(
+            instanceid = (yield self._txn.execSQL(
                 """
                 insert into TIME_RANGE
                 (CALENDAR_RESOURCE_ID, CALENDAR_OBJECT_RESOURCE_ID, FLOATING, START_DATE, END_DATE, FBTYPE, TRANSPARENT)
@@ -362,10 +376,10 @@
                     icalfbtype_to_indexfbtype.get(instance.component.getFBType(), icalfbtype_to_indexfbtype["FREE"]),
                     transp,
                 ],
-            )[0][0]
+            ))[0][0]
             peruserdata = component.perUserTransparency(instance.rid)
             for useruid, transp in peruserdata:
-                self._txn.execSQL(
+                yield self._txn.execSQL(
                     """
                     insert into TRANSPARENCY
                     (TIME_RANGE_INSTANCE_ID, USER_ID, TRANSPARENT)
@@ -385,7 +399,7 @@
             start = datetime.datetime(2100, 1, 1, 0, 0, 0, tzinfo=utc)
             end = datetime.datetime(2100, 1, 1, 1, 0, 0, tzinfo=utc)
             float = False
-            instanceid = self._txn.execSQL(
+            instanceid = (yield self._txn.execSQL(
                 """
                 insert into TIME_RANGE
                 (CALENDAR_RESOURCE_ID, CALENDAR_OBJECT_RESOURCE_ID, FLOATING, START_DATE, END_DATE, FBTYPE, TRANSPARENT)
@@ -403,10 +417,10 @@
                     icalfbtype_to_indexfbtype["UNKNOWN"],
                     True,
                 ],
-            )[0][0]
+            ))[0][0]
             peruserdata = component.perUserTransparency(None)
             for useruid, transp in peruserdata:
-                self._txn.execSQL(
+                yield self._txn.execSQL(
                     """
                     insert into TRANSPARENCY
                     (TIME_RANGE_INSTANCE_ID, USER_ID, TRANSPARENT)
@@ -420,34 +434,21 @@
                     ],
                 )
 
+
+    @inlineCallbacks
     def component(self):
-        return VComponent.fromString(self.iCalendarText())
+        returnValue(VComponent.fromString((yield self.iCalendarText())))
 
-    def text(self):
-        if self._objectText is None:
-            text = self._txn.execSQL(
-                "select ICALENDAR_TEXT from CALENDAR_OBJECT where "
-                "RESOURCE_ID = %s", [self._resourceID]
-            )[0][0]
-            self._objectText = text
-            return text
-        else:
-            return self._objectText
 
-    iCalendarText = text
+    iCalendarText = CommonObjectResource.text
 
-    def uid(self):
-        return self.component().resourceUID()
 
-    def name(self):
-        return self._name
+    @inlineCallbacks
+    def organizer(self):
+        returnValue((yield self.component()).getOrganizer())
 
-    def componentType(self):
-        return self.component().mainType()
 
-    def organizer(self):
-        return self.component().getOrganizer()
-
+    @inlineCallbacks
     def createAttachmentWithName(self, name, contentType):
 
         try:
@@ -456,56 +457,70 @@
             pass
 
         attachment = Attachment(self, name)
-        self._txn.execSQL("""
+        yield self._txn.execSQL(
+            """
             insert into ATTACHMENT (CALENDAR_OBJECT_RESOURCE_ID, CONTENT_TYPE,
-            SIZE, MD5, PATH)
-            values (%s, %s, %s, %s, %s)
+            SIZE, MD5, PATH) values (%s, %s, %s, %s, %s)
             """,
             [
-                self._resourceID,
-                generateContentType(contentType),
-                0,
-                "",
+                self._resourceID, generateContentType(contentType), 0, "",
                 name,
             ]
         )
-        return attachment.store(contentType)
+        returnValue(attachment.store(contentType))
 
+
+    @inlineCallbacks
     def removeAttachmentWithName(self, name):
         attachment = Attachment(self, name)
         self._txn.postCommit(attachment._path.remove)
-        self._txn.execSQL("""
-        delete from ATTACHMENT where CALENDAR_OBJECT_RESOURCE_ID = %s AND
-        PATH = %s
-        """, [self._resourceID, name])
+        yield self._txn.execSQL(
+            """
+            delete from ATTACHMENT where CALENDAR_OBJECT_RESOURCE_ID = %s AND
+            PATH = %s
+            """, [self._resourceID, name]
+        )
 
+
+    @inlineCallbacks
     def attachmentWithName(self, name):
         attachment = Attachment(self, name)
-        if attachment._populate():
-            return attachment
+        if (yield attachment._populate()):
+            returnValue(attachment)
         else:
-            return None
+            returnValue(None)
 
+
+    @inlineCallbacks
     def attendeesCanManageAttachments(self):
-        return self.component().hasPropertyInAnyComponent("X-APPLE-DROPBOX")
+        returnValue((yield self.component()).hasPropertyInAnyComponent(
+            "X-APPLE-DROPBOX"
+        ))
 
-    def dropboxID(self):
-        return dropboxIDFromCalendarObject(self)
 
+    dropboxID = dropboxIDFromCalendarObject
+
+
     def _attachmentPathRoot(self):
         attachmentRoot = self._txn._store.attachmentsPath
         
         # Use directory hashing scheme based on owner user id
         homeName = self._calendar.ownerHome().name()
         return attachmentRoot.child(homeName[0:2]).child(homeName[2:4]).child(homeName).child(self.uid())
-        
+
+
+    @inlineCallbacks
     def attachments(self):
-        rows = self._txn.execSQL("""
-        select PATH from ATTACHMENT where CALENDAR_OBJECT_RESOURCE_ID = %s 
-        """, [self._resourceID])
+        rows = yield self._txn.execSQL(
+            """
+            select PATH from ATTACHMENT where CALENDAR_OBJECT_RESOURCE_ID = %s
+            """, [self._resourceID])
+        result = []
         for row in rows:
-            yield self.attachmentWithName(row[0])
+            result.append((yield self.attachmentWithName(row[0])))
+        returnValue(result)
 
+
     def initPropertyStore(self, props):
         # Setup peruser special properties
         props.setSpecialProperties(
@@ -551,15 +566,21 @@
         self.hash.update(data)
 
 
+    @inlineCallbacks
     def loseConnection(self):
         self.attachment._path.setContent(self.buf)
         contentTypeString = generateContentType(self.contentType)
-        self._txn.execSQL(
-            "update ATTACHMENT set CONTENT_TYPE = %s, SIZE = %s, MD5 = %s, MODIFIED = timezone('UTC', CURRENT_TIMESTAMP) "
-            "WHERE PATH = %s",
-            [contentTypeString, len(self.buf), self.hash.hexdigest(), self.attachment.name()]
+        yield self._txn.execSQL(
+            """
+            update ATTACHMENT set CONTENT_TYPE = %s, SIZE = %s, MD5 = %s,
+            MODIFIED = timezone('UTC', CURRENT_TIMESTAMP) WHERE PATH = %s
+            """,
+            [contentTypeString, len(self.buf),
+             self.hash.hexdigest(), self.attachment.name()]
         )
 
+
+
 class Attachment(object):
 
     implements(IAttachment)
@@ -574,24 +595,27 @@
         return self._calendarObject._txn
 
 
+    @inlineCallbacks
     def _populate(self):
         """
         Execute necessary SQL queries to retrieve attributes.
 
         @return: C{True} if this attachment exists, C{False} otherwise.
         """
-        rows = self._txn.execSQL(
+        rows = yield self._txn.execSQL(
             """
             select CONTENT_TYPE, SIZE, MD5, CREATED, MODIFIED from ATTACHMENT where PATH = %s
-            """, [self._name])
+            """,
+            [self._name]
+        )
         if not rows:
-            return False
+            returnValue(False)
         self._contentType = MimeType.fromString(rows[0][0])
         self._size = rows[0][1]
         self._md5 = rows[0][2]
         self._created = datetimeMktime(datetime.datetime.strptime(rows[0][3], "%Y-%m-%d %H:%M:%S.%f"))
         self._modified = datetimeMktime(datetime.datetime.strptime(rows[0][4], "%Y-%m-%d %H:%M:%S.%f"))
-        return True
+        returnValue(True)
 
 
     def name(self):

Modified: CalendarServer/trunk/txdav/caldav/datastore/test/common.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/test/common.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/txdav/caldav/datastore/test/common.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -19,11 +19,8 @@
 Tests for common calendar store API functions.
 """
 
-from zope.interface.verify import verifyObject
-from zope.interface.exceptions import (
-    BrokenMethodImplementation, DoesNotImplement)
-
-from twisted.internet.defer import Deferred, inlineCallbacks
+from twisted.internet.defer import Deferred, inlineCallbacks, returnValue,\
+    maybeDeferred
 from twisted.internet.protocol import Protocol
 
 from txdav.idav import IPropertyStore, IDataStore, AlreadyFinishedError
@@ -36,6 +33,7 @@
 from txdav.common.icommondatastore import NoSuchObjectResourceError
 from txdav.common.icommondatastore import ObjectResourceNameAlreadyExistsError
 from txdav.common.inotifications import INotificationObject
+from txdav.common.datastore.test.util import CommonCommonTests
 
 from txdav.caldav.icalendarstore import (
     ICalendarObject, ICalendarHome,
@@ -47,7 +45,6 @@
 from twext.web2.dav.element.base import WebDAVUnknownElement
 from twext.python.vcomponent import VComponent
 
-from twistedcaldav.notify import Notifier
 from twistedcaldav.customxml import InviteNotification, InviteSummary
 
 storePath = FilePath(__file__).parent().child("calendar_store")
@@ -146,24 +143,8 @@
 
 
 
-def assertProvides(testCase, interface, provider):
+class CommonTests(CommonCommonTests):
     """
-    Verify that C{provider} properly provides C{interface}
-
-    @type interface: L{zope.interface.Interface}
-    @type provider: C{provider}
-    """
-    try:
-        verifyObject(interface, provider)
-    except BrokenMethodImplementation, e:
-        testCase.fail(e)
-    except DoesNotImplement, e:
-        testCase.fail("%r does not provide %s.%s" %
-                      (provider, interface.__module__, interface.getName()))
-
-
-class CommonTests(object):
-    """
     Tests for common functionality of interfaces defined in
     L{txdav.caldav.icalendarstore}.
     """
@@ -194,74 +175,37 @@
         raise NotImplementedError()
 
 
-    lastTransaction = None
-    savedStore = None
-
-    def transactionUnderTest(self):
-        """
-        Create a transaction from C{storeUnderTest} and save it as
-        C[lastTransaction}.  Also makes sure to use the same store, saving the
-        value from C{storeUnderTest}.
-        """
-        if self.lastTransaction is not None:
-            return self.lastTransaction
-        if self.savedStore is None:
-            self.savedStore = self.storeUnderTest()
-        txn = self.lastTransaction = self.savedStore.newTransaction(self.id())
-        return txn
-
-
-    def commit(self):
-        """
-        Commit the last transaction created from C{transactionUnderTest}, and
-        clear it.
-        """
-        self.lastTransaction.commit()
-        self.lastTransaction = None
-
-
-    def abort(self):
-        """
-        Abort the last transaction created from C[transactionUnderTest}, and
-        clear it.
-        """
-        self.lastTransaction.abort()
-        self.lastTransaction = None
-
-
-    def setUp(self):
-        self.notifierFactory = StubNotifierFactory()
-
-
-    def tearDown(self):
-        if self.lastTransaction is not None:
-            self.commit()
-
-
+    @inlineCallbacks
     def homeUnderTest(self):
         """
         Get the calendar home detailed by C{requirements['home1']}.
         """
-        return self.transactionUnderTest().calendarHomeWithUID("home1")
+        returnValue(
+            (yield self.transactionUnderTest().calendarHomeWithUID("home1"))
+        )
 
 
+    @inlineCallbacks
     def calendarUnderTest(self):
         """
         Get the calendar detailed by C{requirements['home1']['calendar_1']}.
         """
-        return self.homeUnderTest().calendarWithName("calendar_1")
+        returnValue((yield
+            (yield self.homeUnderTest()).calendarWithName("calendar_1"))
+        )
 
 
+    @inlineCallbacks
     def calendarObjectUnderTest(self):
         """
         Get the calendar detailed by
         C{requirements['home1']['calendar_1']['1.ics']}.
         """
-        return self.calendarUnderTest().calendarObjectWithName("1.ics")
+        returnValue(
+            (yield (yield self.calendarUnderTest())
+                .calendarObjectWithName("1.ics")))
 
 
-    assertProvides = assertProvides
-
     def test_calendarStoreProvides(self):
         """
         The calendar store provides L{IDataStore} and its required attributes.
@@ -281,225 +225,251 @@
         self.assertProvides(ICalendarTransaction, txn)
 
 
+    @inlineCallbacks
     def test_homeProvides(self):
         """
         The calendar homes generated by the calendar store provide
         L{ICalendarHome} and its required attributes.
         """
-        self.assertProvides(ICalendarHome, self.homeUnderTest())
+        self.assertProvides(ICalendarHome, (yield self.homeUnderTest()))
 
 
+    @inlineCallbacks
     def test_calendarProvides(self):
         """
         The calendars generated by the calendar store provide L{ICalendar} and
         its required attributes.
         """
-        self.assertProvides(ICalendar, self.calendarUnderTest())
+        self.assertProvides(ICalendar, (yield self.calendarUnderTest()))
 
 
+    @inlineCallbacks
     def test_calendarObjectProvides(self):
         """
         The calendar objects generated by the calendar store provide
         L{ICalendarObject} and its required attributes.
         """
-        self.assertProvides(ICalendarObject, self.calendarObjectUnderTest())
+        self.assertProvides(
+            ICalendarObject, (yield self.calendarObjectUnderTest())
+        )
 
 
+    @inlineCallbacks
     def notificationUnderTest(self):
         txn = self.transactionUnderTest()
-        notifications = txn.notificationsWithUID("home1")
+        notifications = yield txn.notificationsWithUID("home1")
         inviteNotification = InviteNotification()
-        notifications.writeNotificationObject("abc", inviteNotification,
+        yield notifications.writeNotificationObject("abc", inviteNotification,
             inviteNotification.toxml())
-        notificationObject = notifications.notificationObjectWithUID("abc")
-        return notificationObject
+        notificationObject = yield notifications.notificationObjectWithUID("abc")
+        returnValue(notificationObject)
 
 
+    @inlineCallbacks
     def test_notificationObjectProvides(self):
         """
         The objects retrieved from the notification home (the object returned
         from L{notificationsWithUID}) provide L{INotificationObject}.
         """
-        notificationObject = self.notificationUnderTest()
+        notificationObject = yield self.notificationUnderTest()
         self.assertProvides(INotificationObject, notificationObject)
 
 
+    @inlineCallbacks
     def test_replaceNotification(self):
         """
         L{INotificationCollection.writeNotificationObject} will silently
         overwrite the notification object.
         """
-        notifications = self.transactionUnderTest().notificationsWithUID(
+        notifications = yield self.transactionUnderTest().notificationsWithUID(
             "home1"
         )
         inviteNotification = InviteNotification()
-        notifications.writeNotificationObject("abc", inviteNotification,
+        yield notifications.writeNotificationObject("abc", inviteNotification,
             inviteNotification.toxml())
         inviteNotification2 = InviteNotification(InviteSummary("a summary"))
-        notifications.writeNotificationObject(
+        yield notifications.writeNotificationObject(
             "abc", inviteNotification, inviteNotification2.toxml())
-        abc = notifications.notificationObjectWithUID("abc")
-        self.assertEquals(abc.xmldata(), inviteNotification2.toxml())
+        abc = yield notifications.notificationObjectWithUID("abc")
+        self.assertEquals((yield abc.xmldata()), inviteNotification2.toxml())
 
 
+    @inlineCallbacks
     def test_notificationObjectModified(self):
         """
         The objects retrieved from the notification home have a C{modified}
         method which returns the timestamp of their last modification.
         """
-        notification = self.notificationUnderTest()
-        self.assertIsInstance(notification.modified(), int)
+        notification = yield self.notificationUnderTest()
+        self.assertIsInstance((yield notification.modified()), int)
 
 
+    @inlineCallbacks
     def test_notificationObjectParent(self):
         """
         L{INotificationObject.notificationCollection} returns the
         L{INotificationCollection} that the object was retrieved from.
         """
         txn = self.transactionUnderTest()
-        collection = txn.notificationsWithUID("home1")
-        notification = self.notificationUnderTest()
+        collection = yield txn.notificationsWithUID("home1")
+        notification = yield self.notificationUnderTest()
         self.assertIdentical(collection, notification.notificationCollection())
 
 
+    @inlineCallbacks
     def test_notifierID(self):
-        home = self.homeUnderTest()
+        home = yield self.homeUnderTest()
         self.assertEquals(home.notifierID(), "CalDAV|home1")
-        calendar = home.calendarWithName("calendar_1")
+        calendar = yield home.calendarWithName("calendar_1")
         self.assertEquals(calendar.notifierID(), "CalDAV|home1")
         self.assertEquals(calendar.notifierID(label="collection"), "CalDAV|home1/calendar_1")
 
 
+    @inlineCallbacks
     def test_calendarHomeWithUID_exists(self):
         """
         Finding an existing calendar home by UID results in an object that
         provides L{ICalendarHome} and has a C{uid()} method that returns the
         same value that was passed in.
         """
-        calendarHome = (self.transactionUnderTest()
+        calendarHome = (yield self.transactionUnderTest()
                         .calendarHomeWithUID("home1"))
         self.assertEquals(calendarHome.uid(), "home1")
         self.assertProvides(ICalendarHome, calendarHome)
 
 
+    @inlineCallbacks
     def test_calendarHomeWithUID_absent(self):
         """
         L{ICommonStoreTransaction.calendarHomeWithUID} should return C{None}
         when asked for a non-existent calendar home.
         """
         txn = self.transactionUnderTest()
-        self.assertEquals(txn.calendarHomeWithUID("xyzzy"), None)
+        self.assertEquals((yield txn.calendarHomeWithUID("xyzzy")), None)
 
 
+    @inlineCallbacks
     def test_calendarWithName_exists(self):
         """
         L{ICalendarHome.calendarWithName} returns an L{ICalendar} provider,
         whose name matches the one passed in.
         """
-        home = self.homeUnderTest()
+        home = yield self.homeUnderTest()
         for name in home1_calendarNames:
-            calendar = home.calendarWithName(name)
+            calendar = yield home.calendarWithName(name)
             if calendar is None:
                 self.fail("calendar %r didn't exist" % (name,))
             self.assertProvides(ICalendar, calendar)
             self.assertEquals(calendar.name(), name)
 
 
+    @inlineCallbacks
     def test_calendarRename(self):
         """
         L{ICalendar.rename} changes the name of the L{ICalendar}.
         """
-        home = self.homeUnderTest()
-        calendar = home.calendarWithName("calendar_1")
-        calendar.rename("some_other_name")
+        home = yield self.homeUnderTest()
+        calendar = yield home.calendarWithName("calendar_1")
+        yield calendar.rename("some_other_name")
+        @inlineCallbacks
         def positiveAssertions():
             self.assertEquals(calendar.name(), "some_other_name")
-            self.assertEquals(calendar, home.calendarWithName("some_other_name"))
-            self.assertEquals(None, home.calendarWithName("calendar_1"))
-        positiveAssertions()
-        self.commit()
-        home = self.homeUnderTest()
-        calendar = home.calendarWithName("some_other_name")
-        positiveAssertions()
+            self.assertEquals(
+                calendar, (yield home.calendarWithName("some_other_name")))
+            self.assertEquals(
+                None, (yield home.calendarWithName("calendar_1")))
+        yield positiveAssertions()
+        yield self.commit()
+        home = yield self.homeUnderTest()
+        calendar = yield home.calendarWithName("some_other_name")
+        yield positiveAssertions()
         # FIXME: revert
         # FIXME: test for multiple renames
         # FIXME: test for conflicting renames (a->b, c->a in the same txn)
 
 
+    @inlineCallbacks
     def test_calendarWithName_absent(self):
         """
         L{ICalendarHome.calendarWithName} returns C{None} for calendars which
         do not exist.
         """
-        self.assertEquals(self.homeUnderTest().calendarWithName("xyzzy"),
-                          None)
+        home = yield self.homeUnderTest()
+        calendar = yield home.calendarWithName("xyzzy")
+        self.assertEquals(calendar, None)
 
 
+    @inlineCallbacks
     def test_createCalendarWithName_absent(self):
         """
         L{ICalendarHome.createCalendarWithName} creates a new L{ICalendar} that
         can be retrieved with L{ICalendarHome.calendarWithName}.
         """
-        home = self.homeUnderTest()
+        home = yield self.homeUnderTest()
         name = "new"
-        self.assertIdentical(home.calendarWithName(name), None)
-        home.createCalendarWithName(name)
-        self.assertNotIdentical(home.calendarWithName(name), None)
+        self.assertIdentical((yield home.calendarWithName(name)), None)
+        yield home.createCalendarWithName(name)
+        self.assertNotIdentical((yield home.calendarWithName(name)), None)
+        @inlineCallbacks
         def checkProperties():
-            calendarProperties = home.calendarWithName(name).properties()
+            calendarProperties = (
+                yield home.calendarWithName(name)).properties()
             self.assertEquals(
                 calendarProperties[
                     PropertyName.fromString(davxml.ResourceType.sname())
                 ],
                 davxml.ResourceType.calendar #@UndefinedVariable
             )
-        checkProperties()
+        yield checkProperties()
 
-        self.commit()
+        yield self.commit()
 
         # Make sure notification fired after commit
         self.assertEquals(self.notifierFactory.history,
             [("update", "CalDAV|home1")])
 
         # Make sure it's available in a new transaction; i.e. test the commit.
-        home = self.homeUnderTest()
-        self.assertNotIdentical(home.calendarWithName(name), None)
+        home = yield self.homeUnderTest()
+        self.assertNotIdentical((yield home.calendarWithName(name)), None)
 
         # Sanity check: are the properties actually persisted?  Check in
         # subsequent transaction.
-        checkProperties()
+        yield checkProperties()
 
         # FIXME: no independent testing of the property store's persistence
         # right now
 
 
+    @inlineCallbacks
     def test_createCalendarWithName_exists(self):
         """
         L{ICalendarHome.createCalendarWithName} raises
         L{CalendarAlreadyExistsError} when the name conflicts with an already-
         existing 
         """
+        home = yield self.homeUnderTest()
         for name in home1_calendarNames:
-            self.assertRaises(
-                HomeChildNameAlreadyExistsError,
-                self.homeUnderTest().createCalendarWithName, name
+            yield self.failUnlessFailure(
+                maybeDeferred(home.createCalendarWithName, name),
+                HomeChildNameAlreadyExistsError
             )
 
 
+    @inlineCallbacks
     def test_removeCalendarWithName_exists(self):
         """
         L{ICalendarHome.removeCalendarWithName} removes a calendar that already
         exists.
         """
-        home = self.homeUnderTest()
+        home = yield self.homeUnderTest()
 
         # FIXME: test transactions
         for name in home1_calendarNames:
-            self.assertNotIdentical(home.calendarWithName(name), None)
-            home.removeCalendarWithName(name)
-            self.assertEquals(home.calendarWithName(name), None)
+            self.assertNotIdentical((yield home.calendarWithName(name)), None)
+            yield home.removeCalendarWithName(name)
+            self.assertEquals((yield home.calendarWithName(name)), None)
 
-        self.commit()
+        yield self.commit()
 
         # Make sure notification fired after commit
         self.assertEquals(
@@ -515,27 +485,31 @@
         )
 
 
+    @inlineCallbacks
     def test_removeCalendarWithName_absent(self):
         """
         Attempt to remove an non-existing calendar should raise.
         """
-        home = self.homeUnderTest()
-        self.assertRaises(NoSuchHomeChildError,
-                          home.removeCalendarWithName, "xyzzy")
+        home = yield self.homeUnderTest()
+        yield self.failUnlessFailure(
+            maybeDeferred(home.removeCalendarWithName, "xyzzy"),
+            NoSuchHomeChildError
+        )
 
 
+    @inlineCallbacks
     def test_calendarObjects(self):
         """
         L{ICalendar.calendarObjects} will enumerate the calendar objects present
         in the filesystem, in name order, but skip those with hidden names.
         """
-        calendar1 = self.calendarUnderTest()
-        calendarObjects = list(calendar1.calendarObjects())
+        calendar1 = yield self.calendarUnderTest()
+        calendarObjects = list((yield calendar1.calendarObjects()))
 
         for calendarObject in calendarObjects:
             self.assertProvides(ICalendarObject, calendarObject)
             self.assertEquals(
-                calendar1.calendarObjectWithName(calendarObject.name()),
+                (yield calendar1.calendarObjectWithName(calendarObject.name())),
                 calendarObject
             )
 
@@ -545,72 +519,77 @@
         )
 
 
+    @inlineCallbacks
     def test_calendarObjectsWithRemovedObject(self):
         """
         L{ICalendar.calendarObjects} skips those objects which have been
         removed by L{Calendar.removeCalendarObjectWithName} in the same
         transaction, even if it has not yet been committed.
         """
-        calendar1 = self.calendarUnderTest()
+        calendar1 = yield self.calendarUnderTest()
         calendar1.removeCalendarObjectWithName("2.ics")
-        calendarObjects = list(calendar1.calendarObjects())
+        calendarObjects = list((yield calendar1.calendarObjects()))
         self.assertEquals(set(o.name() for o in calendarObjects),
                           set(calendar1_objectNames) - set(["2.ics"]))
 
 
+    @inlineCallbacks
     def test_ownerCalendarHome(self):
         """
         L{ICalendar.ownerCalendarHome} should match the home UID.
         """
         self.assertEquals(
-            self.calendarUnderTest().ownerCalendarHome().uid(),
-            self.homeUnderTest().uid()
+            (yield self.calendarUnderTest()).ownerCalendarHome().uid(),
+            (yield self.homeUnderTest()).uid()
         )
 
 
+    @inlineCallbacks
     def test_calendarObjectWithName_exists(self):
         """
         L{ICalendar.calendarObjectWithName} returns an L{ICalendarObject}
         provider for calendars which already exist.
         """
-        calendar1 = self.calendarUnderTest()
+        calendar1 = yield self.calendarUnderTest()
         for name in calendar1_objectNames:
-            calendarObject = calendar1.calendarObjectWithName(name)
+            calendarObject = yield calendar1.calendarObjectWithName(name)
             self.assertProvides(ICalendarObject, calendarObject)
             self.assertEquals(calendarObject.name(), name)
             # FIXME: add more tests based on CommonTests.requirements
 
 
+    @inlineCallbacks
     def test_calendarObjectWithName_absent(self):
         """
         L{ICalendar.calendarObjectWithName} returns C{None} for calendars which
         don't exist.
         """
-        calendar1 = self.calendarUnderTest()
-        self.assertEquals(calendar1.calendarObjectWithName("xyzzy"), None)
+        calendar1 = yield self.calendarUnderTest()
+        self.assertEquals((yield calendar1.calendarObjectWithName("xyzzy")), None)
 
 
+    @inlineCallbacks
     def test_removeCalendarObjectWithUID_exists(self):
         """
         Remove an existing calendar object.
         """
-        calendar = self.calendarUnderTest()
+        calendar = yield self.calendarUnderTest()
         for name in calendar1_objectNames:
             uid = (u'uid' + name.rstrip(".ics"))
-            self.assertNotIdentical(calendar.calendarObjectWithUID(uid),
+            self.assertNotIdentical((yield calendar.calendarObjectWithUID(uid)),
                                     None)
-            calendar.removeCalendarObjectWithUID(uid)
+            yield calendar.removeCalendarObjectWithUID(uid)
             self.assertEquals(
-                calendar.calendarObjectWithUID(uid),
+                (yield calendar.calendarObjectWithUID(uid)),
                 None
             )
             self.assertEquals(
-                calendar.calendarObjectWithName(name),
+                (yield calendar.calendarObjectWithName(name)),
                 None
             )
 
         # Make sure notifications are fired after commit
-        self.commit()
+        yield self.commit()
         self.assertEquals(
             self.notifierFactory.history,
             [
@@ -623,52 +602,60 @@
             ]
         )
 
+    @inlineCallbacks
     def test_removeCalendarObjectWithName_exists(self):
         """
         Remove an existing calendar object.
         """
-        calendar = self.calendarUnderTest()
+        calendar = yield self.calendarUnderTest()
         for name in calendar1_objectNames:
             self.assertNotIdentical(
-                calendar.calendarObjectWithName(name), None
+                (yield calendar.calendarObjectWithName(name)), None
             )
-            calendar.removeCalendarObjectWithName(name)
+            yield calendar.removeCalendarObjectWithName(name)
             self.assertIdentical(
-                calendar.calendarObjectWithName(name), None
+                (yield calendar.calendarObjectWithName(name)), None
             )
 
 
+    @inlineCallbacks
     def test_removeCalendarObjectWithName_absent(self):
         """
         Attempt to remove an non-existing calendar object should raise.
         """
-        calendar = self.calendarUnderTest()
-        self.assertRaises(
-            NoSuchObjectResourceError,
-            calendar.removeCalendarObjectWithName, "xyzzy"
+        calendar = yield self.calendarUnderTest()
+        yield self.failUnlessFailure(
+            maybeDeferred(calendar.removeCalendarObjectWithName, "xyzzy"),
+            NoSuchObjectResourceError
         )
 
 
+    @inlineCallbacks
     def test_calendarName(self):
         """
         L{Calendar.name} reflects the name of the calendar.
         """
-        self.assertEquals(self.calendarUnderTest().name(), "calendar_1")
+        self.assertEquals((yield self.calendarUnderTest()).name(), "calendar_1")
 
 
+    @inlineCallbacks
     def test_calendarObjectName(self):
         """
         L{ICalendarObject.name} reflects the name of the calendar object.
         """
-        self.assertEquals(self.calendarObjectUnderTest().name(), "1.ics")
+        self.assertEquals(
+            (yield self.calendarObjectUnderTest()).name(),
+            "1.ics"
+        )
 
 
+    @inlineCallbacks
     def test_component(self):
         """
         L{ICalendarObject.component} returns a L{VComponent} describing the
         calendar data underlying that calendar object.
         """
-        component = self.calendarObjectUnderTest().component()
+        component = yield (yield self.calendarObjectUnderTest()).component()
 
         self.failUnless(
             isinstance(component, VComponent),
@@ -680,24 +667,28 @@
         self.assertEquals(component.resourceUID(), "uid1")
 
 
+    @inlineCallbacks
     def test_iCalendarText(self):
         """
         L{ICalendarObject.iCalendarText} returns a C{str} describing the same
         data provided by L{ICalendarObject.component}.
         """
-        text = self.calendarObjectUnderTest().iCalendarText()
+        text = yield (yield self.calendarObjectUnderTest()).iCalendarText()
         self.assertIsInstance(text, str)
         self.failUnless(text.startswith("BEGIN:VCALENDAR\r\n"))
         self.assertIn("\r\nUID:uid1\r\n", text)
         self.failUnless(text.endswith("\r\nEND:VCALENDAR\r\n"))
 
 
+    @inlineCallbacks
     def test_calendarObjectUID(self):
         """
         L{ICalendarObject.uid} returns a C{str} describing the C{UID} property
         of the calendar object's component.
         """
-        self.assertEquals(self.calendarObjectUnderTest().uid(), "uid1")
+        self.assertEquals(
+            (yield self.calendarObjectUnderTest()).uid(), "uid1"
+        )
 
 
     def test_organizer(self):
@@ -707,20 +698,23 @@
         component.
         """
         self.assertEquals(
-            self.calendarObjectUnderTest().organizer(),
+            (yield self.calendarObjectUnderTest()).organizer(),
             "mailto:wsanchez at apple.com"
         )
 
 
+    @inlineCallbacks
     def test_calendarObjectWithUID_absent(self):
         """
         L{ICalendar.calendarObjectWithUID} returns C{None} for calendars which
         don't exist.
         """
-        calendar1 = self.calendarUnderTest()
-        self.assertEquals(calendar1.calendarObjectWithUID("xyzzy"), None)
+        calendar1 = yield self.calendarUnderTest()
+        self.assertEquals((yield calendar1.calendarObjectWithUID("xyzzy")),
+                          None)
 
 
+    @inlineCallbacks
     def test_calendars(self):
         """
         L{ICalendarHome.calendars} returns an iterable of L{ICalendar}
@@ -729,13 +723,13 @@
         """
         # Add a dot directory to make sure we don't find it
         # self.home1._path.child(".foo").createDirectory()
-        home = self.homeUnderTest()
-        calendars = list(home.calendars())
+        home = yield self.homeUnderTest()
+        calendars = list((yield home.calendars()))
 
         for calendar in calendars:
             self.assertProvides(ICalendar, calendar)
             self.assertEquals(calendar,
-                              home.calendarWithName(calendar.name()))
+                              (yield home.calendarWithName(calendar.name())))
 
         self.assertEquals(
             set(c.name() for c in calendars),
@@ -743,33 +737,39 @@
         )
 
 
+    @inlineCallbacks
     def test_calendarsAfterAddCalendar(self):
         """
         L{ICalendarHome.calendars} includes calendars recently added with
         L{ICalendarHome.createCalendarWithName}.
         """
-        home = self.homeUnderTest()
-        before = set(x.name() for x in home.calendars())
-        home.createCalendarWithName("new-name")
-        after = set(x.name() for x in home.calendars())
+        home = yield self.homeUnderTest()
+        allCalendars = yield home.calendars()
+        before = set(x.name() for x in allCalendars)
+        yield home.createCalendarWithName("new-name")
+        allCalendars = yield home.calendars()
+        after = set(x.name() for x in allCalendars)
         self.assertEquals(before | set(['new-name']), after)
 
 
+    @inlineCallbacks
     def test_createCalendarObjectWithName_absent(self):
         """
         L{ICalendar.createCalendarObjectWithName} creates a new
         L{ICalendarObject}.
         """
-        calendar1 = self.calendarUnderTest()
+        calendar1 = yield self.calendarUnderTest()
         name = "4.ics"
-        self.assertIdentical(calendar1.calendarObjectWithName(name), None)
+        self.assertIdentical(
+            (yield calendar1.calendarObjectWithName(name)), None
+        )
         component = VComponent.fromString(event4_text)
-        calendar1.createCalendarObjectWithName(name, component)
+        yield calendar1.createCalendarObjectWithName(name, component)
 
-        calendarObject = calendar1.calendarObjectWithName(name)
-        self.assertEquals(calendarObject.component(), component)
+        calendarObject = yield calendar1.calendarObjectWithName(name)
+        self.assertEquals((yield calendarObject.component()), component)
 
-        self.commit()
+        yield self.commit()
 
         # Make sure notifications fire after commit
         self.assertEquals(
@@ -781,61 +781,64 @@
         )
 
 
+    @inlineCallbacks
     def test_createCalendarObjectWithName_exists(self):
         """
         L{ICalendar.createCalendarObjectWithName} raises
         L{CalendarObjectNameAlreadyExistsError} if a calendar object with the
         given name already exists in that calendar.
         """
-        cal = self.calendarUnderTest()
+        cal = yield self.calendarUnderTest()
         comp = VComponent.fromString(event4_text)
-        self.assertRaises(
+        yield self.failUnlessFailure(
+            maybeDeferred(cal.createCalendarObjectWithName, "1.ics", comp),
             ObjectResourceNameAlreadyExistsError,
-            cal.createCalendarObjectWithName,
-            "1.ics", comp
         )
 
 
+    @inlineCallbacks
     def test_createCalendarObjectWithName_invalid(self):
         """
         L{ICalendar.createCalendarObjectWithName} raises
         L{InvalidCalendarComponentError} if presented with invalid iCalendar
         text.
         """
-        self.assertRaises(
+        yield self.failUnlessFailure(
+            maybeDeferred((yield self.calendarUnderTest()).createCalendarObjectWithName,
+            "new", VComponent.fromString(event4notCalDAV_text)),
             InvalidObjectResourceError,
-            self.calendarUnderTest().createCalendarObjectWithName,
-            "new", VComponent.fromString(event4notCalDAV_text)
         )
 
-
+    @inlineCallbacks
     def test_setComponent_invalid(self):
         """
         L{ICalendarObject.setComponent} raises L{InvalidICalendarDataError} if
         presented with invalid iCalendar text.
         """
-        calendarObject = self.calendarObjectUnderTest()
-        self.assertRaises(
+        calendarObject = yield self.calendarObjectUnderTest()
+        yield self.failUnlessFailure(
+            maybeDeferred(calendarObject.setComponent,
+                          VComponent.fromString(event4notCalDAV_text)),
             InvalidObjectResourceError,
-            calendarObject.setComponent,
-            VComponent.fromString(event4notCalDAV_text)
         )
 
 
+    @inlineCallbacks
     def test_setComponent_uidchanged(self):
         """
         L{ICalendarObject.setComponent} raises L{InvalidCalendarComponentError}
         when given a L{VComponent} whose UID does not match its existing UID.
         """
-        calendar1 = self.calendarUnderTest()
+        calendar1 = yield self.calendarUnderTest()
         component = VComponent.fromString(event4_text)
-        calendarObject = calendar1.calendarObjectWithName("1.ics")
-        self.assertRaises(
+        calendarObject = yield calendar1.calendarObjectWithName("1.ics")
+        yield self.failUnlessFailure(
+            maybeDeferred(calendarObject.setComponent, component),
             InvalidObjectResourceError,
-            calendarObject.setComponent, component
         )
 
 
+    @inlineCallbacks
     def test_calendarHomeWithUID_create(self):
         """
         L{ICommonStoreTransaction.calendarHomeWithUID} with C{create=True}
@@ -843,25 +846,27 @@
         """
         txn = self.transactionUnderTest()
         noHomeUID = "xyzzy"
-        calendarHome = txn.calendarHomeWithUID(
+        calendarHome = yield txn.calendarHomeWithUID(
             noHomeUID,
             create=True
         )
+        @inlineCallbacks
         def readOtherTxn():
             otherTxn = self.savedStore.newTransaction(self.id() + "other txn")
             self.addCleanup(otherTxn.commit)
-            return otherTxn.calendarHomeWithUID(noHomeUID)
+            returnValue((yield otherTxn.calendarHomeWithUID(noHomeUID)))
         self.assertProvides(ICalendarHome, calendarHome)
         # Default calendar should be automatically created.
         self.assertProvides(ICalendar,
-                            calendarHome.calendarWithName("calendar"))
+                            (yield calendarHome.calendarWithName("calendar")))
         # A concurrent transaction shouldn't be able to read it yet:
-        self.assertIdentical(readOtherTxn(), None)
-        self.commit()
+        self.assertIdentical((yield readOtherTxn()), None)
+        yield self.commit()
         # But once it's committed, other transactions should see it.
-        self.assertProvides(ICalendarHome, readOtherTxn())
+        self.assertProvides(ICalendarHome, (yield readOtherTxn()))
 
 
+    @inlineCallbacks
     def test_setComponent(self):
         """
         L{CalendarObject.setComponent} changes the result of
@@ -869,18 +874,18 @@
         """
         component = VComponent.fromString(event1modified_text)
 
-        calendar1 = self.calendarUnderTest()
-        calendarObject = calendar1.calendarObjectWithName("1.ics")
-        oldComponent = calendarObject.component()
+        calendar1 = yield self.calendarUnderTest()
+        calendarObject = yield calendar1.calendarObjectWithName("1.ics")
+        oldComponent = yield calendarObject.component()
         self.assertNotEqual(component, oldComponent)
-        calendarObject.setComponent(component)
-        self.assertEquals(calendarObject.component(), component)
+        yield calendarObject.setComponent(component)
+        self.assertEquals((yield calendarObject.component()), component)
 
         # Also check a new instance
-        calendarObject = calendar1.calendarObjectWithName("1.ics")
-        self.assertEquals(calendarObject.component(), component)
+        calendarObject = yield calendar1.calendarObjectWithName("1.ics")
+        self.assertEquals((yield calendarObject.component()), component)
 
-        self.commit()
+        yield self.commit()
 
         # Make sure notification fired after commit
         self.assertEquals(
@@ -901,40 +906,45 @@
         self.assertProvides(IPropertyStore, properties)
 
 
+    @inlineCallbacks
     def test_homeProperties(self):
         """
         L{ICalendarHome.properties} returns a property store.
         """
-        self.checkPropertiesMethod(self.homeUnderTest())
+        self.checkPropertiesMethod((yield self.homeUnderTest()))
 
 
+    @inlineCallbacks
     def test_calendarProperties(self):
         """
         L{ICalendar.properties} returns a property store.
         """
-        self.checkPropertiesMethod(self.calendarUnderTest())
+        self.checkPropertiesMethod((yield self.calendarUnderTest()))
 
 
+    @inlineCallbacks
     def test_calendarObjectProperties(self):
         """
         L{ICalendarObject.properties} returns a property store.
         """
-        self.checkPropertiesMethod(self.calendarObjectUnderTest())
+        self.checkPropertiesMethod((yield self.calendarObjectUnderTest()))
 
 
+    @inlineCallbacks
     def test_newCalendarObjectProperties(self):
         """
         L{ICalendarObject.properties} returns an empty property store for a
         calendar object which has been created but not committed.
         """
-        calendar = self.calendarUnderTest()
-        calendar.createCalendarObjectWithName(
+        calendar = yield self.calendarUnderTest()
+        yield calendar.createCalendarObjectWithName(
             "4.ics", VComponent.fromString(event4_text)
         )
-        newEvent = calendar.calendarObjectWithName("4.ics")
+        newEvent = yield calendar.calendarObjectWithName("4.ics")
         self.assertEquals(newEvent.properties().items(), [])
 
 
+    @inlineCallbacks
     def test_setComponentPreservesProperties(self):
         """
         L{ICalendarObject.setComponent} preserves properties.
@@ -948,16 +958,16 @@
         propertyContent.name = propertyName.name
         propertyContent.namespace = propertyName.namespace
 
-        self.calendarObjectUnderTest().properties()[
+        (yield self.calendarObjectUnderTest()).properties()[
             propertyName] = propertyContent
-        self.commit()
+        yield self.commit()
         # Sanity check; are properties even readable in a separate transaction?
         # Should probably be a separate test.
         self.assertEquals(
-            self.calendarObjectUnderTest().properties()[propertyName],
+            (yield self.calendarObjectUnderTest()).properties()[propertyName],
             propertyContent)
-        obj = self.calendarObjectUnderTest()
-        event1_text = obj.iCalendarText()
+        obj = yield self.calendarObjectUnderTest()
+        event1_text = yield obj.iCalendarText()
         event1_text_withDifferentSubject = event1_text.replace(
             "SUMMARY:CalDAV protocol updates",
             "SUMMARY:Changed"
@@ -969,9 +979,9 @@
 
         # Putting everything into a separate transaction to account for any
         # caching that may take place.
-        self.commit()
+        yield self.commit()
         self.assertEquals(
-            self.calendarObjectUnderTest().properties()[propertyName],
+            (yield self.calendarObjectUnderTest()).properties()[propertyName],
             propertyContent
         )
 
@@ -1014,20 +1024,23 @@
 END:VCALENDAR
     """.strip().split("\n"))
 
+
+    @inlineCallbacks
     def test_dropboxID(self):
         """
         L{ICalendarObject.dropboxID} should synthesize its dropbox from the X
         -APPLE-DROPBOX property, if available.
         """
-        cal = self.calendarUnderTest()
-        cal.createCalendarObjectWithName("drop.ics", VComponent.fromString(
+        cal = yield self.calendarUnderTest()
+        yield cal.createCalendarObjectWithName("drop.ics", VComponent.fromString(
                 self.eventWithDropbox
             )
         )
-        obj = cal.calendarObjectWithName("drop.ics")
-        self.assertEquals(obj.dropboxID(), "some-dropbox-id")
+        obj = yield cal.calendarObjectWithName("drop.ics")
+        self.assertEquals((yield obj.dropboxID()), "some-dropbox-id")
 
 
+    @inlineCallbacks
     def test_indexByDropboxProperty(self):
         """
         L{ICalendarHome.calendarObjectWithDropboxID} will return a calendar
@@ -1035,17 +1048,17 @@
         -APPLE-DROPBOX} property URI.
         """
         objName = "with-dropbox.ics"
-        cal = self.calendarUnderTest()
+        cal = yield self.calendarUnderTest()
         cal.createCalendarObjectWithName(
             objName, VComponent.fromString(
                 self.eventWithDropbox
             )
         )
-        self.commit()
-        home = self.homeUnderTest()
-        cal = self.calendarUnderTest()
-        fromName = cal.calendarObjectWithName(objName)
-        fromDropbox = home.calendarObjectWithDropboxID("some-dropbox-id")
+        yield self.commit()
+        home = yield self.homeUnderTest()
+        cal = yield self.calendarUnderTest()
+        fromName = yield cal.calendarObjectWithName(objName)
+        fromDropbox = yield home.calendarObjectWithDropboxID("some-dropbox-id")
         self.assertEquals(fromName, fromDropbox)
 
 
@@ -1054,12 +1067,14 @@
         """
         Common logic for attachment-creation tests.
         """
-        obj = self.calendarObjectUnderTest()
-        t = obj.createAttachmentWithName("new.attachment", MimeType("text", "x-fixture"))
+        obj = yield self.calendarObjectUnderTest()
+        t = yield obj.createAttachmentWithName(
+            "new.attachment", MimeType("text", "x-fixture")
+        )
         t.write("new attachment")
         t.write(" text")
         t.loseConnection()
-        obj = refresh(obj)
+        obj = yield refresh(obj)
         class CaptureProtocol(Protocol):
             buf = ''
             def dataReceived(self, data):
@@ -1068,7 +1083,7 @@
                 self.deferred.callback(self.buf)
         capture = CaptureProtocol()
         capture.deferred = Deferred()
-        attachment = obj.attachmentWithName("new.attachment")
+        attachment = yield obj.attachmentWithName("new.attachment")
         self.assertProvides(IAttachment, attachment)
         attachment.retrieve(capture)
         data = yield capture.deferred
@@ -1078,7 +1093,7 @@
         self.assertEquals(contentType, MimeType("text", "x-fixture"))
         self.assertEquals(attachment.md5(), '50a9f27aeed9247a0833f30a631f1858')
         self.assertEquals(
-            [attachment.name() for attachment in obj.attachments()],
+            [attachment.name() for attachment in (yield obj.attachments())],
             ['new.attachment']
         )
 
@@ -1098,9 +1113,11 @@
         L{IAttachment} object that can be retrieved by
         L{ICalendarObject.attachmentWithName} in subsequent transactions.
         """
+        @inlineCallbacks
         def refresh(obj):
-            self.commit()
-            return self.calendarObjectUnderTest()
+            yield self.commit()
+            result = yield self.calendarObjectUnderTest()
+            returnValue(result)
         return self.createAttachmentTest(refresh)
 
 
@@ -1109,14 +1126,15 @@
         L{ICalendarObject.removeAttachmentWithName} will remove the calendar
         object with the given name.
         """
+        @inlineCallbacks
         def deleteIt(ignored):
-            obj = self.calendarObjectUnderTest()
+            obj = yield self.calendarObjectUnderTest()
             obj.removeAttachmentWithName("new.attachment")
-            obj = refresh(obj)
+            obj = yield refresh(obj)
             self.assertIdentical(
-                None, obj.attachmentWithName("new.attachment")
+                None, (yield obj.attachmentWithName("new.attachment"))
             )
-            self.assertEquals(list(obj.attachments()), [])
+            self.assertEquals(list((yield obj.attachments())), [])
         return self.test_createAttachmentCommit().addCallback(deleteIt)
 
 
@@ -1125,73 +1143,92 @@
         L{ICalendarObject.removeAttachmentWithName} will remove the calendar
         object with the given name.  (After commit, it will still be gone.)
         """
+        @inlineCallbacks
         def refresh(obj):
-            self.commit()
-            return self.calendarObjectUnderTest()
+            yield self.commit()
+            result = yield self.calendarObjectUnderTest()
+            returnValue(result)
         return self.test_removeAttachmentWithName(refresh)
 
 
+    @inlineCallbacks
     def test_noDropboxCalendar(self):
         """
         L{ICalendarObject.createAttachmentWithName} may create a directory
         named 'dropbox', but this should not be seen as a calendar by
         L{ICalendarHome.calendarWithName} or L{ICalendarHome.calendars}.
         """
-        obj = self.calendarObjectUnderTest()
-        t = obj.createAttachmentWithName("new.attachment", MimeType("text", "plain"))
+        obj = yield self.calendarObjectUnderTest()
+        t = yield obj.createAttachmentWithName(
+            "new.attachment", MimeType("text", "plain")
+        )
         t.write("new attachment text")
         t.loseConnection()
-        self.commit()
-        self.assertEquals(self.homeUnderTest().calendarWithName("dropbox"),
-                          None)
+        yield self.commit()
+        home = (yield self.homeUnderTest())
+        calendars = (yield home.calendars())
+        self.assertEquals((yield home.calendarWithName("dropbox")), None)
         self.assertEquals(
-            set([n.name() for n in self.homeUnderTest().calendars()]),
+            set([n.name() for n in calendars]),
             set(home1_calendarNames))
 
 
+    @inlineCallbacks
     def test_finishedOnCommit(self):
         """ 
         Calling L{ITransaction.abort} or L{ITransaction.commit} after
         L{ITransaction.commit} has already been called raises an
         L{AlreadyFinishedError}.
         """
-        self.calendarObjectUnderTest()
+        yield self.calendarObjectUnderTest()
         txn = self.lastTransaction
-        self.commit()
-        self.assertRaises(AlreadyFinishedError, txn.commit)
-        self.assertRaises(AlreadyFinishedError, txn.abort)
+        yield self.commit()
+        
+        yield self.failUnlessFailure(
+            maybeDeferred(txn.commit),
+            AlreadyFinishedError
+        )
+        yield self.failUnlessFailure(
+            maybeDeferred(txn.abort),
+            AlreadyFinishedError
+        )
 
 
+    @inlineCallbacks
     def test_dontLeakCalendars(self):
         """
         Calendars in one user's calendar home should not show up in another
         user's calendar home.
         """
-        home2 = self.transactionUnderTest().calendarHomeWithUID(
+        home2 = yield self.transactionUnderTest().calendarHomeWithUID(
             "home2", create=True)
-        self.assertIdentical(home2.calendarWithName("calendar_1"), None)
+        self.assertIdentical(
+            (yield home2.calendarWithName("calendar_1")), None)
 
 
+    @inlineCallbacks
     def test_dontLeakObjects(self):
         """
         Calendar objects in one user's calendar should not show up in another
         user's via uid or name queries.
         """
-        home1 = self.homeUnderTest()
-        home2 = self.transactionUnderTest().calendarHomeWithUID(
+        home1 = yield self.homeUnderTest()
+        home2 = yield self.transactionUnderTest().calendarHomeWithUID(
             "home2", create=True)
-        calendar1 = home1.calendarWithName("calendar_1")
-        calendar2 = home2.calendarWithName("calendar")
-        objects = list(home2.calendarWithName("calendar").calendarObjects())
+        calendar1 = yield home1.calendarWithName("calendar_1")
+        calendar2 = yield home2.calendarWithName("calendar")
+        objects = list(
+            (yield (yield home2.calendarWithName("calendar")).calendarObjects()))
         self.assertEquals(objects, [])
         for resourceName in self.requirements['home1']['calendar_1'].keys():
-            obj = calendar1.calendarObjectWithName(resourceName)
+            obj = yield calendar1.calendarObjectWithName(resourceName)
             self.assertIdentical(
-                calendar2.calendarObjectWithName(resourceName), None)
+                (yield calendar2.calendarObjectWithName(resourceName)), None)
             self.assertIdentical(
-                calendar2.calendarObjectWithUID(obj.uid()), None)
+                (yield calendar2.calendarObjectWithUID(obj.uid())), None)
 
 
+    @inlineCallbacks
     def test_eachCalendarHome(self):
         """
         L{ICalendarTransaction.eachCalendarHome} returns an iterator that
@@ -1201,11 +1238,13 @@
         additionalUIDs = set('alpha-uid home2 home3 beta-uid'.split())
         txn = self.transactionUnderTest()
         for name in additionalUIDs:
-            txn.calendarHomeWithUID(name, create=True)
-        self.commit()
+            # maybe it's not actually necessary to yield (i.e. wait) for each
+            # one?  commit() should wait for all of them.
+            yield txn.calendarHomeWithUID(name, create=True)
+        yield self.commit()
         foundUIDs = set([])
         lastTxn = None
-        for txn, home in self.storeUnderTest().eachCalendarHome():
+        for txn, home in (yield self.storeUnderTest().eachCalendarHome()):
             self.addCleanup(txn.commit)
             foundUIDs.add(home.uid())
             self.assertNotIdentical(lastTxn, txn)
@@ -1219,18 +1258,3 @@
 
 
 
-class StubNotifierFactory(object):
-
-    """ For testing push notifications without an XMPP server """
-
-    def __init__(self):
-        self.reset()
-
-    def newNotifier(self, label="default", id=None, prefix=None):
-        return Notifier(self, label=label, id=id, prefix=prefix)
-
-    def send(self, op, id):
-        self.history.append((op, id))
-
-    def reset(self):
-        self.history = []

Modified: CalendarServer/trunk/txdav/caldav/datastore/test/test_file.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/test/test_file.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/txdav/caldav/datastore/test/test_file.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -22,9 +22,11 @@
 # deleted and replaced with either implementation-specific methods on
 # FileStorageTests, or implementation-agnostic methods on CommonTests.
 
-from twext.python.filepath import CachingFilePath as FilePath
 from twisted.trial import unittest
+from twisted.internet.defer import inlineCallbacks
 
+from twext.python.filepath import CachingFilePath as FilePath
+
 from twext.python.vcomponent import VComponent
 
 from txdav.common.icommondatastore import HomeChildNameNotAllowedError
@@ -63,21 +65,22 @@
     storePath.copyTo(calendarPath)
 
     test.calendarStore = CalendarStore(storeRootPath, test.notifierFactory)
-    test.txn = test.calendarStore.newTransaction()
+    test.txn = test.calendarStore.newTransaction(test.id() + "(old)")
     assert test.calendarStore is not None, "No calendar store?"
 
 
 
+ at inlineCallbacks
 def setUpHome1(test):
     setUpCalendarStore(test)
-    test.home1 = test.txn.calendarHomeWithUID("home1")
+    test.home1 = yield test.txn.calendarHomeWithUID("home1")
     assert test.home1 is not None, "No calendar home?"
 
 
-
+ at inlineCallbacks
 def setUpCalendar1(test):
-    setUpHome1(test)
-    test.calendar1 = test.home1.calendarWithName("calendar_1")
+    yield setUpHome1(test)
+    test.calendar1 = yield test.home1.calendarWithName("calendar_1")
     assert test.calendar1 is not None, "No calendar?"
 
 
@@ -109,7 +112,7 @@
 
     notifierFactory = None
     def setUp(self):
-        setUpHome1(self)
+        return setUpHome1(self)
 
 
     def test_init(self):
@@ -167,7 +170,7 @@
     notifierFactory = None
 
     def setUp(self):
-        setUpCalendar1(self)
+        return setUpCalendar1(self)
 
 
     def test_init(self):
@@ -185,29 +188,31 @@
         )
 
 
+    @inlineCallbacks
     def test_useIndexImmediately(self):
         """
         L{Calendar._index} is usable in the same transaction it is created, with
         a temporary filename.
         """
         self.home1.createCalendarWithName("calendar2")
-        calendar = self.home1.calendarWithName("calendar2")
+        calendar = yield self.home1.calendarWithName("calendar2")
         index = calendar._index
-        self.assertEquals(set(index.calendarObjects()),
-                          set(calendar.calendarObjects()))
-        self.txn.commit()
+        yield self.assertEquals(set((yield index.calendarObjects())),
+                                set((yield calendar.calendarObjects())))
+        yield self.txn.commit()
         self.txn = self.calendarStore.newTransaction()
-        self.home1 = self.txn.calendarHomeWithUID("home1")
-        calendar = self.home1.calendarWithName("calendar2")
+        self.home1 = yield self.txn.calendarHomeWithUID("home1")
+        calendar = yield self.home1.calendarWithName("calendar2")
         # FIXME: we should be curating our own index here, but in order to fix
         # that the code in the old implicit scheduler needs to change.  This
         # test would be more effective if there were actually some objects in
         # this list.
         index = calendar._index
-        self.assertEquals(set(index.calendarObjects()),
-                          set(calendar.calendarObjects()))
+        self.assertEquals(set((yield index.calendarObjects())),
+                          set((yield calendar.calendarObjects())))
 
 
+    @inlineCallbacks
     def test_calendarObjectWithName_dot(self):
         """
         Filenames starting with "." are reserved by this
@@ -216,22 +221,25 @@
         """
         name = ".foo.ics"
         self.home1._path.child(name).touch()
-        self.assertEquals(self.calendar1.calendarObjectWithName(name), None)
+        self.assertEquals(
+            (yield self.calendar1.calendarObjectWithName(name)),
+            None)
 
 
     @featureUnimplemented
+    @inlineCallbacks
     def test_calendarObjectWithUID_exists(self):
         """
         Find existing calendar object by name.
         """
-        calendarObject = self.calendar1.calendarObjectWithUID("1")
+        calendarObject = yield self.calendar1.calendarObjectWithUID("1")
         self.failUnless(
             isinstance(calendarObject, CalendarObject),
             calendarObject
         )
         self.assertEquals(
             calendarObject.component(),
-            self.calendar1.calendarObjectWithName("1.ics").component()
+            (yield self.calendar1.calendarObjectWithName("1.ics")).component()
         )
 
 
@@ -249,13 +257,14 @@
 
 
     @featureUnimplemented
+    @inlineCallbacks
     def test_createCalendarObjectWithName_uidconflict(self):
         """
         Attempt to create a calendar object with a conflicting UID
         should raise.
         """
         name = "foo.ics"
-        assert self.calendar1.calendarObjectWithName(name) is None
+        assert (yield self.calendar1.calendarObjectWithName(name)) is None
         component = VComponent.fromString(event1modified_text)
         self.assertRaises(
             ObjectResourceUIDAlreadyExistsError,
@@ -264,6 +273,7 @@
         )
 
 
+    @inlineCallbacks
     def test_removeCalendarObject_delayedEffect(self):
         """
         Removing a calendar object should not immediately remove the underlying
@@ -271,7 +281,7 @@
         """
         self.calendar1.removeCalendarObjectWithName("2.ics")
         self.failUnless(self.calendar1._path.child("2.ics").exists())
-        self.txn.commit()
+        yield self.txn.commit()
         self.failIf(self.calendar1._path.child("2.ics").exists())
 
 
@@ -289,16 +299,22 @@
         )
 
 
+    counter = 0
+    @inlineCallbacks
     def _refresh(self):
         """
         Re-read the (committed) home1 and calendar1 objects in a new
         transaction.
         """
-        self.txn = self.calendarStore.newTransaction()
-        self.home1 = self.txn.calendarHomeWithUID("home1")
-        self.calendar1 = self.home1.calendarWithName("calendar_1")
+        self.counter += 1
+        self.txn = self.calendarStore.newTransaction(
+            self.id() + " (old #" + str(self.counter) + ")"
+        )
+        self.home1 = yield self.txn.calendarHomeWithUID("home1")
+        self.calendar1 = yield self.home1.calendarWithName("calendar_1")
 
 
+    @inlineCallbacks
     def test_undoCreateCalendarObject(self):
         """
         If a calendar object is created as part of a transaction, it will be
@@ -306,19 +322,22 @@
         """
         # Make sure that the calendar home is actually committed; rolling back
         # calendar home creation will remove the whole directory.
-        self.txn.commit()
-        self._refresh()
+        yield self.txn.commit()
+        yield self._refresh()
         self.calendar1.createCalendarObjectWithName(
             "sample.ics",
             VComponent.fromString(event4_text)
         )
-        self._refresh()
+        yield self.txn.abort()
+        yield self._refresh()
         self.assertIdentical(
-            self.calendar1.calendarObjectWithName("sample.ics"),
+            (yield self.calendar1.calendarObjectWithName("sample.ics")),
             None
         )
+        yield self.txn.commit()
 
 
+    @inlineCallbacks
     def doThenUndo(self):
         """
         Commit the current transaction, but add an operation that will cause it
@@ -330,43 +349,46 @@
             raise RuntimeError("oops")
         self.txn.addOperation(fail, "dummy failing operation")
         self.assertRaises(RuntimeError, self.txn.commit)
-        self._refresh()
+        yield self._refresh()
 
 
+    @inlineCallbacks
     def test_undoModifyCalendarObject(self):
         """
         If an existing calendar object is modified as part of a transaction, it
         should be restored to its previous status if the transaction aborts.
         """
-        originalComponent = self.calendar1.calendarObjectWithName(
+        originalComponent = yield self.calendar1.calendarObjectWithName(
             "1.ics").component()
-        self.calendar1.calendarObjectWithName("1.ics").setComponent(
+        (yield self.calendar1.calendarObjectWithName("1.ics")).setComponent(
             VComponent.fromString(event1modified_text)
         )
         # Sanity check.
         self.assertEquals(
-            self.calendar1.calendarObjectWithName("1.ics").component(),
+            (yield self.calendar1.calendarObjectWithName("1.ics")).component(),
             VComponent.fromString(event1modified_text)
         )
-        self.doThenUndo()
+        yield self.doThenUndo()
         self.assertEquals(
-            self.calendar1.calendarObjectWithName("1.ics").component(),
+            (yield self.calendar1.calendarObjectWithName("1.ics")).component(),
             originalComponent
         )
 
 
+    @inlineCallbacks
     def test_modifyCalendarObjectCaches(self):
         """
         Modifying a calendar object should cache the modified component in
         memory, to avoid unnecessary parsing round-trips.
         """
+        self.addCleanup(self.txn.commit)
         modifiedComponent = VComponent.fromString(event1modified_text)
-        self.calendar1.calendarObjectWithName("1.ics").setComponent(
+        (yield self.calendar1.calendarObjectWithName("1.ics")).setComponent(
             modifiedComponent
         )
         self.assertIdentical(
             modifiedComponent,
-            self.calendar1.calendarObjectWithName("1.ics").component()
+            (yield self.calendar1.calendarObjectWithName("1.ics")).component()
         )
 
 
@@ -410,9 +432,10 @@
 class CalendarObjectTest(unittest.TestCase):
     notifierFactory = None
 
+    @inlineCallbacks
     def setUp(self):
-        setUpCalendar1(self)
-        self.object1 = self.calendar1.calendarObjectWithName("1.ics")
+        yield setUpCalendar1(self)
+        self.object1 = yield self.calendar1.calendarObjectWithName("1.ics")
 
 
     def test_init(self):
@@ -464,10 +487,11 @@
                           self.storeRootPath)
 
 
+    @inlineCallbacks
     def test_calendarObjectsWithDotFile(self):
         """
         Adding a dotfile to the calendar home should not increase
         """
-        self.homeUnderTest()._path.child(".foo").createDirectory()
-        self.test_calendarObjects()
+        (yield self.homeUnderTest())._path.child(".foo").createDirectory()
+        yield self.test_calendarObjects()
 

Copied: CalendarServer/trunk/txdav/caldav/datastore/test/test_index_file.py (from rev 6445, CalendarServer/branches/users/glyph/more-deferreds-7/txdav/caldav/datastore/test/test_index_file.py)
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/test/test_index_file.py	                        (rev 0)
+++ CalendarServer/trunk/txdav/caldav/datastore/test/test_index_file.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -0,0 +1,943 @@
+##
+# Copyright (c) 2010 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from twisted.internet import reactor
+from twisted.internet.task import deferLater
+
+from txdav.caldav.datastore.index_file import Index, MemcachedUIDReserver
+from txdav.common.icommondatastore import ReservationError
+
+from twistedcaldav import caldavxml
+from twistedcaldav.caldavxml import TimeRange
+from twistedcaldav.ical import Component
+from twistedcaldav.instance import InvalidOverriddenInstanceError
+from twistedcaldav.query import calendarqueryfilter
+from twistedcaldav.test.util import InMemoryMemcacheProtocol
+import twistedcaldav.test.util
+
+import datetime
+import os
+from twisted.internet.defer import inlineCallbacks
+
+
+class MinimalResourceReplacement(object):
+    """
+    Provide the minimal set of attributes and methods from CalDAVFile required
+    by L{Index}.
+    """
+
+    def __init__(self, filePath):
+        self.fp = filePath
+
+
+    def isCalendarCollection(self):
+        return True
+
+
+    def getChild(self, name):
+        # FIXME: this should really return something with a child method
+        return self.fp.child(name)
+
+
+    def initSyncToken(self):
+        pass
+
+
+
+class SQLIndexTests (twistedcaldav.test.util.TestCase):
+    """
+    Test abstract SQL DB class
+    """
+
+    def setUp(self):
+        super(SQLIndexTests, self).setUp()
+        self.site.resource.isCalendarCollection = lambda: True
+        self.indexDirPath = self.site.resource.fp
+        # FIXME: since this resource lies about isCalendarCollection, it doesn't
+        # have all the associated backend machinery to actually get children.
+        self.db = Index(MinimalResourceReplacement(self.indexDirPath))
+
+
+    def test_reserve_uid_ok(self):
+        uid = "test-test-test"
+        d = self.db.isReservedUID(uid)
+        d.addCallback(self.assertFalse)
+        d.addCallback(lambda _: self.db.reserveUID(uid))
+        d.addCallback(lambda _: self.db.isReservedUID(uid))
+        d.addCallback(self.assertTrue)
+        d.addCallback(lambda _: self.db.unreserveUID(uid))
+        d.addCallback(lambda _: self.db.isReservedUID(uid))
+        d.addCallback(self.assertFalse)
+
+        return d
+
+
+    def test_reserve_uid_twice(self):
+        uid = "test-test-test"
+        d = self.db.reserveUID(uid)
+        d.addCallback(lambda _: self.db.isReservedUID(uid))
+        d.addCallback(self.assertTrue)
+        d.addCallback(lambda _:
+                      self.assertFailure(self.db.reserveUID(uid),
+                                         ReservationError))
+        return d
+
+
+    def test_unreserve_unreserved(self):
+        uid = "test-test-test"
+        return self.assertFailure(self.db.unreserveUID(uid),
+                                  ReservationError)
+
+
+    def test_reserve_uid_timeout(self):
+        # WARNING: This test is fundamentally flawed and will fail
+        # intermittently because it uses the real clock.
+        uid = "test-test-test"
+        from twistedcaldav.config import config
+        old_timeout = config.UIDReservationTimeOut
+        config.UIDReservationTimeOut = 1
+
+        def _finally():
+            config.UIDReservationTimeOut = old_timeout
+
+        d = self.db.isReservedUID(uid)
+        d.addCallback(self.assertFalse)
+        d.addCallback(lambda _: self.db.reserveUID(uid))
+        d.addCallback(lambda _: self.db.isReservedUID(uid))
+        d.addCallback(self.assertTrue)
+        d.addCallback(lambda _: deferLater(reactor, 2, lambda: None))
+        d.addCallback(lambda _: self.db.isReservedUID(uid))
+        d.addCallback(self.assertFalse)
+        self.addCleanup(_finally)
+
+        return d
+
+
+    def test_index(self):
+        data = (
+            (
+                "#1.1 Simple component",
+                "1.1",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1.1
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                False,
+                True,
+            ),
+            (
+                "#2.1 Recurring component",
+                "2.1",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-2.1
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+RRULE:FREQ=WEEKLY;COUNT=2
+END:VEVENT
+END:VCALENDAR
+""",
+                False,
+                True,
+            ),
+            (
+                "#2.2 Recurring component with override",
+                "2.2",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-2.2
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+RRULE:FREQ=WEEKLY;COUNT=2
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890-2.2
+RECURRENCE-ID:20080608T120000Z
+DTSTART:20080608T120000Z
+DTEND:20080608T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                False,
+                True,
+            ),
+            (
+                "#2.3 Recurring component with broken override - new",
+                "2.3",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-2.3
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+RRULE:FREQ=WEEKLY;COUNT=2
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890-2.3
+RECURRENCE-ID:20080609T120000Z
+DTSTART:20080608T120000Z
+DTEND:20080608T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                False,
+                False,
+            ),
+            (
+                "#2.4 Recurring component with broken override - existing",
+                "2.4",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-2.4
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+RRULE:FREQ=WEEKLY;COUNT=2
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890-2.4
+RECURRENCE-ID:20080609T120000Z
+DTSTART:20080608T120000Z
+DTEND:20080608T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                True,
+                True,
+            ),
+        )
+
+        for description, name, calendar_txt, reCreate, ok in data:
+            calendar = Component.fromString(calendar_txt)
+            if ok:
+                f = open(os.path.join(self.indexDirPath.path, name), "w")
+                f.write(calendar_txt)
+                del f
+
+                self.db.addResource(name, calendar, reCreate=reCreate)
+                self.assertTrue(self.db.resourceExists(name), msg=description)
+            else:
+                self.assertRaises(InvalidOverriddenInstanceError, self.db.addResource, name, calendar)
+                self.assertFalse(self.db.resourceExists(name), msg=description)
+
+        self.db._db_recreate()
+        for description, name, calendar_txt, reCreate, ok in data:
+            if ok:
+                self.assertTrue(self.db.resourceExists(name), msg=description)
+            else:
+                self.assertFalse(self.db.resourceExists(name), msg=description)
+
+        self.db.testAndUpdateIndex(datetime.date(2020, 1, 1))
+        for description, name, calendar_txt, reCreate, ok in data:
+            if ok:
+                self.assertTrue(self.db.resourceExists(name), msg=description)
+            else:
+                self.assertFalse(self.db.resourceExists(name), msg=description)
+
+
+    @inlineCallbacks
+    def test_index_timespan(self):
+        data = (
+            (
+                "#1.1 Simple component - busy",
+                "1.1",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1.1
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                "20080601T000000Z", "20080602T000000Z",
+                "mailto:user1 at example.com",
+                (('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'F'),),
+            ),
+            (
+                "#1.2 Simple component - transparent",
+                "1.2",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1.2
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+TRANSP:TRANSPARENT
+END:VEVENT
+END:VCALENDAR
+""",
+                "20080602T000000Z", "20080603T000000Z",
+                "mailto:user1 at example.com",
+                (('N', "2008-06-02 12:00:00+00:00", "2008-06-02 13:00:00+00:00", 'B', 'T'),),
+            ),
+            (
+                "#1.3 Simple component - canceled",
+                "1.3",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1.3
+DTSTART:20080603T120000Z
+DTEND:20080603T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+STATUS:CANCELLED
+END:VEVENT
+END:VCALENDAR
+""",
+                "20080603T000000Z", "20080604T000000Z",
+                "mailto:user1 at example.com",
+                (('N', "2008-06-03 12:00:00+00:00", "2008-06-03 13:00:00+00:00", 'F', 'F'),),
+            ),
+            (
+                "#1.4 Simple component - tentative",
+                "1.4",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1.4
+DTSTART:20080604T120000Z
+DTEND:20080604T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+STATUS:TENTATIVE
+END:VEVENT
+END:VCALENDAR
+""",
+                "20080604T000000Z", "20080605T000000Z",
+                "mailto:user1 at example.com",
+                (('N', "2008-06-04 12:00:00+00:00", "2008-06-04 13:00:00+00:00", 'T', 'F'),),
+            ),
+            (
+                "#2.1 Recurring component - busy",
+                "2.1",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-2.1
+DTSTART:20080605T120000Z
+DTEND:20080605T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+RRULE:FREQ=DAILY;COUNT=2
+END:VEVENT
+END:VCALENDAR
+""",
+                "20080605T000000Z", "20080607T000000Z",
+                "mailto:user1 at example.com",
+                (
+                    ('N', "2008-06-05 12:00:00+00:00", "2008-06-05 13:00:00+00:00", 'B', 'F'),
+                    ('N', "2008-06-06 12:00:00+00:00", "2008-06-06 13:00:00+00:00", 'B', 'F'),
+                ),
+            ),
+            (
+                "#2.2 Recurring component - busy",
+                "2.2",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-2.2
+DTSTART:20080607T120000Z
+DTEND:20080607T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+RRULE:FREQ=DAILY;COUNT=2
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890-2.2
+RECURRENCE-ID:20080608T120000Z
+DTSTART:20080608T140000Z
+DTEND:20080608T150000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+TRANSP:TRANSPARENT
+END:VEVENT
+END:VCALENDAR
+""",
+                "20080607T000000Z", "20080609T000000Z",
+                "mailto:user1 at example.com",
+                (
+                    ('N', "2008-06-07 12:00:00+00:00", "2008-06-07 13:00:00+00:00", 'B', 'F'),
+                    ('N', "2008-06-08 14:00:00+00:00", "2008-06-08 15:00:00+00:00", 'B', 'T'),
+                ),
+            ),
+        )
+
+        for description, name, calendar_txt, trstart, trend, organizer, instances in data:
+            calendar = Component.fromString(calendar_txt)
+
+            f = open(os.path.join(self.indexDirPath.path, name), "w")
+            f.write(calendar_txt)
+            del f
+
+            self.db.addResource(name, calendar)
+            self.assertTrue(self.db.resourceExists(name), msg=description)
+
+            # Create fake filter element to match time-range
+            filter =  caldavxml.Filter(
+                  caldavxml.ComponentFilter(
+                      caldavxml.ComponentFilter(
+                          TimeRange(
+                              start=trstart,
+                              end=trend,
+                          ),
+                          name=("VEVENT", "VFREEBUSY", "VAVAILABILITY"),
+                      ),
+                      name="VCALENDAR",
+                   )
+              )
+            filter = calendarqueryfilter.Filter(filter)
+
+            resources = yield self.db.indexedSearch(filter, fbtype=True)
+            index_results = set()
+            for _ignore_name, _ignore_uid, type, test_organizer, float, start, end, fbtype, transp in resources:
+                self.assertEqual(test_organizer, organizer, msg=description)
+                index_results.add((float, start, end, fbtype, transp,))
+
+            self.assertEqual(set(instances), index_results, msg=description)
+
+
+    @inlineCallbacks
+    def test_index_timespan_per_user(self):
+        data = (
+            (
+                "#1.1 Single per-user non-recurring component",
+                "1.1",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1.1
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890-1.1
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+TRANSP:TRANSPARENT
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""",
+                "20080601T000000Z", "20080602T000000Z",
+                "mailto:user1 at example.com",
+                (
+                    (
+                        "user01",
+                        (('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'T'),),
+                    ),
+                    (
+                        "user02",
+                        (('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'F'),),
+                    ),
+                ),
+            ),
+            (
+                "#1.2 Two per-user non-recurring component",
+                "1.2",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1.2
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890-1.2
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+TRANSP:TRANSPARENT
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890-1.2
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""",
+                "20080601T000000Z", "20080602T000000Z",
+                "mailto:user1 at example.com",
+                (
+                    (
+                        "user01",
+                        (('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'T'),),
+                    ),
+                    (
+                        "user02",
+                        (('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'F'),),
+                    ),
+                    (
+                        "user03",
+                        (('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'F'),),
+                    ),
+                ),
+            ),
+            (
+                "#2.1 Single per-user simple recurring component",
+                "2.1",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1.1
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+RRULE:FREQ=DAILY;COUNT=10
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890-1.1
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+TRANSP:TRANSPARENT
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""",
+                "20080601T000000Z", "20080603T000000Z",
+                "mailto:user1 at example.com",
+                (
+                    (
+                        "user01",
+                        (
+                            ('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'T'),
+                            ('N', "2008-06-02 12:00:00+00:00", "2008-06-02 13:00:00+00:00", 'B', 'T'),
+                        ),
+                    ),
+                    (
+                        "user02",
+                        (
+                            ('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'F'),
+                            ('N', "2008-06-02 12:00:00+00:00", "2008-06-02 13:00:00+00:00", 'B', 'F'),
+                        ),
+                    ),
+                ),
+            ),
+            (
+                "#2.2 Two per-user simple recurring component",
+                "2.2",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1.2
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+RRULE:FREQ=DAILY;COUNT=10
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890-1.2
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+TRANSP:TRANSPARENT
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890-1.2
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""",
+                "20080601T000000Z", "20080603T000000Z",
+                "mailto:user1 at example.com",
+                (
+                    (
+                        "user01",
+                        (
+                            ('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'T'),
+                            ('N', "2008-06-02 12:00:00+00:00", "2008-06-02 13:00:00+00:00", 'B', 'T'),
+                        ),
+                    ),
+                    (
+                        "user02",
+                        (
+                            ('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'F'),
+                            ('N', "2008-06-02 12:00:00+00:00", "2008-06-02 13:00:00+00:00", 'B', 'F'),
+                        ),
+                    ),
+                    (
+                        "user03",
+                        (
+                            ('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'F'),
+                            ('N', "2008-06-02 12:00:00+00:00", "2008-06-02 13:00:00+00:00", 'B', 'F'),
+                        ),
+                    ),
+                ),
+            ),
+            (
+                "#3.1 Single per-user complex recurring component",
+                "3.1",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1.1
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+RRULE:FREQ=DAILY;COUNT=10
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890-1.1
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890-1.1
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+TRANSP:TRANSPARENT
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080602T120000Z
+TRANSP:OPAQUE
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""",
+                "20080601T000000Z", "20080604T000000Z",
+                "mailto:user1 at example.com",
+                (
+                    (
+                        "user01",
+                        (
+                            ('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'T'),
+                            ('N', "2008-06-02 13:00:00+00:00", "2008-06-02 14:00:00+00:00", 'B', 'F'),
+                            ('N', "2008-06-03 12:00:00+00:00", "2008-06-03 13:00:00+00:00", 'B', 'T'),
+                        ),
+                    ),
+                    (
+                        "user02",
+                        (
+                            ('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'F'),
+                            ('N', "2008-06-02 13:00:00+00:00", "2008-06-02 14:00:00+00:00", 'B', 'F'),
+                            ('N', "2008-06-03 12:00:00+00:00", "2008-06-03 13:00:00+00:00", 'B', 'F'),
+                        ),
+                    ),
+                ),
+            ),
+            (
+                "#3.2 Two per-user complex recurring component",
+                "3.2",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1.2
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+RRULE:FREQ=DAILY;COUNT=10
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890-1.2
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890-1.2
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+TRANSP:TRANSPARENT
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080602T120000Z
+TRANSP:OPAQUE
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890-1.2
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080603T120000Z
+TRANSP:TRANSPARENT
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""",
+                "20080601T000000Z", "20080604T000000Z",
+                "mailto:user1 at example.com",
+                (
+                    (
+                        "user01",
+                        (
+                            ('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'T'),
+                            ('N', "2008-06-02 13:00:00+00:00", "2008-06-02 14:00:00+00:00", 'B', 'F'),
+                            ('N', "2008-06-03 12:00:00+00:00", "2008-06-03 13:00:00+00:00", 'B', 'T'),
+                        ),
+                    ),
+                    (
+                        "user02",
+                        (
+                            ('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'F'),
+                            ('N', "2008-06-02 13:00:00+00:00", "2008-06-02 14:00:00+00:00", 'B', 'F'),
+                            ('N', "2008-06-03 12:00:00+00:00", "2008-06-03 13:00:00+00:00", 'B', 'T'),
+                        ),
+                    ),
+                    (
+                        "user03",
+                        (
+                            ('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'F'),
+                            ('N', "2008-06-02 13:00:00+00:00", "2008-06-02 14:00:00+00:00", 'B', 'F'),
+                            ('N', "2008-06-03 12:00:00+00:00", "2008-06-03 13:00:00+00:00", 'B', 'F'),
+                        ),
+                    ),
+                ),
+            ),
+        )
+
+        for description, name, calendar_txt, trstart, trend, organizer, peruserinstances in data:
+            calendar = Component.fromString(calendar_txt)
+
+            f = open(os.path.join(self.indexDirPath.path, name), "w")
+            f.write(calendar_txt)
+            del f
+
+            self.db.addResource(name, calendar)
+            self.assertTrue(self.db.resourceExists(name), msg=description)
+
+            # Create fake filter element to match time-range
+            filter =  caldavxml.Filter(
+                  caldavxml.ComponentFilter(
+                      caldavxml.ComponentFilter(
+                          TimeRange(
+                              start=trstart,
+                              end=trend,
+                          ),
+                          name=("VEVENT", "VFREEBUSY", "VAVAILABILITY"),
+                      ),
+                      name="VCALENDAR",
+                   )
+              )
+            filter = calendarqueryfilter.Filter(filter)
+
+            for useruid, instances in peruserinstances:
+                resources = yield self.db.indexedSearch(filter, useruid=useruid, fbtype=True)
+                index_results = set()
+                for _ignore_name, _ignore_uid, type, test_organizer, float, start, end, fbtype, transp in resources:
+                    self.assertEqual(test_organizer, organizer, msg=description)
+                    index_results.add((str(float), str(start), str(end), str(fbtype), str(transp),))
+    
+                self.assertEqual(set(instances), index_results, msg="%s, user:%s" % (description, useruid,))
+
+            self.db.deleteResource(name)
+
+
+    def test_index_revisions(self):
+        data1 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1.1
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+END:VEVENT
+END:VCALENDAR
+"""
+        data2 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-2.1
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+RRULE:FREQ=WEEKLY;COUNT=2
+END:VEVENT
+END:VCALENDAR
+"""
+        data3 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-2.3
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+RRULE:FREQ=WEEKLY;COUNT=2
+END:VEVENT
+END:VCALENDAR
+"""
+
+        calendar = Component.fromString(data1)
+        self.db.addResource("data1.ics", calendar)
+        calendar = Component.fromString(data2)
+        self.db.addResource("data2.ics", calendar)
+        calendar = Component.fromString(data3)
+        self.db.addResource("data3.ics", calendar)
+        self.db.deleteResource("data3.ics")
+
+        tests = (
+            (0, (["data1.ics", "data2.ics",], [],)),
+            (1, (["data2.ics",], ["data3.ics",],)),
+            (2, ([], ["data3.ics",],)),
+            (3, ([], ["data3.ics",],)),
+            (4, ([], [],)),
+            (5, ([], [],)),
+        )
+        
+        for revision, results in tests:
+            for depth in ("1", "infinity"):
+                self.assertEquals(self.db.whatchanged(revision, depth), results, "Mismatched results for whatchanged with revision %d" % (revision,))
+
+class MemcacheTests(SQLIndexTests):
+    def setUp(self):
+        super(MemcacheTests, self).setUp()
+        self.memcache = InMemoryMemcacheProtocol()
+        self.db.reserver = MemcachedUIDReserver(self.db, self.memcache)
+
+
+    def tearDown(self):
+        for _ignore_k, v in self.memcache._timeouts.iteritems():
+            if v.active():
+                v.cancel()


Property changes on: CalendarServer/trunk/txdav/caldav/datastore/test/test_index_file.py
___________________________________________________________________
Added: svn:mergeinfo
   + /CalendarServer/branches/config-separation/txdav/caldav/datastore/test/test_index_file.py:4379-4443
/CalendarServer/branches/egg-info-351/txdav/caldav/datastore/test/test_index_file.py:4589-4625
/CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/test/test_index_file.py:6167-6191
/CalendarServer/branches/new-store/txdav/caldav/datastore/test/test_index_file.py:5594-5934
/CalendarServer/branches/new-store-no-caldavfile/txdav/caldav/datastore/test/test_index_file.py:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2/txdav/caldav/datastore/test/test_index_file.py:5936-5981
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/txdav/caldav/datastore/test/test_index_file.py:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/txdav/caldav/datastore/test/test_index_file.py:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591/txdav/caldav/datastore/test/test_index_file.py:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/txdav/caldav/datastore/test/test_index_file.py:4465-4957
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/txdav/caldav/datastore/test/test_index_file.py:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/txdav/caldav/datastore/test/test_index_file.py:5188-5440
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/caldav/datastore/test/test_index_file.py:4971-5080
/CalendarServer/branches/users/glyph/more-deferreds-6/txdav/caldav/datastore/test/test_index_file.py:6322-6368
/CalendarServer/branches/users/glyph/more-deferreds-7/txdav/caldav/datastore/test/test_index_file.py:6369-6445
/CalendarServer/branches/users/glyph/sendfdport/txdav/caldav/datastore/test/test_index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sql-store/txdav/caldav/datastore/test/test_index_file.py:5929-6073
/CalendarServer/branches/users/glyph/use-system-twisted/txdav/caldav/datastore/test/test_index_file.py:5084-5149
/CalendarServer/branches/users/sagen/locations-resources/txdav/caldav/datastore/test/test_index_file.py:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2/txdav/caldav/datastore/test/test_index_file.py:5052-5061
/CalendarServer/branches/users/sagen/resource-delegates-4038/txdav/caldav/datastore/test/test_index_file.py:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/txdav/caldav/datastore/test/test_index_file.py:4068-4075
/CalendarServer/branches/users/sagen/resources-2/txdav/caldav/datastore/test/test_index_file.py:5084-5093
/CalendarServer/branches/users/wsanchez/transations/txdav/caldav/datastore/test/test_index_file.py:5515-5593
/CalendarServer/trunk/twistedcaldav/test/test_index.py:6322-6394

Modified: CalendarServer/trunk/txdav/caldav/datastore/test/test_sql.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/test/test_sql.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/txdav/caldav/datastore/test/test_sql.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -19,19 +19,18 @@
 L{txdav.caldav.datastore.test.common}.
 """
 
-import time
+from twisted.trial import unittest
+from twisted.internet.defer import inlineCallbacks, returnValue
+from twisted.internet.task import deferLater
+from twisted.internet import reactor
 
+from twext.python.vcomponent import VComponent
+from twext.web2.dav.element.rfc2518 import GETContentLanguage, ResourceType
+
 from txdav.caldav.datastore.test.common import CommonTests as CalendarCommonTests
-
 from txdav.common.datastore.sql import ECALENDARTYPE
 from txdav.common.datastore.test.util import buildStore, populateCalendarsFrom
 
-from twisted.trial import unittest
-from twisted.internet.defer import inlineCallbacks
-from twisted.internet.threads import deferToThread
-from twext.python.vcomponent import VComponent
-from twext.web2.dav.element.rfc2518 import GETContentLanguage, ResourceType
-
 from txdav.caldav.datastore.test.test_file import setUpCalendarStore
 from txdav.caldav.datastore.util import _migrateCalendar, migrateHome
 from txdav.base.propertystore.base import PropertyName
@@ -47,11 +46,12 @@
     def setUp(self):
         super(CalendarSQLStorageTests, self).setUp()
         self._sqlCalendarStore = yield buildStore(self, self.notifierFactory)
-        self.populate()
+        yield self.populate()
 
 
+    @inlineCallbacks
     def populate(self):
-        populateCalendarsFrom(self.requirements, self.storeUnderTest())
+        yield populateCalendarsFrom(self.requirements, self.storeUnderTest())
         self.notifierFactory.reset()
 
 
@@ -62,19 +62,24 @@
         return self._sqlCalendarStore
 
 
+    @inlineCallbacks
     def assertCalendarsSimilar(self, a, b, bCalendarFilter=None):
         """
         Assert that two calendars have a similar structure (contain the same
         events).
         """
-        def namesAndComponents(x, filter=lambda x:x.component()):
-            return dict([(fromObj.name(), filter(fromObj))
-                         for fromObj in x.calendarObjects()])
+        @inlineCallbacks
+        def namesAndComponents(x, filter=lambda x: x.component()):
+            result = {}
+            for fromObj in (yield x.calendarObjects()):
+                result[fromObj.name()] = yield filter(fromObj)
+            returnValue(result)
         if bCalendarFilter is not None:
             extra = [bCalendarFilter]
         else:
             extra = []
-        self.assertEquals(namesAndComponents(a), namesAndComponents(b, *extra))
+        self.assertEquals((yield namesAndComponents(a)),
+                          (yield namesAndComponents(b, *extra)))
 
 
     def assertPropertiesSimilar(self, a, b, disregard=[]):
@@ -103,6 +108,7 @@
         self.addCleanup(txn.commit)
         return txn
 
+
     @inlineCallbacks
     def test_attachmentPath(self):
         """
@@ -111,32 +117,40 @@
         L{ICalendarObject.attachmentWithName}.
         """
         yield self.createAttachmentTest(lambda x: x)
-        attachmentRoot = self.calendarObjectUnderTest()._txn._store.attachmentsPath
+        attachmentRoot = (
+            yield self.calendarObjectUnderTest()
+        )._txn._store.attachmentsPath
         attachmentPath = attachmentRoot.child("ho").child("me").child("home1")
-        attachmentPath = attachmentPath.child(self.calendarObjectUnderTest().uid()).child("new.attachment")
+        attachmentPath = attachmentPath.child(
+            (yield self.calendarObjectUnderTest()).uid()).child(
+                "new.attachment")
         self.assertTrue(attachmentPath.isfile())
 
+
+    @inlineCallbacks
     def test_migrateCalendarFromFile(self):
         """
         C{_migrateCalendar()} can migrate a file-backed calendar to a database-
         backed calendar.
         """
-        fromCalendar = self.fileTransaction().calendarHomeWithUID(
-            "home1").calendarWithName("calendar_1")
-        toHome = self.transactionUnderTest().calendarHomeWithUID(
+        fromCalendar = yield (yield self.fileTransaction().calendarHomeWithUID(
+            "home1")).calendarWithName("calendar_1")
+        toHome = yield self.transactionUnderTest().calendarHomeWithUID(
             "new-home", create=True)
-        toCalendar = toHome.calendarWithName("calendar")
-        _migrateCalendar(fromCalendar, toCalendar, lambda x: x.component())
-        self.assertCalendarsSimilar(fromCalendar, toCalendar)
+        toCalendar = yield toHome.calendarWithName("calendar")
+        yield _migrateCalendar(fromCalendar, toCalendar,
+                               lambda x: x.component())
+        yield self.assertCalendarsSimilar(fromCalendar, toCalendar)
 
 
+    @inlineCallbacks
     def test_migrateHomeFromFile(self):
         """
         L{migrateHome} will migrate an L{ICalendarHome} provider from one
         backend to another; in this specific case, from the file-based backend
         to the SQL-based backend.
         """
-        fromHome = self.fileTransaction().calendarHomeWithUID("home1")
+        fromHome = yield self.fileTransaction().calendarHomeWithUID("home1")
 
         builtinProperties = [PropertyName.fromElement(ResourceType)]
 
@@ -145,19 +159,21 @@
 
         key = PropertyName.fromElement(GETContentLanguage)
         fromHome.properties()[key] = GETContentLanguage("C")
-        fromHome.calendarWithName("calendar_1").properties()[key] = (
+        (yield fromHome.calendarWithName("calendar_1")).properties()[key] = (
             GETContentLanguage("pig-latin")
         )
-        toHome = self.transactionUnderTest().calendarHomeWithUID(
+        toHome = yield self.transactionUnderTest().calendarHomeWithUID(
             "new-home", create=True
         )
-        migrateHome(fromHome, toHome, lambda x: x.component())
-        self.assertEquals(set([c.name() for c in toHome.calendars()]),
+        yield migrateHome(fromHome, toHome, lambda x: x.component())
+        toCalendars = yield toHome.calendars()
+        self.assertEquals(set([c.name() for c in toCalendars]),
                           set([k for k in self.requirements['home1'].keys()
                                if self.requirements['home1'][k] is not None]))
-        for c in fromHome.calendars():
+        fromCalendars = yield fromHome.calendars()
+        for c in fromCalendars:
             self.assertPropertiesSimilar(
-                c, toHome.calendarWithName(c.name()),
+                c, (yield toHome.calendarWithName(c.name())),
                 builtinProperties
             )
         self.assertPropertiesSimilar(fromHome, toHome, builtinProperties)
@@ -175,55 +191,58 @@
         "stubbed out, as migration only needs to go from file->sql currently")
 
 
-
-
     @inlineCallbacks
     def test_homeProvisioningConcurrency(self):
         """
-        Test that two concurrent attempts to provision a calendar home do not cause a race-condition
-        whereby the second commit results in a second INSERT that violates a unique constraint. Also verify
-        that, whilst the two provisioning attempts are happening and doing various lock operations, that we
-        do not block other reads of the table.
+        Test that two concurrent attempts to provision a calendar home do not
+        cause a race-condition whereby the second commit results in a second
+        C{INSERT} that violates a unique constraint. Also verify that, while
+        the two provisioning attempts are happening and doing various lock
+        operations, that we do not block other reads of the table.
         """
 
-        calendarStore1 = yield buildStore(self, self.notifierFactory)
-        calendarStore2 = yield buildStore(self, self.notifierFactory)
-        calendarStore3 = yield buildStore(self, self.notifierFactory)
+        calendarStore = yield buildStore(self, self.notifierFactory)
 
-        txn1 = calendarStore1.newTransaction()
-        txn2 = calendarStore2.newTransaction()
-        txn3 = calendarStore3.newTransaction()
-        
-        # Provision one home now - we will use this to later verify we can do reads of
-        # existing data in the table
-        home_uid2 = txn3.homeWithUID(ECALENDARTYPE, "uid2", create=True)
+        txn1 = calendarStore.newTransaction()
+        txn2 = calendarStore.newTransaction()
+        txn3 = calendarStore.newTransaction()
+
+        # Provision one home now - we will use this to later verify we can do
+        # reads of existing data in the table
+        home_uid2 = yield txn3.homeWithUID(ECALENDARTYPE, "uid2", create=True)
         self.assertNotEqual(home_uid2, None)
         txn3.commit()
 
-        home_uid1_1 = txn1.homeWithUID(ECALENDARTYPE, "uid1", create=True)
-        
+        home_uid1_1 = yield txn1.homeWithUID(
+            ECALENDARTYPE, "uid1", create=True
+        )
+
+        @inlineCallbacks
         def _defer_home_uid1_2():
-            home_uid1_2 = txn2.homeWithUID(ECALENDARTYPE, "uid1", create=True)
-            txn2.commit()
-            return home_uid1_2
-        d1 = deferToThread(_defer_home_uid1_2)
-        
+            home_uid1_2 = yield txn2.homeWithUID(
+                ECALENDARTYPE, "uid1", create=True
+            )
+            yield txn2.commit()
+            returnValue(home_uid1_2)
+        d1 = _defer_home_uid1_2()
+
+        @inlineCallbacks
         def _pause_home_uid1_1():
-            time.sleep(1)
-            txn1.commit()
-        d2 = deferToThread(_pause_home_uid1_1)
-        
+            yield deferLater(reactor, 1.0, lambda : None)
+            yield txn1.commit()
+        d2 = _pause_home_uid1_1()
+
         # Verify that we can still get to the existing home - i.e. the lock
         # on the table allows concurrent reads
-        txn4 = calendarStore3.newTransaction()
-        home_uid2 = txn4.homeWithUID(ECALENDARTYPE, "uid2", create=True)
+        txn4 = calendarStore.newTransaction()
+        home_uid2 = yield txn4.homeWithUID(ECALENDARTYPE, "uid2", create=True)
         self.assertNotEqual(home_uid2, None)
         txn4.commit()
-        
+
         # Now do the concurrent provision attempt
         yield d2
         home_uid1_2 = yield d1
-        
+
         self.assertNotEqual(home_uid1_1, None)
         self.assertNotEqual(home_uid1_2, None)
 
@@ -240,21 +259,22 @@
 
         # Provision the home now
         txn = calendarStore1.newTransaction()
-        home = txn.homeWithUID(ECALENDARTYPE, "uid1", create=True)
+        home = yield txn.homeWithUID(ECALENDARTYPE, "uid1", create=True)
         self.assertNotEqual(home, None)
         txn.commit()
 
         txn1 = calendarStore1.newTransaction()
         txn2 = calendarStore2.newTransaction()
 
-        home1 = txn1.homeWithUID(ECALENDARTYPE, "uid1", create=True)
-        home2 = txn2.homeWithUID(ECALENDARTYPE, "uid1", create=True)
-        
-        adbk1 = home1.calendarWithName("calendar")
-        adbk2 = home2.calendarWithName("calendar")
-        
+        home1 = yield txn1.homeWithUID(ECALENDARTYPE, "uid1", create=True)
+        home2 = yield txn2.homeWithUID(ECALENDARTYPE, "uid1", create=True)
+
+        adbk1 = yield home1.calendarWithName("calendar")
+        adbk2 = yield home2.calendarWithName("calendar")
+
+        @inlineCallbacks
         def _defer1():
-            adbk1.createObjectResourceWithName("1.ics", VComponent.fromString(
+            yield adbk1.createObjectResourceWithName("1.ics", VComponent.fromString(
     "BEGIN:VCALENDAR\r\n"
       "VERSION:2.0\r\n"
       "PRODID:-//Apple Inc.//iCal 4.0.1//EN\r\n"
@@ -294,11 +314,12 @@
       "END:VEVENT\r\n"
     "END:VCALENDAR\r\n"
             ))
-            txn1.commit()
-        d1 = deferToThread(_defer1)
-            
+            yield txn1.commit()
+        d1 = _defer1()
+
+        @inlineCallbacks
         def _defer2():
-            adbk2.createObjectResourceWithName("2.ics", VComponent.fromString(
+            yield adbk2.createObjectResourceWithName("2.ics", VComponent.fromString(
     "BEGIN:VCALENDAR\r\n"
       "VERSION:2.0\r\n"
       "PRODID:-//Apple Inc.//iCal 4.0.1//EN\r\n"
@@ -338,8 +359,8 @@
       "END:VEVENT\r\n"
     "END:VCALENDAR\r\n"
             ))
-            txn2.commit()
-        d2 = deferToThread(_defer2)
+            yield txn2.commit()
+        d2 = _defer2()
 
         yield d1
         yield d2

Modified: CalendarServer/trunk/txdav/caldav/datastore/test/test_util.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/test/test_util.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/txdav/caldav/datastore/test/test_util.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -18,6 +18,7 @@
 Tests for txdav.caldav.datastore.util.
 """
 
+from twisted.internet.defer import inlineCallbacks
 from twistedcaldav.ical import Component
 from twistedcaldav.test.util import TestCase
 from txdav.caldav.datastore.util import dropboxIDFromCalendarObject
@@ -31,19 +32,20 @@
         """
         Fake object resource to work with tests.
         """
-        
+
         def __init__(self, data):
-            
+
             self.ical = Component.fromString(data)
-            
+
         def component(self):
             return self.ical
-    
+
         def uid(self):
             return self.ical.resourceUID()
 
+
+    @inlineCallbacks
     def test_noAttachOrXdash(self):
-
         resource = DropboxIDTests.FakeCalendarResource("""BEGIN:VCALENDAR
 VERSION:2.0
 BEGIN:VEVENT
@@ -54,9 +56,14 @@
 END:VEVENT
 END:VCALENDAR
 """)
-        
-        self.assertEquals(dropboxIDFromCalendarObject(resource), "12345-67890.dropbox")
 
+        self.assertEquals(
+            (yield dropboxIDFromCalendarObject(resource)),
+            "12345-67890.dropbox"
+        )
+
+
+    @inlineCallbacks
     def test_okXdash(self):
 
         resource = DropboxIDTests.FakeCalendarResource("""BEGIN:VCALENDAR
@@ -70,11 +77,15 @@
 END:VEVENT
 END:VCALENDAR
 """)
-        
-        self.assertEquals(dropboxIDFromCalendarObject(resource), "12345-67890X.dropbox")
 
+        self.assertEquals(
+            (yield dropboxIDFromCalendarObject(resource)),
+            "12345-67890X.dropbox"
+        )
+
+
+    @inlineCallbacks
     def test_badXdash(self):
-
         resource = DropboxIDTests.FakeCalendarResource("""BEGIN:VCALENDAR
 VERSION:2.0
 BEGIN:VEVENT
@@ -86,9 +97,11 @@
 END:VEVENT
 END:VCALENDAR
 """)
-        
-        self.assertEquals(dropboxIDFromCalendarObject(resource), "")
 
+        self.assertEquals( (yield dropboxIDFromCalendarObject(resource)), "")
+
+
+    @inlineCallbacks
     def test_okAttach(self):
 
         resource = DropboxIDTests.FakeCalendarResource("""BEGIN:VCALENDAR
@@ -102,9 +115,14 @@
 END:VEVENT
 END:VCALENDAR
 """)
-        
-        self.assertEquals(dropboxIDFromCalendarObject(resource), "12345-67890Y.dropbox")
 
+        self.assertEquals(
+            (yield dropboxIDFromCalendarObject(resource)),
+            "12345-67890Y.dropbox"
+        )
+
+
+    @inlineCallbacks
     def test_badAttach(self):
 
         resource = DropboxIDTests.FakeCalendarResource("""BEGIN:VCALENDAR
@@ -118,9 +136,14 @@
 END:VEVENT
 END:VCALENDAR
 """)
-        
-        self.assertEquals(dropboxIDFromCalendarObject(resource), "12345-67890.dropbox")
 
+        self.assertEquals(
+            (yield dropboxIDFromCalendarObject(resource)),
+            "12345-67890.dropbox"
+        )
+
+
+    @inlineCallbacks
     def test_inlineAttach(self):
 
         resource = DropboxIDTests.FakeCalendarResource("""BEGIN:VCALENDAR
@@ -134,9 +157,14 @@
 END:VEVENT
 END:VCALENDAR
 """)
-        
-        self.assertEquals(dropboxIDFromCalendarObject(resource), "12345-67890.dropbox")
 
+        self.assertEquals(
+            (yield dropboxIDFromCalendarObject(resource)),
+            "12345-67890.dropbox"
+        )
+
+
+    @inlineCallbacks
     def test_multipleAttach(self):
 
         resource = DropboxIDTests.FakeCalendarResource("""BEGIN:VCALENDAR
@@ -152,9 +180,14 @@
 END:VEVENT
 END:VCALENDAR
 """)
-        
-        self.assertEquals(dropboxIDFromCalendarObject(resource), "12345-67890Z.dropbox")
 
+        self.assertEquals(
+            (yield dropboxIDFromCalendarObject(resource)),
+            "12345-67890Z.dropbox"
+        )
+
+
+    @inlineCallbacks
     def test_okAttachRecurring(self):
 
         resource = DropboxIDTests.FakeCalendarResource("""BEGIN:VCALENDAR
@@ -176,10 +209,14 @@
 END:VEVENT
 END:VCALENDAR
 """)
-        
-        self.assertEquals(dropboxIDFromCalendarObject(resource), "12345-67890Y.dropbox")
 
+        self.assertEquals(
+            (yield dropboxIDFromCalendarObject(resource)),
+            "12345-67890Y.dropbox"
+        )
 
+
+    @inlineCallbacks
     def test_okAttachAlarm(self):
 
         resource = DropboxIDTests.FakeCalendarResource("""BEGIN:VCALENDAR
@@ -198,6 +235,10 @@
 END:VEVENT
 END:VCALENDAR
 """)
-        
-        self.assertEquals(dropboxIDFromCalendarObject(resource), "12345-67890.dropbox")
 
+        self.assertEquals(
+            (yield dropboxIDFromCalendarObject(resource)), 
+            "12345-67890.dropbox"
+        )
+
+

Modified: CalendarServer/trunk/txdav/caldav/datastore/util.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/util.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/txdav/caldav/datastore/util.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -1,3 +1,4 @@
+# -*- test-case-name: txdav.caldav.datastore.test.test_util -*-
 ##
 # Copyright (c) 2010 Apple Inc. All rights reserved.
 #
@@ -18,7 +19,7 @@
 Utility logic common to multiple backend implementations.
 """
 
-from twisted.internet.defer import inlineCallbacks, Deferred
+from twisted.internet.defer import inlineCallbacks, Deferred, returnValue
 from twisted.internet.protocol import Protocol
 
 from twext.python.vcomponent import InvalidICalendarDataError
@@ -69,6 +70,7 @@
         raise InvalidObjectResourceError(e)
 
 
+ at inlineCallbacks
 def dropboxIDFromCalendarObject(calendarObject):
     """
     Helper to implement L{ICalendarObject.dropboxID}.
@@ -78,16 +80,17 @@
     """
 
     # Try "X-APPLE-DROPBOX" first
-    dropboxProperty = calendarObject.component(
-        ).getFirstPropertyInAnyComponent("X-APPLE-DROPBOX")
+    dropboxProperty = (yield calendarObject.component(
+        )).getFirstPropertyInAnyComponent("X-APPLE-DROPBOX")
     if dropboxProperty is not None:
         componentDropboxID = dropboxProperty.value().split("/")[-1]
-        return componentDropboxID
+        returnValue(componentDropboxID)
 
     # Now look at each ATTACH property and see if it might be a dropbox item
     # and if so extract the id from that
 
-    attachments = calendarObject.component().getAllPropertiesInAnyComponent(
+    attachments = (yield calendarObject.component()
+        ).getAllPropertiesInAnyComponent(
         "ATTACH",
         depth=1,
     )
@@ -99,11 +102,11 @@
             segments = attachment.value().split("/")
             try:
                 if segments[-3] == "dropbox":
-                    return segments[-2]
+                    returnValue(segments[-2])
             except IndexError:
                 pass
 
-    return calendarObject.uid() + ".dropbox"
+    returnValue(calendarObject.uid() + ".dropbox")
 
 
 @inlineCallbacks
@@ -119,24 +122,24 @@
     @return: a L{Deferred} which fires when the calendar has migrated.
     """
     outCalendar.properties().update(inCalendar.properties())
-    for calendarObject in inCalendar.calendarObjects():
+    for calendarObject in (yield inCalendar.calendarObjects()):
         
         try:
-            outCalendar.createCalendarObjectWithName(
+            yield outCalendar.createCalendarObjectWithName(
                 calendarObject.name(),
-                calendarObject.component()) # XXX WRONG SHOULD CALL getComponent
-    
+                (yield calendarObject.component())) # XXX WRONG SHOULD CALL getComponent
+
             # Only the owner's properties are migrated, since previous releases of
             # calendar server didn't have per-user properties.
-            outObject = outCalendar.calendarObjectWithName(
+            outObject = yield outCalendar.calendarObjectWithName(
                 calendarObject.name())
             outObject.properties().update(calendarObject.properties())
     
             # Migrate attachments.
-            for attachment in calendarObject.attachments():
+            for attachment in (yield calendarObject.attachments()):
                 name = attachment.name()
                 ctype = attachment.contentType()
-                transport = outObject.createAttachmentWithName(name, ctype)
+                transport = yield outObject.createAttachmentWithName(name, ctype)
                 proto =_AttachmentMigrationProto(transport)
                 attachment.retrieve(proto)
                 yield proto.done
@@ -184,15 +187,16 @@
         (from a calendar in C{inHome}) and returns a L{VComponent} (to store in
         a calendar in outHome).
     """
-    outHome.removeCalendarWithName("calendar")
-    outHome.removeCalendarWithName("inbox")
+    yield outHome.removeCalendarWithName("calendar")
+    yield outHome.removeCalendarWithName("inbox")
     outHome.properties().update(inHome.properties())
-    for calendar in inHome.calendars():
+    inCalendars = yield inHome.calendars()
+    for calendar in inCalendars:
         name = calendar.name()
         if name == "outbox":
             continue
-        outHome.createCalendarWithName(name)
-        outCalendar = outHome.calendarWithName(name)
+        yield outHome.createCalendarWithName(name)
+        outCalendar = yield outHome.calendarWithName(name)
         try:
             yield _migrateCalendar(calendar, outCalendar, getComponent)
         except InternalDataStoreError:

Modified: CalendarServer/trunk/txdav/caldav/icalendarstore.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/icalendarstore.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/txdav/caldav/icalendarstore.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -57,8 +57,8 @@
         If C{create} is C{True}, create the calendar home if it doesn't
         already exist.
 
-        @return: an L{ICalendarHome} or C{None} if no such calendar
-            home exists.
+        @return: a L{Deferred} which fires with L{ICalendarHome} or C{None} if
+            no such calendar home exists.
         """
 
 
@@ -202,8 +202,9 @@
         in this calendar.
 
         @param uid: a string.
-        @return: an L{ICalendarObject} or C{None} if no such calendar
-            object exists.
+
+        @return: a L{Deferred} firing an L{ICalendarObject} or C{None} if no
+            such calendar object exists.
         """
 
     def createCalendarObjectWithName(name, component):

Modified: CalendarServer/trunk/txdav/carddav/datastore/file.py
===================================================================
--- CalendarServer/trunk/txdav/carddav/datastore/file.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/txdav/carddav/datastore/file.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -33,7 +33,7 @@
 
 from twistedcaldav.sharing import InvitesDatabase
 from twistedcaldav.vcard import Component as VComponent, InvalidVCardDataError
-from twistedcaldav.vcardindex import AddressBookIndex as OldIndex
+from txdav.carddav.datastore.index_file import AddressBookIndex as OldIndex
 
 from txdav.carddav.datastore.util import validateAddressBookComponent
 from txdav.carddav.iaddressbookstore import IAddressBook, IAddressBookObject

Copied: CalendarServer/trunk/txdav/carddav/datastore/index_file.py (from rev 6445, CalendarServer/branches/users/glyph/more-deferreds-7/txdav/carddav/datastore/index_file.py)
===================================================================
--- CalendarServer/trunk/txdav/carddav/datastore/index_file.py	                        (rev 0)
+++ CalendarServer/trunk/txdav/carddav/datastore/index_file.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -0,0 +1,696 @@
+##
+# Copyright (c) 2005-2009 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+"""
+CardDAV Index.
+
+This API is considered private to static.py and is therefore subject to
+change.
+"""
+
+__all__ = [
+    "AddressBookIndex",
+]
+
+import datetime
+import os
+import time
+import hashlib
+
+try:
+    import sqlite3 as sqlite
+except ImportError:
+    from pysqlite2 import dbapi2 as sqlite
+
+from twisted.internet.defer import maybeDeferred
+
+from twistedcaldav import carddavxml
+from txdav.common.icommondatastore import SyncTokenValidException,\
+    ReservationError
+from twistedcaldav.query import addressbookquery
+from twistedcaldav.sql import AbstractSQLDatabase
+from twistedcaldav.sql import db_prefix
+from twistedcaldav.vcard import Component
+
+from twext.python.log import Logger, LoggingMixIn
+from twistedcaldav.config import config
+from twistedcaldav.memcachepool import CachePoolUserMixIn
+
+log = Logger()
+
+db_basename = db_prefix + "sqlite"
+schema_version = "2"
+
+def wrapInDeferred(f):
+    def _(*args, **kwargs):
+        return maybeDeferred(f, *args, **kwargs)
+
+    return _
+
+
+class MemcachedUIDReserver(CachePoolUserMixIn, LoggingMixIn):
+    def __init__(self, index, cachePool=None):
+        self.index = index
+        self._cachePool = cachePool
+
+    def _key(self, uid):
+        return 'reservation:%s' % (
+            hashlib.md5('%s:%s' % (uid,
+                                   self.index.resource.fp.path)).hexdigest())
+
+    def reserveUID(self, uid):
+        uid = uid.encode('utf-8')
+        self.log_debug("Reserving UID %r @ %r" % (
+                uid,
+                self.index.resource.fp.path))
+
+        def _handleFalse(result):
+            if result is False:
+                raise ReservationError(
+                    "UID %s already reserved for address book collection %s."
+                    % (uid, self.index.resource)
+                    )
+
+        d = self.getCachePool().add(self._key(uid),
+                                    'reserved',
+                                    expireTime=config.UIDReservationTimeOut)
+        d.addCallback(_handleFalse)
+        return d
+
+
+    def unreserveUID(self, uid):
+        uid = uid.encode('utf-8')
+        self.log_debug("Unreserving UID %r @ %r" % (
+                uid,
+                self.index.resource.fp.path))
+
+        def _handleFalse(result):
+            if result is False:
+                raise ReservationError(
+                    "UID %s is not reserved for address book collection %s."
+                    % (uid, self.index.resource)
+                    )
+
+        d =self.getCachePool().delete(self._key(uid))
+        d.addCallback(_handleFalse)
+        return d
+
+
+    def isReservedUID(self, uid):
+        uid = uid.encode('utf-8')
+        self.log_debug("Is reserved UID %r @ %r" % (
+                uid,
+                self.index.resource.fp.path))
+
+        def _checkValue((flags, value)):
+            if value is None:
+                return False
+            else:
+                return True
+
+        d = self.getCachePool().get(self._key(uid))
+        d.addCallback(_checkValue)
+        return d
+
+
+
+class SQLUIDReserver(object):
+    def __init__(self, index):
+        self.index = index
+
+    @wrapInDeferred
+    def reserveUID(self, uid):
+        """
+        Reserve a UID for this index's resource.
+        @param uid: the UID to reserve
+        @raise ReservationError: if C{uid} is already reserved
+        """
+
+        try:
+            self.index._db_execute("insert into RESERVED (UID, TIME) values (:1, :2)", uid, datetime.datetime.now())
+            self.index._db_commit()
+        except sqlite.IntegrityError:
+            self.index._db_rollback()
+            raise ReservationError(
+                "UID %s already reserved for address book collection %s."
+                % (uid, self.index.resource)
+            )
+        except sqlite.Error, e:
+            log.err("Unable to reserve UID: %s", (e,))
+            self.index._db_rollback()
+            raise
+
+    def unreserveUID(self, uid):
+        """
+        Unreserve a UID for this index's resource.
+        @param uid: the UID to reserve
+        @raise ReservationError: if C{uid} is not reserved
+        """
+
+        def _cb(result):
+            if result == False:
+                raise ReservationError(
+                    "UID %s is not reserved for address book collection %s."
+                    % (uid, self.index.resource)
+                    )
+            else:
+                try:
+                    self.index._db_execute(
+                        "delete from RESERVED where UID = :1", uid)
+                    self.index._db_commit()
+                except sqlite.Error, e:
+                    log.err("Unable to unreserve UID: %s", (e,))
+                    self.index._db_rollback()
+                    raise
+
+        d = self.isReservedUID(uid)
+        d.addCallback(_cb)
+        return d
+
+
+    @wrapInDeferred
+    def isReservedUID(self, uid):
+        """
+        Check to see whether a UID is reserved.
+        @param uid: the UID to check
+        @return: True if C{uid} is reserved, False otherwise.
+        """
+
+        rowiter = self.index._db_execute("select UID, TIME from RESERVED where UID = :1", uid)
+        for uid, attime in rowiter:
+            # Double check that the time is within a reasonable period of now
+            # otherwise we probably have a stale reservation
+            tm = time.strptime(attime[:19], "%Y-%m-%d %H:%M:%S")
+            dt = datetime.datetime(year=tm.tm_year, month=tm.tm_mon, day=tm.tm_mday, hour=tm.tm_hour, minute=tm.tm_min, second = tm.tm_sec)
+            if datetime.datetime.now() - dt > datetime.timedelta(seconds=config.UIDReservationTimeOut):
+                try:
+                    self.index._db_execute("delete from RESERVED where UID = :1", uid)
+                    self.index._db_commit()
+                except sqlite.Error, e:
+                    log.err("Unable to unreserve UID: %s", (e,))
+                    self.index._db_rollback()
+                    raise
+                return False
+            else:
+                return True
+
+        return False
+
+class AddressBookIndex(AbstractSQLDatabase):
+    """
+    AddressBook collection index abstract base class that defines the apis for the index.
+    """
+
+    def __init__(self, resource):
+        """
+        @param resource: the L{CalDAVResource} resource to
+            index. C{resource} must be an addressbook collection (ie.
+            C{resource.isAddressBookCollection()} returns C{True}.)
+        """
+        assert resource.isAddressBookCollection(), "non-addressbook collection resource %s has no index." % (resource,)
+        self.resource = resource
+        db_filename = os.path.join(self.resource.fp.path, db_basename)
+        super(AddressBookIndex, self).__init__(db_filename, False)
+
+        if (
+            hasattr(config, "Memcached") and
+            config.Memcached.Pools.Default.ClientEnabled
+        ):
+            self.reserver = MemcachedUIDReserver(self)
+        else:
+            self.reserver = SQLUIDReserver(self)
+
+    def create(self):
+        """
+        Create the index and initialize it.
+        """
+        self._db()
+
+    def recreate(self):
+        """
+        Delete the database and re-create it
+        """
+        try:
+            os.remove(self.dbpath)
+        except OSError:
+            pass
+        self.create()
+
+    #
+    # A dict of sets. The dict keys are address book collection paths,
+    # and the sets contains reserved UIDs for each path.
+    #
+    
+    def reserveUID(self, uid):
+        return self.reserver.reserveUID(uid)
+    
+    def unreserveUID(self, uid):
+        return self.reserver.unreserveUID(uid)
+    
+    def isReservedUID(self, uid):
+        return self.reserver.isReservedUID(uid)
+        
+    def isAllowedUID(self, uid, *names):
+        """
+        Checks to see whether to allow an operation which would add the
+        specified UID to the index.  Specifically, the operation may not
+        violate the constraint that UIDs must be unique.
+        @param uid: the UID to check
+        @param names: the names of resources being replaced or deleted by the
+            operation; UIDs associated with these resources are not checked.
+        @return: True if the UID is not in the index and is not reserved,
+            False otherwise.
+        """
+        rname = self.resourceNameForUID(uid)
+        return (rname is None or rname in names)
+ 
+    def resourceNamesForUID(self, uid):
+        """
+        Looks up the names of the resources with the given UID.
+        @param uid: the UID of the resources to look up.
+        @return: a list of resource names
+        """
+        names = self._db_values_for_sql("select NAME from RESOURCE where UID = :1", uid)
+
+        #
+        # Check that each name exists as a child of self.resource.  If not, the
+        # resource record is stale.
+        #
+        resources = []
+        for name in names:
+            name_utf8 = name.encode("utf-8")
+            if name is not None and self.resource.getChild(name_utf8) is None:
+                # Clean up
+                log.err("Stale resource record found for child %s with UID %s in %s" % (name, uid, self.resource))
+                self._delete_from_db(name, uid, False)
+                self._db_commit()
+            else:
+                resources.append(name_utf8)
+
+        return resources
+
+    def resourceNameForUID(self, uid):
+        """
+        Looks up the name of the resource with the given UID.
+        @param uid: the UID of the resource to look up.
+        @return: If the resource is found, its name; C{None} otherwise.
+        """
+        result = None
+
+        for name in self.resourceNamesForUID(uid):
+            assert result is None, "More than one resource with UID %s in address book collection %r" % (uid, self)
+            result = name
+            
+        return result
+
+    def resourceUIDForName(self, name):
+        """
+        Looks up the UID of the resource with the given name.
+        @param name: the name of the resource to look up.
+        @return: If the resource is found, the UID of the resource; C{None}
+            otherwise.
+        """
+        uid = self._db_value_for_sql("select UID from RESOURCE where NAME = :1", name)
+
+        return uid
+
+    def addResource(self, name, vcard, fast=False):
+        """
+        Adding or updating an existing resource.
+        To check for an update we attempt to get an existing UID
+        for the resource name. If present, then the index entries for
+        that UID are removed. After that the new index entries are added.
+        @param name: the name of the resource to add.
+        @param vCard: a L{Component} object representing the resource
+            contents.
+        @param fast: if C{True} do not do commit, otherwise do commit.
+        """
+        oldUID = self.resourceUIDForName(name)
+        if oldUID is not None:
+            self._delete_from_db(name, oldUID, False)
+        self._add_to_db(name, vcard)
+        if not fast:
+            self._db_commit()
+
+    def deleteResource(self, name):
+        """
+        Remove this resource from the index.
+        @param name: the name of the resource to add.
+        @param uid: the UID of the vcard component in the resource.
+        """
+        uid = self.resourceUIDForName(name)
+        if uid is not None:
+            self._delete_from_db(name, uid)
+            self._db_commit()
+    
+    def resourceExists(self, name):
+        """
+        Determines whether the specified resource name exists in the index.
+        @param name: the name of the resource to test
+        @return: True if the resource exists, False if not
+        """
+        uid = self._db_value_for_sql("select UID from RESOURCE where NAME = :1", name)
+        return uid is not None
+    
+    def resourcesExist(self, names):
+        """
+        Determines whether the specified resource name exists in the index.
+        @param names: a C{list} containing the names of the resources to test
+        @return: a C{list} of all names that exist
+        """
+        statement = "select NAME from RESOURCE where NAME in ("
+        for ctr, ignore_name in enumerate(names):
+            if ctr != 0:
+                statement += ", "
+            statement += ":%s" % (ctr,)
+        statement += ")"
+        results = self._db_values_for_sql(statement, *names)
+        return results
+    
+    def whatchanged(self, revision, depth):
+
+        results = [(name.encode("utf-8"), deleted) for name, deleted in self._db_execute("select NAME, DELETED from REVISIONS where REVISION > :1", revision)]
+        results.sort(key=lambda x:x[1])
+        
+        changed = []
+        deleted = []
+        for name, wasdeleted in results:
+            if name:
+                if wasdeleted == 'Y':
+                    if revision:
+                        deleted.append(name)
+                else:
+                    changed.append(name)
+            else:
+                raise SyncTokenValidException
+        
+        return changed, deleted,
+
+    def lastRevision(self):
+        return self._db_value_for_sql(
+            "select REVISION from REVISION_SEQUENCE"
+        )
+
+    def bumpRevision(self, fast=False):
+        self._db_execute(
+            """
+            update REVISION_SEQUENCE set REVISION = REVISION + 1
+            """,
+        )
+        if not fast:
+            self._db_commit()
+        return self._db_value_for_sql(
+            """
+            select REVISION from REVISION_SEQUENCE
+            """,
+        )
+
+    def searchValid(self, filter):
+        if isinstance(filter, carddavxml.Filter):
+            qualifiers = addressbookquery.sqladdressbookquery(filter)
+        else:
+            qualifiers = None
+            
+        return qualifiers is not None
+
+    def search(self, filter):
+        """
+        Finds resources matching the given qualifiers.
+        @param filter: the L{Filter} for the addressbook-query to execute.
+        @return: an interable iterable of tuples for each resource matching the
+            given C{qualifiers}. The tuples are C{(name, uid, type)}, where
+            C{name} is the resource name, C{uid} is the resource UID, and
+            C{type} is the resource iCalendar component type.x
+        """
+        # FIXME: Don't forget to use maximum_future_expansion_duration when we
+        # start caching...
+        
+        # Make sure we have a proper Filter element and get the partial SQL statement to use.
+        if isinstance(filter, carddavxml.Filter):
+            qualifiers = addressbookquery.sqladdressbookquery(filter)
+        else:
+            qualifiers = None
+        if qualifiers is not None:
+            rowiter = self._db_execute("select DISTINCT RESOURCE.NAME, RESOURCE.UID" + qualifiers[0], *qualifiers[1])
+        else:
+            rowiter = self._db_execute("select NAME, UID from RESOURCE")
+            
+        for row in rowiter:
+            name = row[0]
+            if self.resource.getChild(name.encode("utf-8")):
+                yield row
+            else:
+                log.err("vCard resource %s is missing from %s. Removing from index."
+                        % (name, self.resource))
+                self.deleteResource(name, None)
+
+    def bruteForceSearch(self):
+        """
+        List the whole index and tests for existence, updating the index
+        @return: all resources in the index
+        """
+        # List all resources
+        rowiter = self._db_execute("select NAME, UID from RESOURCE")
+
+        # Check result for missing resources:
+
+        for row in rowiter:
+            name = row[0]
+            if self.resource.getChild(name.encode("utf-8")):
+                yield row
+            else:
+                log.err("AddressBook resource %s is missing from %s. Removing from index."
+                        % (name, self.resource))
+                self.deleteResource(name)
+
+
+    def _db_version(self):
+        """
+        @return: the schema version assigned to this index.
+        """
+        return schema_version
+        
+    def _db_type(self):
+        """
+        @return: the collection type assigned to this index.
+        """
+        return "AddressBook"
+        
+    def _db_init_data_tables(self, q):
+        """
+        Initialise the underlying database tables.
+        @param q:           a database cursor to use.
+        """
+        
+        # Create database where the RESOURCE table has unique UID column.
+        self._db_init_data_tables_base(q, True)
+
+    def _db_init_data_tables_base(self, q, uidunique):
+        """
+        Initialise the underlying database tables.
+        @param q:           a database cursor to use.
+        """
+        #
+        # RESOURCE table is the primary index table
+        #   NAME: Last URI component (eg. <uid>.ics, RESOURCE primary key)
+        #   UID: iCalendar UID (may or may not be unique)
+        #
+        q.execute(
+            """
+            create table RESOURCE (
+                NAME           text unique,
+                UID            text unique
+            )
+            """
+        )
+
+        #
+        # REVISIONS table tracks changes
+        #   NAME: Last URI component (eg. <uid>.ics, RESOURCE primary key)
+        #   REVISION: revision number
+        #   WASDELETED: Y if revision deleted, N if added or changed
+        #
+        q.execute(
+            """
+            create table REVISION_SEQUENCE (
+                REVISION        integer
+            )
+            """
+        )
+        q.execute(
+            """
+            insert into REVISION_SEQUENCE (REVISION) values (0)
+            """
+        )
+        q.execute(
+            """
+            create table REVISIONS (
+                NAME            text unique,
+                REVISION        integer default 0,
+                DELETED         text(1) default "N"
+            )
+            """
+        )
+        q.execute(
+            """
+            create index REVISION on REVISIONS (REVISION)
+            """
+        )
+
+        #
+        # RESERVED table tracks reserved UIDs
+        #   UID: The UID being reserved
+        #   TIME: When the reservation was made
+        #
+        q.execute(
+            """
+            create table RESERVED (
+                UID  text unique,
+                TIME date
+            )
+            """
+        )
+
+    def _db_recreate(self, do_commit=True):
+        """
+        Re-create the database tables from existing address book data.
+        """
+        
+        #
+        # Populate the DB with data from already existing resources.
+        # This allows for index recovery if the DB file gets
+        # deleted.
+        #
+        fp = self.resource.fp
+        for name in fp.listdir():
+            if name.startswith("."):
+                continue
+
+            try:
+                stream = fp.child(name).open()
+            except (IOError, OSError), e:
+                log.err("Unable to open resource %s: %s" % (name, e))
+                continue
+
+            try:
+                # FIXME: This is blocking I/O
+                try:
+                    vcard = Component.fromStream(stream)
+                    vcard.validForCardDAV()
+                except ValueError:
+                    log.err("Non-addressbook resource: %s" % (name,))
+                else:
+                    #log.msg("Indexing resource: %s" % (name,))
+                    self.addResource(name, vcard, True)
+            finally:
+                stream.close()
+        
+        # Do commit outside of the loop for better performance
+        if do_commit:
+            self._db_commit()
+
+    def _db_can_upgrade(self, old_version):
+        """
+        Can we do an in-place upgrade
+        """
+        
+        # v2 is a minor change
+        return True
+
+    def _db_upgrade_data_tables(self, q, old_version):
+        """
+        Upgrade the data from an older version of the DB.
+        """
+
+        # When going to version 2+ all we need to do is add revision table and index
+        if old_version < 2:
+            q.execute(
+                """
+                create table REVISION_SEQUENCE (
+                    REVISION        integer
+                )
+                """
+            )
+            q.execute(
+                """
+                insert into REVISION_SEQUENCE (REVISION) values (0)
+                """
+            )
+            q.execute(
+                """
+                create table REVISIONS (
+                    NAME            text unique,
+                    REVISION        integer default 0,
+                    CREATEDREVISION integer default 0,
+                    WASDELETED      text(1) default "N"
+                )
+                """
+            )
+            q.execute(
+                """
+                create index REVISION on REVISIONS (REVISION)
+                """
+            )
+            
+            self._db_execute(
+                """
+                insert into REVISIONS (NAME)
+                select NAME from RESOURCE
+                """
+            )
+                
+
+    def _add_to_db(self, name, vcard, cursor = None):
+        """
+        Records the given address book resource in the index with the given name.
+        Resource names and UIDs must both be unique; only one resource name may
+        be associated with any given UID and vice versa.
+        NB This method does not commit the changes to the db - the caller
+        MUST take care of that
+        @param name: the name of the resource to add.
+        @param vcard: a L{AddressBook} object representing the resource
+            contents.
+        """
+        uid = vcard.resourceUID()
+
+        self._db_execute(
+            """
+            insert into RESOURCE (NAME, UID)
+            values (:1, :2)
+            """, name, uid,
+        )
+
+        self._db_execute(
+            """
+            insert or replace into REVISIONS (NAME, REVISION, DELETED)
+            values (:1, :2, :3)
+            """, name, self.bumpRevision(fast=True), 'N',
+        )
+    
+    def _delete_from_db(self, name, uid, dorevision=True):
+        """
+        Deletes the specified entry from all dbs.
+        @param name: the name of the resource to delete.
+        @param uid: the uid of the resource to delete.
+        """
+        self._db_execute("delete from RESOURCE where NAME = :1", name)
+        if dorevision:
+            self._db_execute(
+                """
+                update REVISIONS SET REVISION = :1, DELETED = :2
+                where NAME = :3
+                """, self.bumpRevision(fast=True), 'Y', name
+            )


Property changes on: CalendarServer/trunk/txdav/carddav/datastore/index_file.py
___________________________________________________________________
Added: svn:mergeinfo
   + /CalendarServer/branches/config-separation/txdav/carddav/datastore/index_file.py:4379-4443
/CalendarServer/branches/egg-info-351/txdav/carddav/datastore/index_file.py:4589-4625
/CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/index_file.py:6167-6191
/CalendarServer/branches/new-store/txdav/carddav/datastore/index_file.py:5594-5934
/CalendarServer/branches/new-store-no-caldavfile/txdav/carddav/datastore/index_file.py:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2/txdav/carddav/datastore/index_file.py:5936-5981
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/txdav/carddav/datastore/index_file.py:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/txdav/carddav/datastore/index_file.py:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591/txdav/carddav/datastore/index_file.py:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/txdav/carddav/datastore/index_file.py:4465-4957
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/txdav/carddav/datastore/index_file.py:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/txdav/carddav/datastore/index_file.py:5188-5440
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/carddav/datastore/index_file.py:4971-5080
/CalendarServer/branches/users/glyph/more-deferreds-6/txdav/carddav/datastore/index_file.py:6322-6368
/CalendarServer/branches/users/glyph/more-deferreds-7/txdav/carddav/datastore/index_file.py:6369-6445
/CalendarServer/branches/users/glyph/sendfdport/txdav/carddav/datastore/index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sql-store/txdav/carddav/datastore/index_file.py:5929-6073
/CalendarServer/branches/users/glyph/use-system-twisted/txdav/carddav/datastore/index_file.py:5084-5149
/CalendarServer/branches/users/sagen/locations-resources/txdav/carddav/datastore/index_file.py:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2/txdav/carddav/datastore/index_file.py:5052-5061
/CalendarServer/branches/users/sagen/resource-delegates-4038/txdav/carddav/datastore/index_file.py:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/txdav/carddav/datastore/index_file.py:4068-4075
/CalendarServer/branches/users/sagen/resources-2/txdav/carddav/datastore/index_file.py:5084-5093
/CalendarServer/branches/users/wsanchez/transations/txdav/carddav/datastore/index_file.py:5515-5593
/CalendarServer/trunk/twistedcaldav/vcardindex.py:6322-6394

Modified: CalendarServer/trunk/txdav/carddav/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/carddav/datastore/sql.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/txdav/carddav/datastore/sql.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -15,12 +15,20 @@
 # limitations under the License.
 ##
 
+"""
+SQL backend for CardDAV storage.
+"""
+
 __all__ = [
     "AddressBookHome",
     "AddressBook",
     "AddressBookObject",
 ]
 
+from zope.interface.declarations import implements
+
+from twisted.internet.defer import inlineCallbacks, returnValue
+
 from twext.web2.dav.element.rfc2518 import ResourceType
 from twext.web2.http_headers import MimeType
 
@@ -42,8 +50,8 @@
     ADDRESSBOOK_OBJECT_TABLE
 from txdav.base.propertystore.base import PropertyName
 
-from zope.interface.declarations import implements
 
+
 class AddressBookHome(CommonHome):
 
     implements(IAddressBookHome)
@@ -58,15 +66,19 @@
         super(AddressBookHome, self).__init__(transaction, ownerUID, resourceID, notifier)
         self._shares = SQLLegacyAddressBookShares(self)
 
+
     addressbooks = CommonHome.children
     listAddressbooks = CommonHome.listChildren
     addressbookWithName = CommonHome.childWithName
     createAddressBookWithName = CommonHome.createChildWithName
     removeAddressBookWithName = CommonHome.removeChildWithName
 
+
     def createdHome(self):
-        self.createAddressBookWithName("addressbook")
+        return self.createAddressBookWithName("addressbook")
 
+
+
 class AddressBook(CommonHomeChild):
     """
     File-based implementation of L{IAddressBook}.
@@ -99,13 +111,16 @@
         self._revisionsTable = ADDRESSBOOK_OBJECT_REVISIONS_TABLE
         self._objectTable = ADDRESSBOOK_OBJECT_TABLE
 
+
     @property
     def _addressbookHome(self):
         return self._home
 
+
     def resourceType(self):
         return ResourceType.addressbook #@UndefinedVariable
 
+
     ownerAddressBookHome = CommonHomeChild.ownerHome
     addressbookObjects = CommonHomeChild.objectResources
     listAddressbookObjects = CommonHomeChild.listObjectResources
@@ -128,43 +143,52 @@
             ),
         )
 
+
     def _doValidate(self, component):
         component.validForCardDAV()
 
+
     def contentType(self):
         """
         The content type of Addresbook objects is text/vcard.
         """
         return MimeType.fromString("text/vcard; charset=utf-8")
 
+
+
 class AddressBookObject(CommonObjectResource):
 
     implements(IAddressBookObject)
 
-    def __init__(self, name, addressbook, resid):
+    def __init__(self, name, addressbook, resid, uid):
 
-        super(AddressBookObject, self).__init__(name, addressbook, resid)
-
+        super(AddressBookObject, self).__init__(name, addressbook, resid, uid)
         self._objectTable = ADDRESSBOOK_OBJECT_TABLE
 
+
     @property
     def _addressbook(self):
         return self._parentCollection
 
+
     def addressbook(self):
         return self._addressbook
 
+
+    @inlineCallbacks
     def setComponent(self, component, inserting=False):
         validateAddressBookComponent(self, self._addressbook, component, inserting)
 
-        self.updateDatabase(component, inserting=inserting)
+        yield self.updateDatabase(component, inserting=inserting)
         if inserting:
-            self._addressbook._insertRevision(self._name)
+            yield self._addressbook._insertRevision(self._name)
         else:
-            self._addressbook._updateRevision(self._name)
+            yield self._addressbook._updateRevision(self._name)
 
         self._addressbook.notifyChanged()
 
+
+    @inlineCallbacks
     def updateDatabase(self, component, expand_until=None, reCreate=False, inserting=False):
         """
         Update the database tables for the new data being written.
@@ -178,7 +202,7 @@
 
         # ADDRESSBOOK_OBJECT table update
         if inserting:
-            self._resourceID = self._txn.execSQL(
+            self._resourceID = (yield self._txn.execSQL(
                 """
                 insert into ADDRESSBOOK_OBJECT
                 (ADDRESSBOOK_RESOURCE_ID, RESOURCE_NAME, VCARD_TEXT, VCARD_UID)
@@ -192,9 +216,9 @@
                     componentText,
                     component.resourceUID(),
                 ]
-            )[0][0]
+            ))[0][0]
         else:
-            self._txn.execSQL(
+            yield self._txn.execSQL(
                 """
                 update ADDRESSBOOK_OBJECT set
                 (VCARD_TEXT, VCARD_UID, MODIFIED)
@@ -209,31 +233,15 @@
                 ]
             )
 
+
+    @inlineCallbacks
     def component(self):
-        return VCard.fromString(self.vCardText())
+        returnValue(VCard.fromString((yield self.vCardText())))
 
-    def text(self):
-        if self._objectText is None:
-            text = self._txn.execSQL(
-                "select VCARD_TEXT from ADDRESSBOOK_OBJECT where "
-                "RESOURCE_ID = %s", [self._resourceID]
-            )[0][0]
-            self._objectText = text
-            return text
-        else:
-            return self._objectText
 
-    vCardText = text
+    vCardText = CommonObjectResource.text
 
-    def uid(self):
-        return self.component().resourceUID()
 
-    def name(self):
-        return self._name
-
-    def componentType(self):
-        return self.component().mainType()
-
     # IDataStoreResource
     def contentType(self):
         """

Modified: CalendarServer/trunk/txdav/carddav/datastore/test/common.py
===================================================================
--- CalendarServer/trunk/txdav/carddav/datastore/test/common.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/txdav/carddav/datastore/test/common.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -17,12 +17,8 @@
 """
 Tests for common addressbook store API functions.
 """
+from twisted.internet.defer import inlineCallbacks, returnValue, maybeDeferred
 
-from zope.interface.verify import verifyObject
-from zope.interface.exceptions import (
-    BrokenMethodImplementation, DoesNotImplement
-)
-
 from txdav.idav import IPropertyStore, IDataStore
 from txdav.base.propertystore.base import PropertyName
 
@@ -38,8 +34,10 @@
     IAddressBookObject, IAddressBookHome,
     IAddressBook, IAddressBookTransaction
 )
+
+from txdav.common.datastore.test.util import CommonCommonTests
+
 from twistedcaldav.vcard import Component as VComponent
-from twistedcaldav.notify import Notifier
 
 from twext.python.filepath import CachingFilePath as FilePath
 from twext.web2.dav import davxml
@@ -103,25 +101,8 @@
 )
 
 
-def assertProvides(testCase, interface, provider):
+class CommonTests(CommonCommonTests):
     """
-    Verify that C{provider} properly provides C{interface}
-
-    @type interface: L{zope.interface.Interface}
-    @type provider: C{provider}
-    """
-    try:
-        verifyObject(interface, provider)
-    except BrokenMethodImplementation, e:
-        testCase.fail(e)
-    except DoesNotImplement, e:
-        testCase.fail("%r does not provide %s.%s" %
-                      (provider, interface.__module__, interface.getName()))
-
-
-
-class CommonTests(object):
-    """
     Tests for common functionality of interfaces defined in
     L{txdav.carddav.iaddressbookstore}.
     """
@@ -152,49 +133,6 @@
         raise NotImplementedError()
 
 
-    lastTransaction = None
-    savedStore = None
-
-    def transactionUnderTest(self):
-        """
-        Create a transaction from C{storeUnderTest} and save it as
-        C[lastTransaction}.  Also makes sure to use the same store, saving the
-        value from C{storeUnderTest}.
-        """
-        if self.lastTransaction is not None:
-            return self.lastTransaction
-        if self.savedStore is None:
-            self.savedStore = self.storeUnderTest()
-        txn = self.lastTransaction = self.savedStore.newTransaction(self.id())
-        return txn
-
-
-    def commit(self):
-        """
-        Commit the last transaction created from C{transactionUnderTest}, and
-        clear it.
-        """
-        self.lastTransaction.commit()
-        self.lastTransaction = None
-
-
-    def abort(self):
-        """
-        Abort the last transaction created from C[transactionUnderTest}, and
-        clear it.
-        """
-        self.lastTransaction.abort()
-        self.lastTransaction = None
-
-    def setUp(self):
-        self.notifierFactory = StubNotifierFactory()
-
-    def tearDown(self):
-        if self.lastTransaction is not None:
-            self.commit()
-
-
-
     def homeUnderTest(self):
         """
         Get the addressbook home detailed by C{requirements['home1']}.
@@ -202,24 +140,25 @@
         return self.transactionUnderTest().addressbookHomeWithUID("home1")
 
 
+    @inlineCallbacks
     def addressbookUnderTest(self):
         """
         Get the addressbook detailed by C{requirements['home1']['addressbook_1']}.
         """
-        return self.homeUnderTest().addressbookWithName("addressbook_1")
+        returnValue((yield (yield self.homeUnderTest())
+            .addressbookWithName("addressbook_1")))
 
 
+    @inlineCallbacks
     def addressbookObjectUnderTest(self):
         """
         Get the addressbook detailed by
         C{requirements['home1']['addressbook_1']['1.vcf']}.
         """
-        return self.addressbookUnderTest().addressbookObjectWithName("1.vcf")
+        returnValue((yield (yield self.addressbookUnderTest())
+                    .addressbookObjectWithName("1.vcf")))
 
 
-    assertProvides = assertProvides
-
-
     def test_addressbookStoreProvides(self):
         """
         The addressbook store provides L{IAddressBookStore} and its required
@@ -239,114 +178,128 @@
         self.assertProvides(IAddressBookTransaction, txn)
 
 
+    @inlineCallbacks
     def test_homeProvides(self):
         """
         The addressbook homes generated by the addressbook store provide
         L{IAddressBookHome} and its required attributes.
         """
-        self.assertProvides(IAddressBookHome, self.homeUnderTest())
+        self.assertProvides(IAddressBookHome, (yield self.homeUnderTest()))
 
 
+    @inlineCallbacks
     def test_addressbookProvides(self):
         """
         The addressbooks generated by the addressbook store provide L{IAddressBook} and
         its required attributes.
         """
-        self.assertProvides(IAddressBook, self.addressbookUnderTest())
+        self.assertProvides(IAddressBook, (yield self.addressbookUnderTest()))
 
 
+    @inlineCallbacks
     def test_addressbookObjectProvides(self):
         """
         The addressbook objects generated by the addressbook store provide
         L{IAddressBookObject} and its required attributes.
         """
-        self.assertProvides(IAddressBookObject, self.addressbookObjectUnderTest())
+        self.assertProvides(IAddressBookObject,
+                            (yield self.addressbookObjectUnderTest()))
 
+
+    @inlineCallbacks
     def test_notifierID(self):
-        home = self.homeUnderTest()
+        home = yield self.homeUnderTest()
         self.assertEquals(home.notifierID(), "CardDAV|home1")
-        addressbook = home.addressbookWithName("addressbook_1")
+        addressbook = yield home.addressbookWithName("addressbook_1")
         self.assertEquals(addressbook.notifierID(), "CardDAV|home1")
         self.assertEquals(addressbook.notifierID(label="collection"), "CardDAV|home1/addressbook_1")
 
 
+    @inlineCallbacks
     def test_addressbookHomeWithUID_exists(self):
         """
         Finding an existing addressbook home by UID results in an object that
         provides L{IAddressBookHome} and has a C{uid()} method that returns the
         same value that was passed in.
         """
-        addressbookHome = (self.transactionUnderTest()
-                        .addressbookHomeWithUID("home1"))
+        addressbookHome = (yield self.transactionUnderTest()
+                            .addressbookHomeWithUID("home1"))
         self.assertEquals(addressbookHome.uid(), "home1")
         self.assertProvides(IAddressBookHome, addressbookHome)
 
 
+    @inlineCallbacks
     def test_addressbookHomeWithUID_absent(self):
         """
         L{IAddressBookStoreTransaction.addressbookHomeWithUID} should return C{None}
         when asked for a non-existent addressbook home.
         """
         txn = self.transactionUnderTest()
-        self.assertEquals(txn.addressbookHomeWithUID("xyzzy"), None)
+        self.assertEquals((yield txn.addressbookHomeWithUID("xyzzy")), None)
 
 
+    @inlineCallbacks
     def test_addressbookWithName_exists(self):
         """
         L{IAddressBookHome.addressbookWithName} returns an L{IAddressBook} provider,
         whose name matches the one passed in.
         """
-        home = self.homeUnderTest()
+        home = yield self.homeUnderTest()
         for name in home1_addressbookNames:
-            addressbook = home.addressbookWithName(name)
+            addressbook = yield home.addressbookWithName(name)
             if addressbook is None:
                 self.fail("addressbook %r didn't exist" % (name,))
             self.assertProvides(IAddressBook, addressbook)
             self.assertEquals(addressbook.name(), name)
 
 
+    @inlineCallbacks
     def test_addressbookRename(self):
         """
         L{IAddressBook.rename} changes the name of the L{IAddressBook}.
         """
-        home = self.homeUnderTest()
-        addressbook = home.addressbookWithName("addressbook_1")
-        addressbook.rename("some_other_name")
+        home = yield self.homeUnderTest()
+        addressbook = yield home.addressbookWithName("addressbook_1")
+        yield addressbook.rename("some_other_name")
+        @inlineCallbacks
         def positiveAssertions():
             self.assertEquals(addressbook.name(), "some_other_name")
-            self.assertEquals(addressbook, home.addressbookWithName("some_other_name"))
-            self.assertEquals(None, home.addressbookWithName("addressbook_1"))
+            self.assertEquals(addressbook, (yield home.addressbookWithName("some_other_name")))
+            self.assertEquals(None, (yield home.addressbookWithName("addressbook_1")))
+        yield positiveAssertions()
+        yield self.commit()
+        home = yield self.homeUnderTest()
+        addressbook = yield home.addressbookWithName("some_other_name")
         positiveAssertions()
-        self.commit()
-        home = self.homeUnderTest()
-        addressbook = home.addressbookWithName("some_other_name")
-        positiveAssertions()
         # FIXME: revert
         # FIXME: test for multiple renames
         # FIXME: test for conflicting renames (a->b, c->a in the same txn)
 
 
+    @inlineCallbacks
     def test_addressbookWithName_absent(self):
         """
         L{IAddressBookHome.addressbookWithName} returns C{None} for addressbooks which
         do not exist.
         """
-        self.assertEquals(self.homeUnderTest().addressbookWithName("xyzzy"),
-                          None)
+        self.assertEquals(
+            (yield (yield self.homeUnderTest()).addressbookWithName("xyzzy")),
+            None)
 
 
+    @inlineCallbacks
     def test_createAddressBookWithName_absent(self):
         """
         L{IAddressBookHome.createAddressBookWithName} creates a new L{IAddressBook} that
         can be retrieved with L{IAddressBookHome.addressbookWithName}.
         """
-        home = self.homeUnderTest()
+        home = yield self.homeUnderTest()
         name = "new"
-        self.assertIdentical(home.addressbookWithName(name), None)
-        home.createAddressBookWithName(name)
-        self.assertNotIdentical(home.addressbookWithName(name), None)
+        self.assertIdentical((yield home.addressbookWithName(name)), None)
+        yield home.createAddressBookWithName(name)
+        self.assertNotIdentical((yield home.addressbookWithName(name)), None)
         def checkProperties():
-            addressbookProperties = home.addressbookWithName(name).properties()
+            addressbookProperties = (yield home.addressbookWithName(name)).properties()
             addressbookType = davxml.ResourceType.addressbook #@UndefinedVariable
             self.assertEquals(
                 addressbookProperties[
@@ -355,15 +308,15 @@
                 addressbookType
             )
         checkProperties()
-        self.commit()
+        yield self.commit()
 
         # Make sure notification fired after commit
         self.assertEquals(self.notifierFactory.history,
             [("update", "CardDAV|home1")])
 
         # Make sure it's available in a new transaction; i.e. test the commit.
-        home = self.homeUnderTest()
-        self.assertNotIdentical(home.addressbookWithName(name), None)
+        home = yield self.homeUnderTest()
+        self.assertNotIdentical((yield home.addressbookWithName(name)), None)
 
         # FIXME: These two lines aren't in the calendar common tests:
         # home = self.addressbookStore.newTransaction().addressbookHomeWithUID(
@@ -374,6 +327,7 @@
         checkProperties()
 
 
+    @inlineCallbacks
     def test_createAddressBookWithName_exists(self):
         """
         L{IAddressBookHome.createAddressBookWithName} raises
@@ -381,25 +335,27 @@
         existing address book.
         """
         for name in home1_addressbookNames:
-            self.assertRaises(
+            yield self.failUnlessFailure(
+                maybeDeferred(
+                    (yield self.homeUnderTest()).createAddressBookWithName, name),
                 HomeChildNameAlreadyExistsError,
-                self.homeUnderTest().createAddressBookWithName, name
             )
 
 
+    @inlineCallbacks
     def test_removeAddressBookWithName_exists(self):
         """
         L{IAddressBookHome.removeAddressBookWithName} removes a addressbook that already
         exists.
         """
-        home = self.homeUnderTest()
+        home = yield self.homeUnderTest()
         # FIXME: test transactions
         for name in home1_addressbookNames:
-            self.assertNotIdentical(home.addressbookWithName(name), None)
-            home.removeAddressBookWithName(name)
-            self.assertEquals(home.addressbookWithName(name), None)
+            self.assertNotIdentical((yield home.addressbookWithName(name)), None)
+            yield home.removeAddressBookWithName(name)
+            self.assertEquals((yield home.addressbookWithName(name)), None)
 
-        self.commit()
+        yield self.commit()
 
         # Make sure notification fired after commit
         self.assertEquals(
@@ -415,27 +371,31 @@
         )
 
 
+    @inlineCallbacks
     def test_removeAddressBookWithName_absent(self):
         """
         Attempt to remove an non-existing addressbook should raise.
         """
-        home = self.homeUnderTest()
-        self.assertRaises(NoSuchHomeChildError,
-                          home.removeAddressBookWithName, "xyzzy")
+        home = yield self.homeUnderTest()
+        yield self.failUnlessFailure(
+            maybeDeferred(home.removeAddressBookWithName, "xyzzy"),
+            NoSuchHomeChildError
+        )
 
 
+    @inlineCallbacks
     def test_addressbookObjects(self):
         """
         L{IAddressBook.addressbookObjects} will enumerate the addressbook objects present
         in the filesystem, in name order, but skip those with hidden names.
         """
-        addressbook1 = self.addressbookUnderTest()
-        addressbookObjects = list(addressbook1.addressbookObjects())
+        addressbook1 = yield self.addressbookUnderTest()
+        addressbookObjects = list((yield addressbook1.addressbookObjects()))
 
         for addressbookObject in addressbookObjects:
             self.assertProvides(IAddressBookObject, addressbookObject)
             self.assertEquals(
-                addressbook1.addressbookObjectWithName(addressbookObject.name()),
+                (yield addressbook1.addressbookObjectWithName(addressbookObject.name())),
                 addressbookObject
             )
 
@@ -445,87 +405,95 @@
         )
 
 
+    @inlineCallbacks
     def test_addressbookObjectsWithRemovedObject(self):
         """
         L{IAddressBook.addressbookObjects} skips those objects which have been
         removed by L{AddressBook.removeAddressBookObjectWithName} in the same
         transaction, even if it has not yet been committed.
         """
-        addressbook1 = self.addressbookUnderTest()
+        addressbook1 = yield self.addressbookUnderTest()
         addressbook1.removeAddressBookObjectWithName("2.vcf")
-        addressbookObjects = list(addressbook1.addressbookObjects())
+        addressbookObjects = list((yield addressbook1.addressbookObjects()))
         self.assertEquals(set(o.name() for o in addressbookObjects),
                           set(addressbook1_objectNames) - set(["2.vcf"]))
 
 
+    @inlineCallbacks
     def test_ownerAddressBookHome(self):
         """
         L{IAddressBook.ownerAddressBookHome} should match the home UID.
         """
         self.assertEquals(
-            self.addressbookUnderTest().ownerAddressBookHome().uid(),
-            self.homeUnderTest().uid()
+            (yield self.addressbookUnderTest()).ownerAddressBookHome().uid(),
+            (yield self.homeUnderTest()).uid()
         )
 
 
+    @inlineCallbacks
     def test_addressbookObjectWithName_exists(self):
         """
         L{IAddressBook.addressbookObjectWithName} returns an L{IAddressBookObject}
         provider for addressbooks which already exist.
         """
-        addressbook1 = self.addressbookUnderTest()
+        addressbook1 = yield self.addressbookUnderTest()
         for name in addressbook1_objectNames:
-            addressbookObject = addressbook1.addressbookObjectWithName(name)
+            addressbookObject = yield addressbook1.addressbookObjectWithName(name)
             self.assertProvides(IAddressBookObject, addressbookObject)
             self.assertEquals(addressbookObject.name(), name)
             # FIXME: add more tests based on CommonTests.requirements
 
 
+    @inlineCallbacks
     def test_addressbookObjectWithName_absent(self):
         """
         L{IAddressBook.addressbookObjectWithName} returns C{None} for addressbooks which
         don't exist.
         """
-        addressbook1 = self.addressbookUnderTest()
-        self.assertEquals(addressbook1.addressbookObjectWithName("xyzzy"), None)
+        addressbook1 = yield self.addressbookUnderTest()
+        self.assertEquals((yield addressbook1.addressbookObjectWithName("xyzzy")), None)
 
 
+    @inlineCallbacks
     def test_removeAddressBookObjectWithUID_exists(self):
         """
         Remove an existing addressbook object.
         """
-        addressbook = self.addressbookUnderTest()
+        addressbook = yield self.addressbookUnderTest()
         for name in addressbook1_objectNames:
             uid = (u'uid' + name.rstrip(".vcf"))
-            self.assertNotIdentical(addressbook.addressbookObjectWithUID(uid),
-                                    None)
-            addressbook.removeAddressBookObjectWithUID(uid)
+            self.assertNotIdentical(
+                (yield addressbook.addressbookObjectWithUID(uid)),
+                None
+            )
+            yield addressbook.removeAddressBookObjectWithUID(uid)
             self.assertEquals(
-                addressbook.addressbookObjectWithUID(uid),
+                (yield addressbook.addressbookObjectWithUID(uid)),
                 None
             )
             self.assertEquals(
-                addressbook.addressbookObjectWithName(name),
+                (yield addressbook.addressbookObjectWithName(name)),
                 None
             )
 
 
+    @inlineCallbacks
     def test_removeAddressBookObjectWithName_exists(self):
         """
         Remove an existing addressbook object.
         """
-        addressbook = self.addressbookUnderTest()
+        addressbook = yield self.addressbookUnderTest()
         for name in addressbook1_objectNames:
             self.assertNotIdentical(
-                addressbook.addressbookObjectWithName(name), None
+                (yield addressbook.addressbookObjectWithName(name)), None
             )
-            addressbook.removeAddressBookObjectWithName(name)
+            yield addressbook.removeAddressBookObjectWithName(name)
             self.assertIdentical(
-                addressbook.addressbookObjectWithName(name), None
+                (yield addressbook.addressbookObjectWithName(name)), None
             )
 
         # Make sure notifications are fired after commit
-        self.commit()
+        yield self.commit()
         self.assertEquals(
             self.notifierFactory.history,
             [
@@ -539,37 +507,43 @@
         )
 
 
+    @inlineCallbacks
     def test_removeAddressBookObjectWithName_absent(self):
         """
         Attempt to remove an non-existing addressbook object should raise.
         """
-        addressbook = self.addressbookUnderTest()
-        self.assertRaises(
-            NoSuchObjectResourceError,
-            addressbook.removeAddressBookObjectWithName, "xyzzy"
+        addressbook = yield self.addressbookUnderTest()
+        yield self.failUnlessFailure(
+            maybeDeferred(addressbook.removeAddressBookObjectWithName, "xyzzy"),
+            NoSuchObjectResourceError
         )
 
 
+    @inlineCallbacks
     def test_addressbookName(self):
         """
         L{AddressBook.name} reflects the name of the addressbook.
         """
-        self.assertEquals(self.addressbookUnderTest().name(), "addressbook_1")
+        self.assertEquals((yield self.addressbookUnderTest()).name(), "addressbook_1")
 
 
+    @inlineCallbacks
     def test_addressbookObjectName(self):
         """
         L{IAddressBookObject.name} reflects the name of the addressbook object.
         """
-        self.assertEquals(self.addressbookObjectUnderTest().name(), "1.vcf")
+        self.assertEquals(
+            (yield self.addressbookObjectUnderTest()).name(),
+            "1.vcf")
 
 
+    @inlineCallbacks
     def test_component(self):
         """
         L{IAddressBookObject.component} returns a L{VComponent} describing the
         addressbook data underlying that addressbook object.
         """
-        component = self.addressbookObjectUnderTest().component()
+        component = yield (yield self.addressbookObjectUnderTest()).component()
 
         self.failUnless(
             isinstance(component, VComponent),
@@ -580,35 +554,42 @@
         self.assertEquals(component.resourceUID(), "uid1")
 
 
+    @inlineCallbacks
     def test_iAddressBookText(self):
         """
         L{IAddressBookObject.iAddressBookText} returns a C{str} describing the same
         data provided by L{IAddressBookObject.component}.
         """
-        text = self.addressbookObjectUnderTest().vCardText()
+        text = yield (yield self.addressbookObjectUnderTest()).vCardText()
         self.assertIsInstance(text, str)
         self.failUnless(text.startswith("BEGIN:VCARD\r\n"))
         self.assertIn("\r\nUID:uid1\r\n", text)
         self.failUnless(text.endswith("\r\nEND:VCARD\r\n"))
 
 
+    @inlineCallbacks
     def test_addressbookObjectUID(self):
         """
         L{IAddressBookObject.uid} returns a C{str} describing the C{UID} property
         of the addressbook object's component.
         """
-        self.assertEquals(self.addressbookObjectUnderTest().uid(), "uid1")
+        self.assertEquals((yield self.addressbookObjectUnderTest()).uid(), "uid1")
 
 
+    @inlineCallbacks
     def test_addressbookObjectWithUID_absent(self):
         """
         L{IAddressBook.addressbookObjectWithUID} returns C{None} for addressbooks which
         don't exist.
         """
-        addressbook1 = self.addressbookUnderTest()
-        self.assertEquals(addressbook1.addressbookObjectWithUID("xyzzy"), None)
+        addressbook1 = yield self.addressbookUnderTest()
+        self.assertEquals(
+            (yield addressbook1.addressbookObjectWithUID("xyzzy")),
+            None
+        )
 
 
+    @inlineCallbacks
     def test_addressbooks(self):
         """
         L{IAddressBookHome.addressbooks} returns an iterable of L{IAddressBook}
@@ -617,13 +598,15 @@
         """
         # Add a dot directory to make sure we don't find it
         # self.home1._path.child(".foo").createDirectory()
-        home = self.homeUnderTest()
-        addressbooks = list(home.addressbooks())
+        home = yield self.homeUnderTest()
+        addressbooks = list((yield home.addressbooks()))
 
         for addressbook in addressbooks:
             self.assertProvides(IAddressBook, addressbook)
-            self.assertEquals(addressbook,
-                              home.addressbookWithName(addressbook.name()))
+            self.assertEquals(
+                addressbook,
+                (yield home.addressbookWithName(addressbook.name()))
+            )
 
         self.assertEquals(
             set(c.name() for c in addressbooks),
@@ -631,33 +614,37 @@
         )
 
 
+    @inlineCallbacks
     def test_addressbooksAfterAddAddressBook(self):
         """
         L{IAddressBookHome.addressbooks} includes addressbooks recently added with
         L{IAddressBookHome.createAddressBookWithName}.
         """
-        home = self.homeUnderTest()
-        before = set(x.name() for x in home.addressbooks())
-        home.createAddressBookWithName("new-name")
-        after = set(x.name() for x in home.addressbooks())
+        home = yield self.homeUnderTest()
+        allAddressbooks = yield home.addressbooks()
+        before = set(x.name() for x in allAddressbooks)
+        yield home.createAddressBookWithName("new-name")
+        allAddressbooks = yield home.addressbooks()
+        after = set(x.name() for x in allAddressbooks)
         self.assertEquals(before | set(['new-name']), after)
 
 
+    @inlineCallbacks
     def test_createAddressBookObjectWithName_absent(self):
         """
         L{IAddressBook.createAddressBookObjectWithName} creates a new
         L{IAddressBookObject}.
         """
-        addressbook1 = self.addressbookUnderTest()
+        addressbook1 = yield self.addressbookUnderTest()
         name = "4.vcf"
-        self.assertIdentical(addressbook1.addressbookObjectWithName(name), None)
+        self.assertIdentical((yield addressbook1.addressbookObjectWithName(name)), None)
         component = VComponent.fromString(vcard4_text)
-        addressbook1.createAddressBookObjectWithName(name, component)
+        yield addressbook1.createAddressBookObjectWithName(name, component)
 
-        addressbookObject = addressbook1.addressbookObjectWithName(name)
-        self.assertEquals(addressbookObject.component(), component)
+        addressbookObject = yield addressbook1.addressbookObjectWithName(name)
+        self.assertEquals((yield addressbookObject.component()), component)
 
-        self.commit()
+        yield self.commit()
 
         # Make sure notifications fire after commit
         self.assertEquals(
@@ -669,82 +656,92 @@
         )
 
 
+    @inlineCallbacks
     def test_createAddressBookObjectWithName_exists(self):
         """
         L{IAddressBook.createAddressBookObjectWithName} raises
         L{AddressBookObjectNameAlreadyExistsError} if a addressbook object with the
         given name already exists in that addressbook.
         """
-        self.assertRaises(
-            ObjectResourceNameAlreadyExistsError,
-            self.addressbookUnderTest().createAddressBookObjectWithName,
-            "1.vcf", VComponent.fromString(vcard4_text)
+        self.failUnlessFailure(
+            maybeDeferred((yield self.addressbookUnderTest())
+                .createAddressBookObjectWithName,
+                "1.vcf", VComponent.fromString(vcard4_text)),
+            ObjectResourceNameAlreadyExistsError
         )
 
 
+    @inlineCallbacks
     def test_createAddressBookObjectWithName_invalid(self):
         """
         L{IAddressBook.createAddressBookObjectWithName} raises
         L{InvalidAddressBookComponentError} if presented with invalid iAddressBook
         text.
         """
-        self.assertRaises(
-            InvalidObjectResourceError,
-            self.addressbookUnderTest().createAddressBookObjectWithName,
-            "new", VComponent.fromString(vcard4notCardDAV_text)
+        yield self.failUnlessFailure(
+            maybeDeferred((yield self.addressbookUnderTest())
+                .createAddressBookObjectWithName,
+                "new", VComponent.fromString(vcard4notCardDAV_text)),
+            InvalidObjectResourceError
         )
 
 
+    @inlineCallbacks
     def test_setComponent_invalid(self):
         """
         L{IAddressBookObject.setComponent} raises L{InvalidIAddressBookDataError} if
         presented with invalid iAddressBook text.
         """
-        addressbookObject = self.addressbookObjectUnderTest()
-        self.assertRaises(
-            InvalidObjectResourceError,
-            addressbookObject.setComponent,
-            VComponent.fromString(vcard4notCardDAV_text)
+        addressbookObject = (yield self.addressbookObjectUnderTest())
+        yield self.failUnlessFailure(
+            maybeDeferred(addressbookObject.setComponent,
+                VComponent.fromString(vcard4notCardDAV_text)),
+            InvalidObjectResourceError
         )
 
 
+    @inlineCallbacks
     def test_setComponent_uidchanged(self):
         """
-        L{IAddressBookObject.setComponent} raises L{InvalidAddressBookComponentError}
-        when given a L{VComponent} whose UID does not match its existing UID.
+        L{IAddressBookObject.setComponent} raises
+        L{InvalidAddressBookComponentError} when given a L{VComponent} whose
+        UID does not match its existing UID.
         """
-        addressbook1 = self.addressbookUnderTest()
+        addressbook1 = yield self.addressbookUnderTest()
         component = VComponent.fromString(vcard4_text)
-        addressbookObject = addressbook1.addressbookObjectWithName("1.vcf")
-        self.assertRaises(
-            InvalidObjectResourceError,
-            addressbookObject.setComponent, component
+        addressbookObject = yield addressbook1.addressbookObjectWithName("1.vcf")
+        yield self.failUnlessFailure(
+            maybeDeferred(addressbookObject.setComponent, component),
+            InvalidObjectResourceError
         )
 
 
+    @inlineCallbacks
     def test_addressbookHomeWithUID_create(self):
         """
-        L{IAddressBookStoreTransaction.addressbookHomeWithUID} with C{create=True}
-        will create a addressbook home that doesn't exist yet.
+        L{IAddressBookStoreTransaction.addressbookHomeWithUID} with
+        C{create=True} will create a addressbook home that doesn't exist yet.
         """
         txn = self.transactionUnderTest()
         noHomeUID = "xyzzy"
-        addressbookHome = txn.addressbookHomeWithUID(
+        addressbookHome = yield txn.addressbookHomeWithUID(
             noHomeUID,
             create=True
         )
+        @inlineCallbacks
         def readOtherTxn():
             otherTxn = self.savedStore.newTransaction()
             self.addCleanup(otherTxn.commit)
-            return otherTxn.addressbookHomeWithUID(noHomeUID)
+            returnValue((yield otherTxn.addressbookHomeWithUID(noHomeUID)))
         self.assertProvides(IAddressBookHome, addressbookHome)
-        # A concurrent transaction shouldn't be able to read it yet:
-        self.assertIdentical(readOtherTxn(), None)
-        self.commit()
+        # A concurrent tnransaction shouldn't be able to read it yet:
+        self.assertIdentical((yield readOtherTxn()), None)
+        yield self.commit()
         # But once it's committed, other transactions should see it.
-        self.assertProvides(IAddressBookHome, readOtherTxn())
+        self.assertProvides(IAddressBookHome, (yield readOtherTxn()))
 
 
+    @inlineCallbacks
     def test_setComponent(self):
         """
         L{AddressBookObject.setComponent} changes the result of
@@ -752,18 +749,18 @@
         """
         component = VComponent.fromString(vcard1modified_text)
 
-        addressbook1 = self.addressbookUnderTest()
-        addressbookObject = addressbook1.addressbookObjectWithName("1.vcf")
-        oldComponent = addressbookObject.component()
+        addressbook1 = yield self.addressbookUnderTest()
+        addressbookObject = yield addressbook1.addressbookObjectWithName("1.vcf")
+        oldComponent = yield addressbookObject.component()
         self.assertNotEqual(component, oldComponent)
-        addressbookObject.setComponent(component)
-        self.assertEquals(addressbookObject.component(), component)
+        yield addressbookObject.setComponent(component)
+        self.assertEquals((yield addressbookObject.component()), component)
 
         # Also check a new instance
-        addressbookObject = addressbook1.addressbookObjectWithName("1.vcf")
-        self.assertEquals(addressbookObject.component(), component)
+        addressbookObject = yield addressbook1.addressbookObjectWithName("1.vcf")
+        self.assertEquals((yield addressbookObject.component()), component)
 
-        self.commit()
+        yield self.commit()
 
         # Make sure notification fired after commit
         self.assertEquals(
@@ -774,6 +771,7 @@
             ]
         )
 
+
     def checkPropertiesMethod(self, thunk):
         """
         Verify that the given object has a properties method that returns an
@@ -783,40 +781,45 @@
         self.assertProvides(IPropertyStore, properties)
 
 
+    @inlineCallbacks
     def test_homeProperties(self):
         """
         L{IAddressBookHome.properties} returns a property store.
         """
-        self.checkPropertiesMethod(self.homeUnderTest())
+        self.checkPropertiesMethod((yield self.homeUnderTest()))
 
 
+    @inlineCallbacks
     def test_addressbookProperties(self):
         """
         L{IAddressBook.properties} returns a property store.
         """
-        self.checkPropertiesMethod(self.addressbookUnderTest())
+        self.checkPropertiesMethod((yield self.addressbookUnderTest()))
 
 
+    @inlineCallbacks
     def test_addressbookObjectProperties(self):
         """
         L{IAddressBookObject.properties} returns a property store.
         """
-        self.checkPropertiesMethod(self.addressbookObjectUnderTest())
+        self.checkPropertiesMethod((yield self.addressbookObjectUnderTest()))
 
 
+    @inlineCallbacks
     def test_newAddressBookObjectProperties(self):
         """
         L{IAddressBookObject.properties} returns an empty property store for a
         addressbook object which has been created but not committed.
         """
-        addressbook = self.addressbookUnderTest()
-        addressbook.createAddressBookObjectWithName(
+        addressbook = yield self.addressbookUnderTest()
+        yield addressbook.createAddressBookObjectWithName(
             "4.vcf", VComponent.fromString(vcard4_text)
         )
-        newEvent = addressbook.addressbookObjectWithName("4.vcf")
+        newEvent = yield addressbook.addressbookObjectWithName("4.vcf")
         self.assertEquals(newEvent.properties().items(), [])
 
 
+    @inlineCallbacks
     def test_setComponentPreservesProperties(self):
         """
         L{IAddressBookObject.setComponent} preserves properties.
@@ -830,16 +833,18 @@
         propertyContent.name = propertyName.name
         propertyContent.namespace = propertyName.namespace
 
-        self.addressbookObjectUnderTest().properties()[
+        (yield self.addressbookObjectUnderTest()).properties()[
             propertyName] = propertyContent
-        self.commit()
+        yield self.commit()
         # Sanity check; are properties even readable in a separate transaction?
         # Should probably be a separate test.
         self.assertEquals(
-            self.addressbookObjectUnderTest().properties()[propertyName],
+            (yield self.addressbookObjectUnderTest()).properties()[
+                propertyName
+            ],
             propertyContent)
-        obj = self.addressbookObjectUnderTest()
-        vcard1_text = obj.vCardText()
+        obj = yield self.addressbookObjectUnderTest()
+        vcard1_text = yield obj.vCardText()
         vcard1_text_withDifferentNote = vcard1_text.replace(
             "NOTE:CardDAV protocol updates",
             "NOTE:Changed"
@@ -851,43 +856,47 @@
 
         # Putting everything into a separate transaction to account for any
         # caching that may take place.
-        self.commit()
+        yield self.commit()
         self.assertEquals(
-            self.addressbookObjectUnderTest().properties()[propertyName],
+            (yield self.addressbookObjectUnderTest()).properties()[propertyName],
             propertyContent
         )
 
 
+    @inlineCallbacks
     def test_dontLeakAddressbooks(self):
         """
         Addressbooks in one user's addressbook home should not show up in another
         user's addressbook home.
         """
-        home2 = self.transactionUnderTest().addressbookHomeWithUID(
-            "home2", create=True)
-        self.assertIdentical(home2.addressbookWithName("addressbook_1"), None)
+        home2 = yield self.transactionUnderTest().addressbookHomeWithUID(
+            "home2", create=True
+        )
+        self.assertIdentical((yield home2.addressbookWithName("addressbook_1")), None)
 
 
+    @inlineCallbacks
     def test_dontLeakObjects(self):
         """
         Addressbook objects in one user's addressbook should not show up in another
         user's via uid or name queries.
         """
-        home1 = self.homeUnderTest()
-        home2 = self.transactionUnderTest().addressbookHomeWithUID(
+        home1 = yield self.homeUnderTest()
+        home2 = yield self.transactionUnderTest().addressbookHomeWithUID(
             "home2", create=True)
-        addressbook1 = home1.addressbookWithName("addressbook_1")
-        addressbook2 = home2.addressbookWithName("addressbook")
-        objects = list(home2.addressbookWithName("addressbook").addressbookObjects())
+        addressbook1 = yield home1.addressbookWithName("addressbook_1")
+        addressbook2 = yield home2.addressbookWithName("addressbook")
+        objects = list((yield (yield home2.addressbookWithName("addressbook")).addressbookObjects()))
         self.assertEquals(objects, [])
         for resourceName in self.requirements['home1']['addressbook_1'].keys():
-            obj = addressbook1.addressbookObjectWithName(resourceName)
+            obj = yield addressbook1.addressbookObjectWithName(resourceName)
             self.assertIdentical(
-                addressbook2.addressbookObjectWithName(resourceName), None)
+                (yield addressbook2.addressbookObjectWithName(resourceName)), None)
             self.assertIdentical(
-                addressbook2.addressbookObjectWithUID(obj.uid()), None)
+                (yield addressbook2.addressbookObjectWithUID(obj.uid())), None)
 
 
+    @inlineCallbacks
     def test_eachAddressbookHome(self):
         """
         L{IAddressbookTransaction.eachAddressbookHome} returns an iterator that
@@ -897,11 +906,11 @@
         additionalUIDs = set('alpha-uid home2 home3 beta-uid'.split())
         txn = self.transactionUnderTest()
         for name in additionalUIDs:
-            txn.addressbookHomeWithUID(name, create=True)
-        self.commit()
+            yield txn.addressbookHomeWithUID(name, create=True)
+        yield self.commit()
         foundUIDs = set([])
         lastTxn = None
-        for txn, home in self.storeUnderTest().eachAddressbookHome():
+        for txn, home in (yield self.storeUnderTest().eachAddressbookHome()):
             self.addCleanup(txn.commit)
             foundUIDs.add(home.uid())
             self.assertNotIdentical(lastTxn, txn)
@@ -915,18 +924,3 @@
 
 
 
-class StubNotifierFactory(object):
-
-    """ For testing push notifications without an XMPP server """
-
-    def __init__(self):
-        self.reset()
-
-    def newNotifier(self, label="default", id=None, prefix=None):
-        return Notifier(self, label=label, id=id, prefix=prefix)
-
-    def send(self, op, id):
-        self.history.append((op, id))
-
-    def reset(self):
-        self.history = []

Modified: CalendarServer/trunk/txdav/carddav/datastore/test/test_file.py
===================================================================
--- CalendarServer/trunk/txdav/carddav/datastore/test/test_file.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/txdav/carddav/datastore/test/test_file.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -18,9 +18,11 @@
 File addressbook store tests.
 """
 
-from twext.python.filepath import CachingFilePath as FilePath
+from twisted.internet.defer import inlineCallbacks
 from twisted.trial import unittest
 
+from twext.python.filepath import CachingFilePath as FilePath
+
 from twistedcaldav.vcard import Component as VComponent
 
 from txdav.common.icommondatastore import HomeChildNameNotAllowedError
@@ -33,8 +35,10 @@
 from txdav.carddav.datastore.file import AddressBook, AddressBookObject
 
 from txdav.carddav.datastore.test.common import (
-    CommonTests, vcard4_text, vcard1modified_text, StubNotifierFactory)
+    CommonTests, vcard4_text, vcard1modified_text)
 
+from txdav.common.datastore.test.util import StubNotifierFactory
+
 storePath = FilePath(__file__).parent().child("addressbook_store")
 
 def _todo(f, why):
@@ -59,21 +63,23 @@
 
     test.notifierFactory = StubNotifierFactory()
     test.addressbookStore = AddressBookStore(storeRootPath, test.notifierFactory)
-    test.txn = test.addressbookStore.newTransaction()
+    test.txn = test.addressbookStore.newTransaction(test.id() + " (old)")
     assert test.addressbookStore is not None, "No addressbook store?"
 
 
 
+ at inlineCallbacks
 def setUpHome1(test):
     setUpAddressBookStore(test)
-    test.home1 = test.txn.addressbookHomeWithUID("home1")
+    test.home1 = yield test.txn.addressbookHomeWithUID("home1")
     assert test.home1 is not None, "No addressbook home?"
 
 
 
+ at inlineCallbacks
 def setUpAddressBook1(test):
-    setUpHome1(test)
-    test.addressbook1 = test.home1.addressbookWithName("addressbook_1")
+    yield setUpHome1(test)
+    test.addressbook1 = yield test.home1.addressbookWithName("addressbook_1")
     assert test.addressbook1 is not None, "No addressbook?"
 
 
@@ -87,13 +93,15 @@
         setUpAddressBookStore(self)
 
 
+    @inlineCallbacks
     def test_addressbookHomeWithUID_dot(self):
         """
         Filenames starting with "." are reserved by this
         implementation, so no UIDs may start with ".".
         """
         self.assertEquals(
-            self.addressbookStore.newTransaction().addressbookHomeWithUID("xyzzy"),
+            (yield self.addressbookStore.newTransaction(self.id()
+                ).addressbookHomeWithUID(".xyzzy")),
             None
         )
 
@@ -102,7 +110,7 @@
 class AddressBookHomeTest(unittest.TestCase):
 
     def setUp(self):
-        setUpHome1(self)
+        return setUpHome1(self)
 
 
     def test_init(self):
@@ -176,6 +184,7 @@
         )
 
 
+    @inlineCallbacks
     def test_useIndexImmediately(self):
         """
         L{AddressBook._index} is usable in the same transaction it is created, with
@@ -184,21 +193,22 @@
         self.home1.createAddressBookWithName("addressbook2")
         addressbook = self.home1.addressbookWithName("addressbook2")
         index = addressbook._index
-        self.assertEquals(set(index.addressbookObjects()),
-                          set(addressbook.addressbookObjects()))
-        self.txn.commit()
-        self.txn = self.addressbookStore.newTransaction()
-        self.home1 = self.txn.addressbookHomeWithUID("home1")
-        addressbook = self.home1.addressbookWithName("addressbook2")
+        self.assertEquals(set((yield index.addressbookObjects())),
+                          set((yield addressbook.addressbookObjects())))
+        yield self.txn.commit()
+        self.txn = self.addressbookStore.newTransaction(self.id())
+        self.home1 = yield self.txn.addressbookHomeWithUID("home1")
+        addressbook = yield self.home1.addressbookWithName("addressbook2")
         # FIXME: we should be curating our own index here, but in order to fix
         # that the code in the old implicit scheduler needs to change.  This
         # test would be more effective if there were actually some objects in
         # this list.
         index = addressbook._index
-        self.assertEquals(set(index.addressbookObjects()),
-                          set(addressbook.addressbookObjects()))
+        self.assertEquals(set((yield index.addressbookObjects())),
+                          set((yield addressbook.addressbookObjects())))
 
 
+    @inlineCallbacks
     def test_addressbookObjectWithName_dot(self):
         """
         Filenames starting with "." are reserved by this
@@ -207,7 +217,10 @@
         """
         name = ".foo.vcf"
         self.home1._path.child(name).touch()
-        self.assertEquals(self.addressbook1.addressbookObjectWithName(name), None)
+        self.assertEquals(
+            (yield self.addressbook1.addressbookObjectWithName(name)),
+            None
+        )
 
 
     @featureUnimplemented
@@ -215,14 +228,15 @@
         """
         Find existing addressbook object by name.
         """
-        addressbookObject = self.addressbook1.addressbookObjectWithUID("1")
+        addressbookObject = yield self.addressbook1.addressbookObjectWithUID("1")
         self.failUnless(
             isinstance(addressbookObject, AddressBookObject),
             addressbookObject
         )
         self.assertEquals(
             addressbookObject.component(),
-            self.addressbook1.addressbookObjectWithName("1.vcf").component()
+            (yield self.addressbook1.addressbookObjectWithName("1.vcf")
+                ).component()
         )
 
 
@@ -246,7 +260,10 @@
         should raise.
         """
         name = "foo.vcf"
-        assert self.addressbook1.addressbookObjectWithName(name) is None
+        self.assertIdentical(
+            (yield self.addressbook1.addressbookObjectWithName(name)),
+            None
+        )
         component = VComponent.fromString(vcard1modified_text)
         self.assertRaises(
             ObjectResourceUIDAlreadyExistsError,
@@ -280,16 +297,18 @@
         )
 
 
+    @inlineCallbacks
     def _refresh(self):
         """
         Re-read the (committed) home1 and addressbook1 objects in a new
         transaction.
         """
-        self.txn = self.addressbookStore.newTransaction()
-        self.home1 = self.txn.addressbookHomeWithUID("home1")
-        self.addressbook1 = self.home1.addressbookWithName("addressbook_1")
+        self.txn = self.addressbookStore.newTransaction(self.id())
+        self.home1 = yield self.txn.addressbookHomeWithUID("home1")
+        self.addressbook1 = yield self.home1.addressbookWithName("addressbook_1")
 
 
+    @inlineCallbacks
     def test_undoCreateAddressBookObject(self):
         """
         If a addressbook object is created as part of a transaction, it will be
@@ -297,19 +316,21 @@
         """
         # Make sure that the addressbook home is actually committed; rolling back
         # addressbook home creation will remove the whole directory.
-        self.txn.commit()
-        self._refresh()
+        yield self.txn.commit()
+        yield self._refresh()
         self.addressbook1.createAddressBookObjectWithName(
             "sample.vcf",
             VComponent.fromString(vcard4_text)
         )
-        self._refresh()
+        yield self.txn.abort()
+        yield self._refresh()
         self.assertIdentical(
-            self.addressbook1.addressbookObjectWithName("sample.vcf"),
+            (yield self.addressbook1.addressbookObjectWithName("sample.vcf")),
             None
         )
 
 
+    @inlineCallbacks
     def doThenUndo(self):
         """
         Commit the current transaction, but add an operation that will cause it
@@ -321,27 +342,28 @@
             raise RuntimeError("oops")
         self.txn.addOperation(fail, "dummy failing operation")
         self.assertRaises(RuntimeError, self.txn.commit)
-        self._refresh()
+        yield self._refresh()
 
 
+    @inlineCallbacks
     def test_undoModifyAddressBookObject(self):
         """
         If an existing addressbook object is modified as part of a transaction, it
         should be restored to its previous status if the transaction aborts.
         """
-        originalComponent = self.addressbook1.addressbookObjectWithName(
+        originalComponent = yield self.addressbook1.addressbookObjectWithName(
             "1.vcf").component()
-        self.addressbook1.addressbookObjectWithName("1.vcf").setComponent(
+        (yield self.addressbook1.addressbookObjectWithName("1.vcf")).setComponent(
             VComponent.fromString(vcard1modified_text)
         )
         # Sanity check.
         self.assertEquals(
-            self.addressbook1.addressbookObjectWithName("1.vcf").component(),
+            (yield self.addressbook1.addressbookObjectWithName("1.vcf")).component(),
             VComponent.fromString(vcard1modified_text)
         )
-        self.doThenUndo()
+        yield self.doThenUndo()
         self.assertEquals(
-            self.addressbook1.addressbookObjectWithName("1.vcf").component(),
+            (yield self.addressbook1.addressbookObjectWithName("1.vcf")).component(),
             originalComponent
         )
 
@@ -352,13 +374,14 @@
         memory, to avoid unnecessary parsing round-trips.
         """
         modifiedComponent = VComponent.fromString(vcard1modified_text)
-        self.addressbook1.addressbookObjectWithName("1.vcf").setComponent(
+        (yield self.addressbook1.addressbookObjectWithName("1.vcf")).setComponent(
             modifiedComponent
         )
         self.assertIdentical(
             modifiedComponent,
-            self.addressbook1.addressbookObjectWithName("1.vcf").component()
+            (yield self.addressbook1.addressbookObjectWithName("1.vcf")).component()
         )
+        self.txn.commit()
 
 
     @featureUnimplemented
@@ -399,9 +422,11 @@
 
 
 class AddressBookObjectTest(unittest.TestCase):
+
+    @inlineCallbacks
     def setUp(self):
-        setUpAddressBook1(self)
-        self.object1 = self.addressbook1.addressbookObjectWithName("1.vcf")
+        yield setUpAddressBook1(self)
+        self.object1 = yield self.addressbook1.addressbookObjectWithName("1.vcf")
 
 
     def test_init(self):
@@ -420,7 +445,7 @@
         )
 
 
-class FileStorageTests(unittest.TestCase, CommonTests):
+class FileStorageTests(CommonTests, unittest.TestCase):
     """
     File storage tests.
     """
@@ -445,10 +470,12 @@
                           self.storeRootPath)
 
 
+    @inlineCallbacks
     def test_addressbookObjectsWithDotFile(self):
         """
-        Adding a dotfile to the addressbook home should not increase
+        Adding a dotfile to the addressbook home should not create a new
+        addressbook object.
         """
         self.homeUnderTest()._path.child(".foo").createDirectory()
-        self.test_addressbookObjects()
+        yield self.test_addressbookObjects()
 

Copied: CalendarServer/trunk/txdav/carddav/datastore/test/test_index_file.py (from rev 6445, CalendarServer/branches/users/glyph/more-deferreds-7/txdav/carddav/datastore/test/test_index_file.py)
===================================================================
--- CalendarServer/trunk/txdav/carddav/datastore/test/test_index_file.py	                        (rev 0)
+++ CalendarServer/trunk/txdav/carddav/datastore/test/test_index_file.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -0,0 +1,213 @@
+##
+# Copyright (c) 2010 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from twisted.internet import reactor
+from twisted.internet.task import deferLater
+
+from txdav.common.icommondatastore import ReservationError
+from txdav.carddav.datastore.index_file import AddressBookIndex,\
+    MemcachedUIDReserver
+
+from twistedcaldav.test.util import InMemoryMemcacheProtocol
+from twistedcaldav.vcard import Component
+import twistedcaldav.test.util
+
+import os
+
+class MinimalResourceReplacement(object):
+    """
+    Provide the minimal set of attributes and methods from CalDAVFile required
+    by L{Index}.
+    """
+
+    def __init__(self, filePath):
+        self.fp = filePath
+
+
+    def isAddressBookCollection(self):
+        return True
+
+
+    def getChild(self, name):
+        # FIXME: this should really return something with a child method
+        return self.fp.child(name)
+
+
+    def initSyncToken(self):
+        pass
+
+
+
+class SQLIndexTests (twistedcaldav.test.util.TestCase):
+    """
+    Test abstract SQL DB class
+    """
+
+    def setUp(self):
+        super(SQLIndexTests, self).setUp()
+        self.site.resource.isAddressBookCollection = lambda: True
+        self.indexDirPath = self.site.resource.fp
+        # FIXME: since this resource lies about isCalendarCollection, it doesn't
+        # have all the associated backend machinery to actually get children.
+        self.db = AddressBookIndex(MinimalResourceReplacement(self.indexDirPath))
+
+
+    def test_reserve_uid_ok(self):
+        uid = "test-test-test"
+        d = self.db.isReservedUID(uid)
+        d.addCallback(self.assertFalse)
+        d.addCallback(lambda _: self.db.reserveUID(uid))
+        d.addCallback(lambda _: self.db.isReservedUID(uid))
+        d.addCallback(self.assertTrue)
+        d.addCallback(lambda _: self.db.unreserveUID(uid))
+        d.addCallback(lambda _: self.db.isReservedUID(uid))
+        d.addCallback(self.assertFalse)
+
+        return d
+
+
+    def test_reserve_uid_twice(self):
+        uid = "test-test-test"
+        d = self.db.reserveUID(uid)
+        d.addCallback(lambda _: self.db.isReservedUID(uid))
+        d.addCallback(self.assertTrue)
+        d.addCallback(lambda _:
+                      self.assertFailure(self.db.reserveUID(uid),
+                                         ReservationError))
+        return d
+
+
+    def test_unreserve_unreserved(self):
+        uid = "test-test-test"
+        return self.assertFailure(self.db.unreserveUID(uid),
+                                  ReservationError)
+
+
+    def test_reserve_uid_timeout(self):
+        # WARNING: This test is fundamentally flawed and will fail
+        # intermittently because it uses the real clock.
+        uid = "test-test-test"
+        from twistedcaldav.config import config
+        old_timeout = config.UIDReservationTimeOut
+        config.UIDReservationTimeOut = 1
+
+        def _finally():
+            config.UIDReservationTimeOut = old_timeout
+
+        d = self.db.isReservedUID(uid)
+        d.addCallback(self.assertFalse)
+        d.addCallback(lambda _: self.db.reserveUID(uid))
+        d.addCallback(lambda _: self.db.isReservedUID(uid))
+        d.addCallback(self.assertTrue)
+        d.addCallback(lambda _: deferLater(reactor, 2, lambda: None))
+        d.addCallback(lambda _: self.db.isReservedUID(uid))
+        d.addCallback(self.assertFalse)
+        self.addCleanup(_finally)
+
+        return d
+
+
+    def test_index(self):
+        data = (
+            (
+                "#1.1 Simple component",
+                "1.1",
+                """BEGIN:VCARD
+VERSION:3.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+UID:12345-67890-1.1
+FN:Cyrus Daboo
+N:Daboo;Cyrus
+EMAIL;TYPE=INTERNET,PREF:cyrus at example.com
+END:VCARD
+""",
+            ),
+        )
+
+        for description, name, vcard_txt in data:
+            calendar = Component.fromString(vcard_txt)
+            f = open(os.path.join(self.site.resource.fp.path, name), "w")
+            f.write(vcard_txt)
+            del f
+
+            self.db.addResource(name, calendar)
+            self.assertTrue(self.db.resourceExists(name), msg=description)
+
+        self.db._db_recreate()
+        for description, name, vcard_txt in data:
+            self.assertTrue(self.db.resourceExists(name), msg=description)
+
+    def test_index_revisions(self):
+        data1 = """BEGIN:VCARD
+VERSION:3.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+UID:12345-67890-1-1.1
+FN:Cyrus Daboo
+N:Daboo;Cyrus
+EMAIL;TYPE=INTERNET,PREF:cyrus at example.com
+END:VCARD
+"""
+        data2 = """BEGIN:VCARD
+VERSION:3.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+UID:12345-67890-2-1.1
+FN:Wilfredo Sanchez
+N:Sanchez;Wilfredo
+EMAIL;TYPE=INTERNET,PREF:wsanchez at example.com
+END:VCARD
+"""
+        data3 = """BEGIN:VCARD
+VERSION:3.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+UID:12345-67890-3-1.1
+FN:Bruce Gaya
+N:Gaya;Bruce
+EMAIL;TYPE=INTERNET,PREF:bruce at example.com
+END:VCARD
+"""
+
+        vcard = Component.fromString(data1)
+        self.db.addResource("data1.vcf", vcard)
+        vcard = Component.fromString(data2)
+        self.db.addResource("data2.vcf", vcard)
+        vcard = Component.fromString(data3)
+        self.db.addResource("data3.vcf", vcard)
+        self.db.deleteResource("data3.vcf")
+
+        tests = (
+            (0, (["data1.vcf", "data2.vcf",], [],)),
+            (1, (["data2.vcf",], ["data3.vcf",],)),
+            (2, ([], ["data3.vcf",],)),
+            (3, ([], ["data3.vcf",],)),
+            (4, ([], [],)),
+            (5, ([], [],)),
+        )
+        
+        for revision, results in tests:
+            for depth in ("1", "infinity"):
+                self.assertEquals(self.db.whatchanged(revision, depth), results, "Mismatched results for whatchanged with revision %d" % (revision,))
+
+class MemcacheTests(SQLIndexTests):
+    def setUp(self):
+        super(MemcacheTests, self).setUp()
+        self.memcache = InMemoryMemcacheProtocol()
+        self.db.reserver = MemcachedUIDReserver(self.db, self.memcache)
+
+
+    def tearDown(self):
+        for _ignore_k, v in self.memcache._timeouts.iteritems():
+            if v.active():
+                v.cancel()


Property changes on: CalendarServer/trunk/txdav/carddav/datastore/test/test_index_file.py
___________________________________________________________________
Added: svn:mergeinfo
   + /CalendarServer/branches/config-separation/txdav/carddav/datastore/test/test_index_file.py:4379-4443
/CalendarServer/branches/egg-info-351/txdav/carddav/datastore/test/test_index_file.py:4589-4625
/CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/test/test_index_file.py:6167-6191
/CalendarServer/branches/new-store/txdav/carddav/datastore/test/test_index_file.py:5594-5934
/CalendarServer/branches/new-store-no-caldavfile/txdav/carddav/datastore/test/test_index_file.py:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2/txdav/carddav/datastore/test/test_index_file.py:5936-5981
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/txdav/carddav/datastore/test/test_index_file.py:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/txdav/carddav/datastore/test/test_index_file.py:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591/txdav/carddav/datastore/test/test_index_file.py:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/txdav/carddav/datastore/test/test_index_file.py:4465-4957
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/txdav/carddav/datastore/test/test_index_file.py:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/txdav/carddav/datastore/test/test_index_file.py:5188-5440
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/carddav/datastore/test/test_index_file.py:4971-5080
/CalendarServer/branches/users/glyph/more-deferreds-6/txdav/carddav/datastore/test/test_index_file.py:6322-6368
/CalendarServer/branches/users/glyph/more-deferreds-7/txdav/carddav/datastore/test/test_index_file.py:6369-6445
/CalendarServer/branches/users/glyph/sendfdport/txdav/carddav/datastore/test/test_index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sql-store/txdav/carddav/datastore/test/test_index_file.py:5929-6073
/CalendarServer/branches/users/glyph/use-system-twisted/txdav/carddav/datastore/test/test_index_file.py:5084-5149
/CalendarServer/branches/users/sagen/locations-resources/txdav/carddav/datastore/test/test_index_file.py:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2/txdav/carddav/datastore/test/test_index_file.py:5052-5061
/CalendarServer/branches/users/sagen/resource-delegates-4038/txdav/carddav/datastore/test/test_index_file.py:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/txdav/carddav/datastore/test/test_index_file.py:4068-4075
/CalendarServer/branches/users/sagen/resources-2/txdav/carddav/datastore/test/test_index_file.py:5084-5093
/CalendarServer/branches/users/wsanchez/transations/txdav/carddav/datastore/test/test_index_file.py:5515-5593
/CalendarServer/trunk/twistedcaldav/test/test_vcardindex.py:6322-6394

Modified: CalendarServer/trunk/txdav/carddav/datastore/test/test_sql.py
===================================================================
--- CalendarServer/trunk/txdav/carddav/datastore/test/test_sql.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/txdav/carddav/datastore/test/test_sql.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -15,12 +15,10 @@
 ##
 
 """
-Tests for txdav.caldav.datastore.postgres, mostly based on
-L{txdav.caldav.datastore.test.common}.
+Tests for L{txdav.carddav.datastore.sql}, mostly based on
+L{txdav.carddav.datastore.test.common}.
 """
 
-import time
-
 from txdav.carddav.datastore.test.common import CommonTests as AddressBookCommonTests
 
 from txdav.common.datastore.sql import EADDRESSBOOKTYPE
@@ -31,8 +29,8 @@
 from txdav.carddav.datastore.util import _migrateAddressbook, migrateHome
 
 from twisted.trial import unittest
-from twisted.internet.defer import inlineCallbacks
-from twisted.internet.threads import deferToThread
+from twisted.internet.defer import inlineCallbacks, returnValue
+
 from twistedcaldav.vcard import Component as VCard
 
 
@@ -45,29 +43,30 @@
     def setUp(self):
         super(AddressBookSQLStorageTests, self).setUp()
         self._sqlStore = yield buildStore(self, self.notifierFactory)
-        self.populate()
+        yield self.populate()
 
+    @inlineCallbacks
     def populate(self):
         populateTxn = self.storeUnderTest().newTransaction()
         for homeUID in self.requirements:
             addressbooks = self.requirements[homeUID]
             if addressbooks is not None:
-                home = populateTxn.addressbookHomeWithUID(homeUID, True)
+                home = yield populateTxn.addressbookHomeWithUID(homeUID, True)
                 # We don't want the default addressbook to appear unless it's
                 # explicitly listed.
-                home.removeAddressBookWithName("addressbook")
+                yield home.removeAddressBookWithName("addressbook")
                 for addressbookName in addressbooks:
                     addressbookObjNames = addressbooks[addressbookName]
                     if addressbookObjNames is not None:
-                        home.createAddressBookWithName(addressbookName)
-                        addressbook = home.addressbookWithName(addressbookName)
+                        yield home.createAddressBookWithName(addressbookName)
+                        addressbook = yield home.addressbookWithName(addressbookName)
                         for objectName in addressbookObjNames:
                             objData = addressbookObjNames[objectName]
-                            addressbook.createAddressBookObjectWithName(
+                            yield addressbook.createAddressBookObjectWithName(
                                 objectName, VCard.fromString(objData)
                             )
 
-        populateTxn.commit()
+        yield populateTxn.commit()
         self.notifierFactory.reset()
 
 
@@ -79,19 +78,23 @@
         return self._sqlStore
 
 
+    @inlineCallbacks
     def assertAddressbooksSimilar(self, a, b, bAddressbookFilter=None):
         """
         Assert that two addressbooks have a similar structure (contain the same
         events).
         """
+        @inlineCallbacks
         def namesAndComponents(x, filter=lambda x:x.component()):
-            return dict([(fromObj.name(), filter(fromObj))
-                         for fromObj in x.addressbookObjects()])
+            fromObjs = yield x.addressbookObjects()
+            returnValue(dict([(fromObj.name(), (yield filter(fromObj)))
+                              for fromObj in fromObjs]))
         if bAddressbookFilter is not None:
             extra = [bAddressbookFilter]
         else:
             extra = []
-        self.assertEquals(namesAndComponents(a), namesAndComponents(b, *extra))
+        self.assertEquals((yield namesAndComponents(a)),
+                          (yield namesAndComponents(b, *extra)))
 
 
     def assertPropertiesSimilar(self, a, b, disregard=[]):
@@ -121,28 +124,30 @@
         return txn
 
 
+    @inlineCallbacks
     def test_migrateAddressbookFromFile(self):
         """
         C{_migrateAddressbook()} can migrate a file-backed addressbook to a
         database- backed addressbook.
         """
-        fromAddressbook = self.fileTransaction().addressbookHomeWithUID(
+        fromAddressbook = yield self.fileTransaction().addressbookHomeWithUID(
             "home1").addressbookWithName("addressbook_1")
-        toHome = self.transactionUnderTest().addressbookHomeWithUID(
+        toHome = yield self.transactionUnderTest().addressbookHomeWithUID(
             "new-home", create=True)
-        toAddressbook = toHome.addressbookWithName("addressbook")
-        _migrateAddressbook(fromAddressbook, toAddressbook,
-                            lambda x: x.component())
-        self.assertAddressbooksSimilar(fromAddressbook, toAddressbook)
+        toAddressbook = yield toHome.addressbookWithName("addressbook")
+        yield _migrateAddressbook(fromAddressbook, toAddressbook,
+                                  lambda x: x.component())
+        yield self.assertAddressbooksSimilar(fromAddressbook, toAddressbook)
 
 
+    @inlineCallbacks
     def test_migrateHomeFromFile(self):
         """
         L{migrateHome} will migrate an L{IAddressbookHome} provider from one
         backend to another; in this specific case, from the file-based backend
         to the SQL-based backend.
         """
-        fromHome = self.fileTransaction().addressbookHomeWithUID("home1")
+        fromHome = yield self.fileTransaction().addressbookHomeWithUID("home1")
 
         builtinProperties = [PropertyName.fromElement(ResourceType)]
 
@@ -151,19 +156,22 @@
 
         key = PropertyName.fromElement(GETContentLanguage)
         fromHome.properties()[key] = GETContentLanguage("C")
-        fromHome.addressbookWithName("addressbook_1").properties()[key] = (
+        (yield fromHome.addressbookWithName("addressbook_1")).properties()[
+            key] = (
             GETContentLanguage("pig-latin")
         )
-        toHome = self.transactionUnderTest().addressbookHomeWithUID(
+        toHome = yield self.transactionUnderTest().addressbookHomeWithUID(
             "new-home", create=True
         )
-        migrateHome(fromHome, toHome, lambda x: x.component())
-        self.assertEquals(set([c.name() for c in toHome.addressbooks()]),
+        yield migrateHome(fromHome, toHome, lambda x: x.component())
+        toAddressbooks = yield toHome.addressbooks()
+        self.assertEquals(set([c.name() for c in toAddressbooks]),
                           set([k for k in self.requirements['home1'].keys()
                                if self.requirements['home1'][k] is not None]))
-        for c in fromHome.addressbooks():
+        fromAddressbooks = yield fromHome.addressbooks()
+        for c in fromAddressbooks:
             self.assertPropertiesSimilar(
-                c, toHome.addressbookWithName(c.name()),
+                c, (yield toHome.addressbookWithName(c.name())),
                 builtinProperties
             )
         self.assertPropertiesSimilar(fromHome, toHome, builtinProperties)
@@ -182,58 +190,6 @@
 
 
     @inlineCallbacks
-    def test_homeProvisioningConcurrency(self):
-        """
-        Test that two concurrent attempts to provision an addressbook home do
-        not cause a race-condition whereby the second commit results in a
-        second INSERT that violates a unique constraint. Also verify that,
-        whilst the two provisioning attempts are happening and doing various
-        lock operations, that we do not block other reads of the table.
-        """
-
-        addressbookStore1 = yield buildStore(self, self.notifierFactory)
-        addressbookStore2 = yield buildStore(self, self.notifierFactory)
-        addressbookStore3 = yield buildStore(self, self.notifierFactory)
-
-        txn1 = addressbookStore1.newTransaction()
-        txn2 = addressbookStore2.newTransaction()
-        txn3 = addressbookStore3.newTransaction()
-
-        # Provision one home now - we will use this to later verify we can do reads of
-        # existing data in the table
-        home_uid2 = txn3.homeWithUID(EADDRESSBOOKTYPE, "uid2", create=True)
-        self.assertNotEqual(home_uid2, None)
-        txn3.commit()
-
-        home_uid1_1 = txn1.homeWithUID(EADDRESSBOOKTYPE, "uid1", create=True)
-
-        def _defer_home_uid1_2():
-            home_uid1_2 = txn2.homeWithUID(EADDRESSBOOKTYPE, "uid1", create=True)
-            txn2.commit()
-            return home_uid1_2
-        d1 = deferToThread(_defer_home_uid1_2)
-
-        def _pause_home_uid1_1():
-            time.sleep(1)
-            txn1.commit()
-        d2 = deferToThread(_pause_home_uid1_1)
-
-        # Verify that we can still get to the existing home - i.e. the lock
-        # on the table allows concurrent reads
-        txn4 = addressbookStore3.newTransaction()
-        home_uid2 = txn4.homeWithUID(EADDRESSBOOKTYPE, "uid2", create=True)
-        self.assertNotEqual(home_uid2, None)
-        txn4.commit()
-
-        # Now do the concurrent provision attempt
-        yield d2
-        home_uid1_2 = yield d1
-
-        self.assertNotEqual(home_uid1_1, None)
-        self.assertNotEqual(home_uid1_2, None)
-
-
-    @inlineCallbacks
     def test_putConcurrency(self):
         """
         Test that two concurrent attempts to PUT different address book object resources to the
@@ -245,21 +201,22 @@
 
         # Provision the home now
         txn = addressbookStore1.newTransaction()
-        home = txn.homeWithUID(EADDRESSBOOKTYPE, "uid1", create=True)
+        home = yield txn.homeWithUID(EADDRESSBOOKTYPE, "uid1", create=True)
         self.assertNotEqual(home, None)
-        txn.commit()
+        yield txn.commit()
 
         txn1 = addressbookStore1.newTransaction()
         txn2 = addressbookStore2.newTransaction()
 
-        home1 = txn1.homeWithUID(EADDRESSBOOKTYPE, "uid1", create=True)
-        home2 = txn2.homeWithUID(EADDRESSBOOKTYPE, "uid1", create=True)
-        
-        adbk1 = home1.addressbookWithName("addressbook")
-        adbk2 = home2.addressbookWithName("addressbook")
-        
+        home1 = yield txn1.homeWithUID(EADDRESSBOOKTYPE, "uid1", create=True)
+        home2 = yield txn2.homeWithUID(EADDRESSBOOKTYPE, "uid1", create=True)
+
+        adbk1 = yield home1.addressbookWithName("addressbook")
+        adbk2 = yield home2.addressbookWithName("addressbook")
+
+        @inlineCallbacks
         def _defer1():
-            adbk1.createObjectResourceWithName("1.vcf", VCard.fromString(
+            yield adbk1.createObjectResourceWithName("1.vcf", VCard.fromString(
                 """BEGIN:VCARD
 VERSION:3.0
 N:Thompson;Default1;;;
@@ -273,11 +230,12 @@
 END:VCARD
 """.replace("\n", "\r\n")
             ))
-            txn1.commit()
-        d1 = deferToThread(_defer1)
-            
+            yield txn1.commit() # FIXME: CONCURRENT
+        d1 = _defer1()
+
+        @inlineCallbacks
         def _defer2():
-            adbk2.createObjectResourceWithName("2.vcf", VCard.fromString(
+            yield adbk2.createObjectResourceWithName("2.vcf", VCard.fromString(
                 """BEGIN:VCARD
 VERSION:3.0
 N:Thompson;Default2;;;
@@ -291,8 +249,8 @@
 END:VCARD
 """.replace("\n", "\r\n")
             ))
-            txn2.commit()
-        d2 = deferToThread(_defer2)
+            yield txn2.commit() # FIXME: CONCURRENT
+        d2 = _defer2()
 
         yield d1
         yield d2

Modified: CalendarServer/trunk/txdav/carddav/datastore/util.py
===================================================================
--- CalendarServer/trunk/txdav/carddav/datastore/util.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/txdav/carddav/datastore/util.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -18,6 +18,7 @@
 """
 Utility logic common to multiple backend implementations.
 """
+from twisted.internet.defer import inlineCallbacks
 
 from twistedcaldav.vcard import Component as VCard
 from twistedcaldav.vcard import InvalidVCardDataError
@@ -28,6 +29,7 @@
 from twext.python.log import Logger
 log = Logger()
 
+
 def validateAddressBookComponent(addressbookObject, vcard, component, inserting):
     """
     Validate an addressbook component for a particular addressbook.
@@ -61,6 +63,8 @@
         raise InvalidObjectResourceError(e)
 
 
+
+ at inlineCallbacks
 def _migrateAddressbook(inAddressbook, outAddressbook, getComponent):
     """
     Copy all addressbook objects and properties in the given input addressbook
@@ -72,17 +76,18 @@
     @param getComponent: a 1-argument callable; see L{migrateHome}.
     """
     outAddressbook.properties().update(inAddressbook.properties())
-    for addressbookObject in inAddressbook.addressbookObjects():
+    inObjects = yield inAddressbook.addressbookObjects()
+    for addressbookObject in inObjects:
         
         try:
-            outAddressbook.createAddressBookObjectWithName(
+            yield outAddressbook.createAddressBookObjectWithName(
                 addressbookObject.name(),
-                addressbookObject.component()) # XXX WRONG SHOULD CALL getComponent
+                (yield addressbookObject.component())) # XXX WRONG SHOULD CALL getComponent
     
             # Only the owner's properties are migrated, since previous releases of
             # addressbook server didn't have per-user properties.
-            outAddressbook.addressbookObjectWithName(
-                addressbookObject.name()).properties().update(
+            (yield outAddressbook.addressbookObjectWithName(
+                addressbookObject.name())).properties().update(
                     addressbookObject.properties())
 
         except InternalDataStoreError:
@@ -93,16 +98,20 @@
             ))
 
 
+
+ at inlineCallbacks
 def migrateHome(inHome, outHome, getComponent=lambda x:x.component()):
-    outHome.removeAddressBookWithName("addressbook")
+    yield outHome.removeAddressBookWithName("addressbook")
     outHome.properties().update(inHome.properties())
-    for addressbook in inHome.addressbooks():
+    inAddressbooks = yield inHome.addressbooks()
+    for addressbook in inAddressbooks:
         name = addressbook.name()
-        outHome.createAddressBookWithName(name)
-        outAddressbook = outHome.addressbookWithName(name)
+        yield outHome.createAddressBookWithName(name)
+        outAddressbook = yield outHome.addressbookWithName(name)
         try:
-            _migrateAddressbook(addressbook, outAddressbook, getComponent)
+            yield _migrateAddressbook(addressbook, outAddressbook, getComponent)
         except InternalDataStoreError:
             log.error("  Failed to migrate address book: %s/%s" % (inHome.name(), name,))
 
 
+

Modified: CalendarServer/trunk/txdav/carddav/iaddressbookstore.py
===================================================================
--- CalendarServer/trunk/txdav/carddav/iaddressbookstore.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/txdav/carddav/iaddressbookstore.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -44,8 +44,8 @@
         If C{create} is C{True}, create the addressbook home if it doesn't
         already exist.
 
-        @return: an L{IAddressBookHome} or C{None} if no such addressbook
-            home exists.
+        @return: a L{Deferred} which fires with an L{IAddressBookHome} or
+            C{None} if no such addressbook home exists.
         """
 
 
@@ -147,8 +147,9 @@
         in this addressbook.
 
         @param name: a string.
-        @return: an L{IAddressBookObject} or C{None} if no such addressbook
-            object exists.
+        
+        @return: a L{Deferred} that fires with an L{IAddressBookObject} or
+            C{None} if no such addressbook object exists.
         """
 
     def addressbookObjectWithUID(uid):

Modified: CalendarServer/trunk/txdav/common/datastore/file.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/file.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/txdav/common/datastore/file.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -49,7 +49,6 @@
 from errno import EEXIST, ENOENT
 from zope.interface import implements, directlyProvides
 
-import os
 import uuid
 
 ECALENDARTYPE = 0
@@ -891,7 +890,6 @@
     listNotificationObjects = CommonHomeChild.listObjectResources
     notificationObjectWithName = CommonHomeChild.objectResourceWithName
     removeNotificationObjectWithUID = CommonHomeChild.removeObjectResourceWithUID
-    notificationObjectsSinceToken = CommonHomeChild.objectResourcesSinceToken
 
     def notificationObjectWithUID(self, uid):
         name = uid + ".xml"

Modified: CalendarServer/trunk/txdav/common/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/txdav/common/datastore/sql.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -25,6 +25,10 @@
     "CommonHome",
 ]
 
+import sys
+
+from Queue import Queue
+
 from twext.python.log import Logger, LoggingMixIn
 from twext.web2.dav.element.rfc2518 import ResourceType
 from twext.web2.http_headers import MimeType
@@ -34,6 +38,10 @@
 from twisted.python.modules import getModule
 from twisted.python.util import FancyEqMixin
 
+from twisted.internet import reactor
+from twisted.internet.defer import inlineCallbacks, returnValue, Deferred
+from twisted.python.failure import Failure
+
 from twistedcaldav.customxml import NotificationType
 from twistedcaldav.dateops import datetimeMktime
 
@@ -52,7 +60,7 @@
 from txdav.common.inotifications import INotificationCollection, \
     INotificationObject
 from txdav.base.datastore.sql import memoized
-from txdav.base.datastore.util import cached
+
 from txdav.idav import AlreadyFinishedError
 from txdav.base.propertystore.base import PropertyName
 from txdav.base.propertystore.sql import PropertyStore
@@ -108,7 +116,7 @@
     def newTransaction(self, label="unlabeled", migrating=False):
         return CommonStoreTransaction(
             self,
-            self.connectionFactory(),
+            self.connectionFactory,
             self.enableCalendars,
             self.enableAddressBooks,
             self.notifierFactory,
@@ -116,18 +124,104 @@
             migrating,
         )
 
+
+_DONE = object()
+
+_STATE_STOPPED = 'STOPPED'
+_STATE_RUNNING = 'RUNNING'
+_STATE_STOPPING = 'STOPPING'
+
+class ThreadHolder(object):
+    """
+    A queue which will hold a reactor threadpool thread open until all of the
+    work in that queue is done.
+    """
+
+    def __init__(self, reactor):
+        self._reactor = reactor
+        self._state = _STATE_STOPPED
+        self._stopper = None
+        self._q = None
+
+
+    def _run(self):
+        """
+        Worker function which runs in a non-reactor thread.
+        """
+        while True:
+            work = self._q.get()
+            if work is _DONE:
+                def finishStopping():
+                    self._state = _STATE_STOPPED
+                    self._q = None
+                    s = self._stopper
+                    self._stopper = None
+                    s.callback(None)
+                self._reactor.callFromThread(finishStopping)
+                return
+            self._oneWorkUnit(*work)
+
+
+    def _oneWorkUnit(self, deferred, instruction):
+        try: 
+            result = instruction()
+        except:
+            etype, evalue, etb = sys.exc_info()
+            def relayFailure():
+                f = Failure(evalue, etype, etb)
+                deferred.errback(f)
+            self._reactor.callFromThread(relayFailure)
+        else:
+            self._reactor.callFromThread(deferred.callback, result)
+
+
+    def submit(self, work):
+        """
+        Submit some work to be run.
+
+        @param work: a 0-argument callable, which will be run in a thread.
+
+        @return: L{Deferred} that fires with the result of L{work}
+        """
+        d = Deferred()
+        self._q.put((d, work))
+        return d
+
+
+    def start(self):
+        """
+        Start this thing, if it's stopped.
+        """
+        if self._state != _STATE_STOPPED:
+            raise RuntimeError("Not stopped.")
+        self._state = _STATE_RUNNING
+        self._q = Queue(0)
+        self._reactor.callInThread(self._run)
+
+
+    def stop(self):
+        """
+        Stop this thing and release its thread, if it's running.
+        """
+        if self._state != _STATE_RUNNING:
+            raise RuntimeError("Not running.")
+        s = self._stopper = Deferred()
+        self._state = _STATE_STOPPING
+        self._q.put(_DONE)
+        return s
+
+
+
 class CommonStoreTransaction(object):
     """
     Transaction implementation for SQL database.
     """
-
     _homeClass = {}
 
-    def __init__(self, store, connection, enableCalendars, enableAddressBooks, notifierFactory, label, migrating=False):
-
+    def __init__(self, store, connectionFactory,
+                 enableCalendars, enableAddressBooks,
+                 notifierFactory, label, migrating=False):
         self._store = store
-        self._connection = connection
-        self._cursor = connection.cursor()
         self._completed = False
         self._calendarHomes = {}
         self._addressbookHomes = {}
@@ -148,7 +242,20 @@
         from txdav.carddav.datastore.sql import AddressBookHome
         CommonStoreTransaction._homeClass[ECALENDARTYPE] = CalendarHome
         CommonStoreTransaction._homeClass[EADDRESSBOOKTYPE] = AddressBookHome
+        self._holder = ThreadHolder(reactor)
+        self._holder.start()
+        def initCursor():
+            # support threadlevel=1; we can't necessarily cursor() in a
+            # different thread than we do transactions in.
 
+            # FIXME: may need to be pooling ThreadHolders along with
+            # connections, if threadlevel=1 requires connect() be called in the
+            # same thread as cursor() et. al.
+            self._connection = connectionFactory()
+            self._cursor = self._connection.cursor()
+        self._holder.submit(initCursor)
+
+
     def store(self):
         return self._store
 
@@ -157,8 +264,7 @@
         return 'PG-TXN<%s>' % (self._label,)
 
 
-    def execSQL(self, sql, args=[], raiseOnZeroRowCount=None):
-        # print 'EXECUTE %s: %s' % (self._label, sql)
+    def _reallyExecSQL(self, sql, args=[], raiseOnZeroRowCount=None):
         self._cursor.execute(sql, args)
         if raiseOnZeroRowCount is not None and self._cursor.rowcount == 0:
             raise raiseOnZeroRowCount()
@@ -168,75 +274,99 @@
             return None
 
 
+    noisy = False
+
+    def execSQL(self, *args, **kw):
+        result = self._holder.submit(
+            lambda : self._reallyExecSQL(*args, **kw)
+        )
+        if self.noisy:
+            def reportResult(results):
+                sys.stdout.write("\n".join([
+                    "",
+                    "SQL: %r %r" % (args, kw),
+                    "Results: %r" % (results,),
+                    "",
+                    ]))
+                return results
+            result.addBoth(reportResult)
+        return result
+
+
     def __del__(self):
         if not self._completed:
-            self._connection.rollback()
-            self._connection.close()
+            print 'CommonStoreTransaction.__del__: OK'
+            self.abort()
 
 
     @memoized('uid', '_calendarHomes')
     def calendarHomeWithUID(self, uid, create=False):
         return self.homeWithUID(ECALENDARTYPE, uid, create=create)
 
+
     @memoized('uid', '_addressbookHomes')
     def addressbookHomeWithUID(self, uid, create=False):
         return self.homeWithUID(EADDRESSBOOKTYPE, uid, create=create)
 
+
+    @inlineCallbacks
     def homeWithUID(self, storeType, uid, create=False):
-
         if storeType == ECALENDARTYPE:
             homeTable = CALENDAR_HOME_TABLE
         elif storeType == EADDRESSBOOKTYPE:
             homeTable = ADDRESSBOOK_HOME_TABLE
+        else:
+            raise RuntimeError("Unknown home type.")
 
-        data = self.execSQL(
-            "select %(column_RESOURCE_ID)s from %(name)s where %(column_OWNER_UID)s = %%s" % homeTable,
+        data = yield self.execSQL(
+            "select %(column_RESOURCE_ID)s from %(name)s"
+            " where %(column_OWNER_UID)s = %%s" % homeTable,
             [uid]
         )
         if not data:
             if not create:
-                return None
-
+                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
-            self.execSQL(
+            # 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" % homeTable,
             )
-
             # Now test again
-            data = self.execSQL(
-                "select %(column_RESOURCE_ID)s from %(name)s where %(column_OWNER_UID)s = %%s" % homeTable,
+            data = yield self.execSQL(
+                "select %(column_RESOURCE_ID)s from %(name)s"
+                " where %(column_OWNER_UID)s = %%s" % homeTable,
                 [uid]
             )
-
             if not data:
-                self.execSQL(
+                yield self.execSQL(
                     "insert into %(name)s (%(column_OWNER_UID)s) values (%%s)" % homeTable,
                     [uid]
                 )
-                home = self.homeWithUID(storeType, uid)
-                home.createdHome()
-                return home
+                home = yield self.homeWithUID(storeType, uid)
+                yield home.createdHome()
+                returnValue(home)
         resid = data[0][0]
-
         if self._notifierFactory:
-            notifier = self._notifierFactory.newNotifier(id=uid,
-                prefix=NotifierPrefixes[storeType])
+            notifier = self._notifierFactory.newNotifier(
+                id=uid, prefix=NotifierPrefixes[storeType]
+            )
         else:
             notifier = None
+        homeObject = self._homeClass[storeType](self, uid, resid, notifier)
+        yield homeObject._loadPropertyStore()
+        returnValue(homeObject)
 
-        return self._homeClass[storeType](self, uid, resid, notifier)
 
-
     @memoized('uid', '_notificationHomes')
+    @inlineCallbacks
     def notificationsWithUID(self, uid):
         """
         Implement notificationsWithUID.
         """
-        rows = self.execSQL(
+        rows = yield self.execSQL(
             """
             select %(column_RESOURCE_ID)s from %(name)s where
             %(column_OWNER_UID)s = %%s
@@ -246,34 +376,43 @@
             resourceID = rows[0][0]
             created = False
         else:
-            resourceID = str(self.execSQL(
+            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])
+            ))[0][0])
             created = True
         collection = NotificationCollection(self, uid, resourceID)
+        yield collection._loadPropertyStore()
         if created:
             collection._initSyncToken()
-        return collection
+        returnValue(collection)
 
+
     def abort(self):
         if not self._completed:
-            # print 'ABORTING', self._label
+            def reallyAbort():
+                self._connection.rollback()
+                self._connection.close()
             self._completed = True
-            self._connection.rollback()
-            self._connection.close()
+            result = self._holder.submit(reallyAbort)
+            self._holder.stop()
+            return result
         else:
             raise AlreadyFinishedError()
 
 
     def commit(self):
         if not self._completed:
-            # print 'COMPLETING', self._label
             self._completed = True
-            self._connection.commit()
-            self._connection.close()
-            for operation in self._postCommitOperations:
-                operation()
+            def postCommit(ignored):
+                for operation in self._postCommitOperations:
+                    operation()
+            def reallyCommit():
+                self._connection.commit()
+                self._connection.close()
+            result = self._holder.submit(reallyCommit).addCallback(postCommit)
+            self._holder.stop()
+            return result
         else:
             raise AlreadyFinishedError()
 
@@ -283,8 +422,9 @@
         Run things after 'commit.'
         """
         self._postCommitOperations.append(operation)
-        # FIXME: implement.
 
+
+
 class CommonHome(LoggingMixIn):
 
     _childClass = None
@@ -336,13 +476,16 @@
         return self.uid()
 
 
+    @inlineCallbacks
     def children(self):
         """
         Retrieve children contained in this home.
         """
-        names = self.listChildren()
+        x = []
+        names = yield self.listChildren()
         for name in names:
-            yield self.childWithName(name)
+            x.append((yield self.childWithName(name)))
+        returnValue(x)
 
 
     def listChildren(self):
@@ -353,6 +496,7 @@
         """
         return self._listChildren(owned=True)
 
+
     def listSharedChildren(self):
         """
         Retrieve the names of the children in this home.
@@ -361,6 +505,8 @@
         """
         return self._listChildren(owned=False)
 
+
+    @inlineCallbacks
     def _listChildren(self, owned):
         """
         Retrieve the names of the children in this home.
@@ -370,7 +516,7 @@
         # FIXME: not specified on the interface or exercised by the tests, but
         # required by clients of the implementation!
         if owned:
-            rows = self._txn.execSQL("""
+            rows = yield self._txn.execSQL("""
                 select %(column_RESOURCE_NAME)s from %(name)s
                 where
                   %(column_HOME_RESOURCE_ID)s = %%s and
@@ -379,7 +525,7 @@
                 [self._resourceID, _BIND_MODE_OWN]
             )
         else:
-            rows = self._txn.execSQL("""
+            rows = yield self._txn.execSQL("""
                 select %(column_RESOURCE_NAME)s from %(name)s
                 where
                   %(column_HOME_RESOURCE_ID)s = %%s and
@@ -390,7 +536,7 @@
             )
 
         names = [row[0] for row in rows]
-        return names
+        returnValue(names)
 
 
     @memoized('name', '_children')
@@ -400,11 +546,11 @@
         home.
 
         @param name: a string.
-        @return: an L{ICalendar} or C{None} if no such child
-            exists.
+        @return: an L{ICalendar} or C{None} if no such child exists.
         """
         return self._childWithName(name, owned=True)
 
+
     @memoized('name', '_sharedChildren')
     def sharedChildWithName(self, name):
         """
@@ -423,6 +569,8 @@
         """
         return self._childWithName(name, owned=False)
 
+
+    @inlineCallbacks
     def _childWithName(self, name, owned):
         """
         Retrieve the child with the given C{name} contained in this
@@ -434,7 +582,7 @@
         """
         
         if owned:
-            data = self._txn.execSQL("""
+            data = yield self._txn.execSQL("""
                 select %(column_RESOURCE_ID)s from %(name)s
                 where
                   %(column_RESOURCE_NAME)s = %%s and
@@ -448,7 +596,7 @@
                 ]
             )
         else:
-            data = self._txn.execSQL("""
+            data = yield self._txn.execSQL("""
                 select %(column_RESOURCE_ID)s from %(name)s
                 where
                   %(column_RESOURCE_NAME)s = %%s and
@@ -463,36 +611,40 @@
             )
 
         if not data:
-            return None
+            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
-        return self._childClass(self, name, resourceID, notifier)
+        child = self._childClass(self, name, resourceID, notifier)
+        yield child._loadPropertyStore()
+        returnValue(child)
 
+
+    @inlineCallbacks
     def createChildWithName(self, name):
         if name.startswith("."):
             raise HomeChildNameNotAllowedError(name)
 
-        rows = self._txn.execSQL(
+        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()
+            raise HomeChildNameAlreadyExistsError(name)
 
-        rows = self._txn.execSQL("select nextval('RESOURCE_ID_SEQ')")
+        rows = yield self._txn.execSQL("select nextval('RESOURCE_ID_SEQ')")
         resourceID = rows[0][0]
-        self._txn.execSQL(
+        yield self._txn.execSQL(
             "insert into %(name)s (%(column_RESOURCE_ID)s) values "
             "(%%s)" % self._childTable,
             [resourceID])
 
-        self._txn.execSQL("""
+        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,
@@ -503,7 +655,7 @@
              _BIND_STATUS_ACCEPTED]
         )
 
-        newChild = self.childWithName(name)
+        newChild = yield self.childWithName(name)
         newChild.properties()[
             PropertyName.fromElement(ResourceType)
         ] = newChild.resourceType()
@@ -517,14 +669,14 @@
         pass
 
 
+    @inlineCallbacks
     def removeChildWithName(self, name):
-        
-        child = self.childWithName(name)
+        child = yield self.childWithName(name)
         if not child:
             raise NoSuchHomeChildError()
-        child._deletedSyncToken()
+        yield child._deletedSyncToken()
 
-        self._txn.execSQL(
+        yield self._txn.execSQL(
             "delete from %(name)s where %(column_RESOURCE_ID)s = %%s" % self._childTable,
             [child._resourceID]
         )
@@ -535,8 +687,9 @@
         child.notifyChanged()
 
 
+    @inlineCallbacks
     def syncToken(self):
-        revision = self._txn.execSQL(
+        revision = (yield self._txn.execSQL(
             """
             select max(%(REV:column_REVISION)s) from %(REV:name)s
             where %(REV:column_RESOURCE_ID)s in (
@@ -548,9 +701,11 @@
             )
             """ % self._revisionBindJoinTable,
             [self._resourceID, self._resourceID,]
-        )[0][0]
-        return "%s#%s" % (self._resourceID, revision)
+        ))[0][0]
+        returnValue("%s#%s" % (self._resourceID, revision))
 
+
+    @inlineCallbacks
     def resourceNamesSinceToken(self, token, depth):
 
         results = [
@@ -560,7 +715,7 @@
                 wasdeleted
             )
             for path, collection, name, wasdeleted in
-            self._txn.execSQL("""
+            (yield self._txn.execSQL("""
                 select %(BIND:column_RESOURCE_NAME)s, %(REV:column_COLLECTION_NAME)s, %(REV:column_RESOURCE_NAME)s, %(REV:column_DELETED)s
                 from %(REV:name)s
                 left outer join %(BIND:name)s on (
@@ -572,7 +727,7 @@
                   %(REV:name)s.%(REV:column_HOME_RESOURCE_ID)s = %%s
                 """ % self._revisionBindJoinTable,
                 [self._resourceID, token, self._resourceID],
-            )
+            ))
         ]
         
         deleted = []
@@ -593,10 +748,10 @@
                     changed_collections.add(path)
         
         # Now deal with shared collections
-        shares = self.listSharedChildren()
+        shares = yield self.listSharedChildren()
         for sharename in shares:
             sharetoken = 0 if sharename in changed_collections else token
-            shareID = self._txn.execSQL("""
+            shareID = (yield self._txn.execSQL("""
                 select %(column_RESOURCE_ID)s from %(name)s
                 where
                   %(column_RESOURCE_NAME)s = %%s and
@@ -608,7 +763,7 @@
                     self._resourceID,
                     _BIND_MODE_OWN
                 ]
-            )[0][0]
+            ))[0][0]
             results = [
                 (
                     sharename,
@@ -616,13 +771,13 @@
                     wasdeleted
                 )
                 for name, wasdeleted in
-                self._txn.execSQL("""
+                (yield self._txn.execSQL("""
                     select %(column_RESOURCE_NAME)s, %(column_DELETED)s
                     from %(name)s
                     where %(column_REVISION)s > %%s and %(column_RESOURCE_ID)s = %%s
                     """ % self._revisionsTable,
                     [sharetoken, shareID],
-                ) if name
+                )) if name
             ]
 
             for path, name, wasdeleted in results:
@@ -636,16 +791,22 @@
         
         changed.sort()
         deleted.sort()
-        return changed, deleted,
+        returnValue((changed, deleted))
 
-    @cached
-    def properties(self):
-        return PropertyStore(
+
+    @inlineCallbacks
+    def _loadPropertyStore(self):
+        props = yield PropertyStore.load(
             self.uid(),
             self._txn,
             self._resourceID
         )
+        self._propertyStore = props
 
+
+    def properties(self):
+        return self._propertyStore
+
     
     # IDataStoreResource
     def contentType(self):
@@ -725,16 +886,19 @@
     def retrieveOldInvites(self):
         return self._invites
 
+
     def __repr__(self):
         return "<%s: %s>" % (self.__class__.__name__, self._resourceID)
 
+
     def name(self):
         return self._name
 
 
+    @inlineCallbacks
     def rename(self, name):
         oldName = self._name
-        self._txn.execSQL(
+        yield self._txn.execSQL(
             "update %(name)s set %(column_RESOURCE_NAME)s = %%s "
             "where %(column_RESOURCE_ID)s = %%s AND "
             "%(column_HOME_RESOURCE_ID)s = %%s" % self._bindTable,
@@ -744,7 +908,7 @@
         # update memos
         del self._home._children[oldName]
         self._home._children[name] = self
-        self._renameSyncToken()
+        yield self._renameSyncToken()
 
         self.notifyChanged()
 
@@ -757,67 +921,92 @@
         self.properties()._setPerUserUID(uid)
 
 
+    @inlineCallbacks
     def objectResources(self):
-        for name in self.listObjectResources():
-            yield self.objectResourceWithName(name)
+        x = []
+        r = x.append
+        for name in (yield self.listObjectResources()):
+            r((yield self.objectResourceWithName(name)))
+        returnValue(x)
 
 
+    @inlineCallbacks
     def listObjectResources(self):
-        rows = self._txn.execSQL(
+        rows = yield self._txn.execSQL(
             "select %(column_RESOURCE_NAME)s from %(name)s "
             "where %(column_PARENT_RESOURCE_ID)s = %%s" % self._objectTable,
             [self._resourceID])
-        return sorted([row[0] for row in rows])
+        returnValue(sorted([row[0] for row in rows]))
 
 
     @memoized('name', '_objects')
+    @inlineCallbacks
     def objectResourceWithName(self, name):
-        rows = self._txn.execSQL(
-            "select %(column_RESOURCE_ID)s from %(name)s "
+        rows = yield self._txn.execSQL(
+            "select %(column_RESOURCE_ID)s, %(column_UID)s from %(name)s "
             "where %(column_RESOURCE_NAME)s = %%s and %(column_PARENT_RESOURCE_ID)s = %%s" % self._objectTable,
             [name, self._resourceID]
         )
         if not rows:
-            return None
-        resid = rows[0][0]
-        return self._objectResourceClass(name, self, resid)
+            returnValue(None)
+        [resid, uid] = rows[0]
+        returnValue((yield self._makeObjectResource(name, resid, uid)))
 
 
+    @inlineCallbacks
+    def _makeObjectResource(self, name, resid, uid):
+        """
+        Create an instance of C{self._objectResourceClass}.
+        """
+        objectResource = yield self._objectResourceClass(
+            name, self, resid, uid
+        )
+        yield objectResource._loadPropertyStore()
+        returnValue(objectResource)
+
+
     @memoized('uid', '_objects')
+    @inlineCallbacks
     def objectResourceWithUID(self, uid):
-        rows = self._txn.execSQL(
-            "select %(column_RESOURCE_ID)s, %(column_RESOURCE_NAME)s from %(name)s "
-            "where %(column_UID)s = %%s and %(column_PARENT_RESOURCE_ID)s = %%s" % self._objectTable,
+        rows = yield self._txn.execSQL(
+            "select %(column_RESOURCE_ID)s, %(column_RESOURCE_NAME)s "
+            "from %(name)s where %(column_UID)s = %%s "
+            "and %(column_PARENT_RESOURCE_ID)s = %%s" % self._objectTable,
             [uid, self._resourceID]
         )
         if not rows:
-            return None
+            returnValue(None)
         resid = rows[0][0]
         name = rows[0][1]
-        return self._objectResourceClass(name, self, resid)
+        returnValue((yield self._makeObjectResource(name, resid, uid)))
 
 
+    @inlineCallbacks
     def createObjectResourceWithName(self, name, component):
         if name.startswith("."):
             raise ObjectResourceNameNotAllowedError(name)
 
-        rows = self._txn.execSQL(
+        rows = yield self._txn.execSQL(
             "select %(column_RESOURCE_ID)s from %(name)s "
-            "where %(column_RESOURCE_NAME)s = %%s and %(column_PARENT_RESOURCE_ID)s = %%s" % self._objectTable,
+            "where %(column_RESOURCE_NAME)s = %%s "
+            "and %(column_PARENT_RESOURCE_ID)s = %%s" % self._objectTable,
             [name, self._resourceID]
         )
         if rows:
             raise ObjectResourceNameAlreadyExistsError()
 
-        objectResource = self._objectResourceClass(name, self, None)
-        objectResource.setComponent(component, inserting=True)
+        objectResource = (
+            yield self._makeObjectResource(name, None, component.resourceUID())
+        )
+        yield objectResource.setComponent(component, inserting=True)
 
         # Note: setComponent triggers a notification, so we don't need to
         # call notify( ) here like we do for object removal.
 
 
+    @inlineCallbacks
     def removeObjectResourceWithName(self, name):
-        rows = self._txn.execSQL(
+        rows = yield self._txn.execSQL(
             "delete from %(name)s "
             "where %(column_RESOURCE_NAME)s = %%s and %(column_PARENT_RESOURCE_ID)s = %%s "
             "returning %(column_UID)s" % self._objectTable,
@@ -827,13 +1016,14 @@
         uid = rows[0][0]
         self._objects.pop(name, None)
         self._objects.pop(uid, None)
-        self._deleteRevision(name)
+        yield self._deleteRevision(name)
 
         self.notifyChanged()
 
 
+    @inlineCallbacks
     def removeObjectResourceWithUID(self, uid):
-        rows = self._txn.execSQL(
+        rows = yield self._txn.execSQL(
             "delete from %(name)s "
             "where %(column_UID)s = %%s and %(column_PARENT_RESOURCE_ID)s = %%s "
             "returning %(column_RESOURCE_NAME)s" % self._objectTable,
@@ -843,43 +1033,46 @@
         name = rows[0][0]
         self._objects.pop(name, None)
         self._objects.pop(uid, None)
-        self._deleteRevision(name)
+        yield self._deleteRevision(name)
 
         self.notifyChanged()
 
 
+    @inlineCallbacks
     def syncToken(self):
-        revision = self._txn.execSQL(
+        revision = (yield self._txn.execSQL(
             """
             select max(%(column_REVISION)s) from %(name)s
             where %(column_RESOURCE_ID)s = %%s and %(column_RESOURCE_NAME)s is not null
             """ % self._revisionsTable,
             [self._resourceID,]
-        )[0][0]
+        ))[0][0]
         if revision is None:
-            revision = self._txn.execSQL(
+            revision = (yield self._txn.execSQL(
                 """
                 select %(column_REVISION)s from %(name)s
                 where %(column_RESOURCE_ID)s = %%s and %(column_RESOURCE_NAME)s is null
                 """ % self._revisionsTable,
                 [self._resourceID,]
-            )[0][0]
-            
-        return "%s#%s" % (self._resourceID, revision,)
+            ))[0][0]
+        returnValue(("%s#%s" % (self._resourceID, revision,)))
 
+
     def objectResourcesSinceToken(self, token):
         raise NotImplementedError()
 
+
+    @inlineCallbacks
     def resourceNamesSinceToken(self, token):
         results = [
             (name if name else "", deleted)
             for name, deleted in
-            self._txn.execSQL("""
+            (yield self._txn.execSQL("""
                 select %(column_RESOURCE_NAME)s, %(column_DELETED)s from %(name)s
                 where %(column_REVISION)s > %%s and %(column_RESOURCE_ID)s = %%s
                 """ % self._revisionsTable,
                 [token, self._resourceID],
-            )
+            ))
         ]
         results.sort(key=lambda x:x[1])
         
@@ -893,12 +1086,14 @@
                 else:
                     changed.append(name)
         
-        return changed, deleted,
+        returnValue((changed, deleted))
 
+
+    @inlineCallbacks
     def _initSyncToken(self):
-        
+
         # Remove any deleted revision entry that uses the same name
-        self._txn.execSQL("""
+        yield self._txn.execSQL("""
             delete from %(name)s
             where %(column_HOME_RESOURCE_ID)s = %%s and %(column_COLLECTION_NAME)s = %%s
             """ % self._revisionsTable,
@@ -906,7 +1101,7 @@
         )
 
         # Insert new entry
-        self._txn.execSQL("""
+        yield self._txn.execSQL("""
             insert into %(name)s
             (%(column_HOME_RESOURCE_ID)s, %(column_RESOURCE_ID)s, %(column_COLLECTION_NAME)s, %(column_RESOURCE_NAME)s, %(column_REVISION)s, %(column_DELETED)s)
             values (%%s, %%s, %%s, null, nextval('%(sequence)s'), FALSE)
@@ -914,9 +1109,11 @@
             [self._home._resourceID, self._resourceID, self._name]
         )
 
+
+    @inlineCallbacks
     def _updateSyncToken(self):
 
-        self._txn.execSQL("""
+        yield self._txn.execSQL("""
             update %(name)s
             set (%(column_REVISION)s) = (nextval('%(sequence)s'))
             where %(column_RESOURCE_ID)s = %%s and %(column_RESOURCE_NAME)s is null
@@ -924,9 +1121,11 @@
             [self._resourceID,]
         )
 
+
+    @inlineCallbacks
     def _renameSyncToken(self):
 
-        self._txn.execSQL("""
+        yield self._txn.execSQL("""
             update %(name)s
             set (%(column_REVISION)s, %(column_COLLECTION_NAME)s) = (nextval('%(sequence)s'), %%s)
             where %(column_RESOURCE_ID)s = %%s and %(column_RESOURCE_NAME)s is null
@@ -934,19 +1133,21 @@
             [self._name, self._resourceID,]
         )
 
+
+    @inlineCallbacks
     def _deletedSyncToken(self):
 
         # Remove all child entries
-        self._txn.execSQL("""
+        yield self._txn.execSQL("""
             delete from %(name)s
             where %(column_HOME_RESOURCE_ID)s = %%s and %(column_RESOURCE_ID)s = %%s and %(column_COLLECTION_NAME)s is null
             """ % self._revisionsTable,
             [self._home._resourceID, self._resourceID,]
         )
-        
+
         # Then adjust collection entry to deleted state (do this for all entries with this collection's
         # resource-id so that we deal with direct shares which are not normally removed thorugh an unshare
-        self._txn.execSQL("""
+        yield self._txn.execSQL("""
             update %(name)s
             set (%(column_RESOURCE_ID)s, %(column_REVISION)s, %(column_DELETED)s)
              = (null, nextval('%(sequence)s'), TRUE)
@@ -955,24 +1156,27 @@
             [self._resourceID,]
         )
 
+
     def _insertRevision(self, name):
-        self._changeRevision("insert", name)
+        return self._changeRevision("insert", name)
 
     def _updateRevision(self, name):
-        self._changeRevision("update", name)
+        return self._changeRevision("update", name)
 
     def _deleteRevision(self, name):
-        self._changeRevision("delete", name)
+        return self._changeRevision("delete", name)
 
+
+    @inlineCallbacks
     def _changeRevision(self, action, name):
 
-        nextrevision = self._txn.execSQL("""
+        nextrevision = yield self._txn.execSQL("""
             select nextval('%(sequence)s')
             """ % self._revisionsTable
         )
 
         if action == "delete":
-            self._txn.execSQL("""
+            yield self._txn.execSQL("""
                 update %(name)s
                 set (%(column_REVISION)s, %(column_DELETED)s) = (%%s, TRUE)
                 where %(column_RESOURCE_ID)s = %%s and %(column_RESOURCE_NAME)s = %%s
@@ -980,7 +1184,7 @@
                 [nextrevision, self._resourceID, name]
             )
         elif action == "update":
-            self._txn.execSQL("""
+            yield self._txn.execSQL("""
                 update %(name)s
                 set (%(column_REVISION)s) = (%%s)
                 where %(column_RESOURCE_ID)s = %%s and %(column_RESOURCE_NAME)s = %%s
@@ -992,15 +1196,14 @@
             # was deleted. In that case an entry in the REVISIONS table still exists so we have to
             # detect that and do db INSERT or UPDATE as appropriate
 
-            self._txn.execSQL("""
+            found = bool( (yield self._txn.execSQL("""
                 select %(column_RESOURCE_ID)s from %(name)s
                 where %(column_RESOURCE_ID)s = %%s and %(column_RESOURCE_NAME)s = %%s
                 """ % self._revisionsTable,
                 [self._resourceID, name, ]
-            )
-            found = self._txn._cursor.rowcount != 0
+            )) )
             if found:
-                self._txn.execSQL("""
+                yield self._txn.execSQL("""
                     update %(name)s
                     set (%(column_REVISION)s, %(column_DELETED)s) = (%%s, FALSE)
                     where %(column_RESOURCE_ID)s = %%s and %(column_RESOURCE_NAME)s = %%s
@@ -1008,7 +1211,7 @@
                     [nextrevision, self._resourceID, name]
                 )
             else:
-                self._txn.execSQL("""
+                yield self._txn.execSQL("""
                     insert into %(name)s
                     (%(column_HOME_RESOURCE_ID)s, %(column_RESOURCE_ID)s, %(column_RESOURCE_NAME)s, %(column_REVISION)s, %(column_DELETED)s)
                     values (%%s, %%s, %%s, %%s, FALSE)
@@ -1016,16 +1219,22 @@
                     [self._home._resourceID, self._resourceID, name, nextrevision]
                 )
 
-    @cached
-    def properties(self):
-        props = PropertyStore(
+
+    @inlineCallbacks
+    def _loadPropertyStore(self):
+        props = yield PropertyStore.load(
             self.ownerHome().uid(),
             self._txn,
             self._resourceID
         )
         self.initPropertyStore(props)
-        return props
+        self._properties = props
 
+
+    def properties(self):
+        return self._properties
+
+
     def initPropertyStore(self, props):
         """
         A hook for subclasses to override in order to set up their property
@@ -1033,11 +1242,12 @@
 
         @param props: the L{PropertyStore} from C{properties()}.
         """
-        pass
 
+
     def _doValidate(self, component):
         raise NotImplementedError
 
+
     # IDataStoreResource
     def contentType(self):
         raise NotImplementedError()
@@ -1051,38 +1261,44 @@
         return 0
 
 
+    @inlineCallbacks
     def created(self):
-        created = self._txn.execSQL(
+        created = (yield self._txn.execSQL(
             "select %(column_CREATED)s from %(name)s "
             "where %(column_RESOURCE_ID)s = %%s" % self._homeChildTable,
             [self._resourceID]
-        )[0][0]
+        ))[0][0]
         utc = datetime.datetime.strptime(created, "%Y-%m-%d %H:%M:%S.%f")
-        return datetimeMktime(utc)
+        returnValue(datetimeMktime(utc))
 
+
+    @inlineCallbacks
     def modified(self):
-        modified = self._txn.execSQL(
+        modified = (yield self._txn.execSQL(
             "select %(column_MODIFIED)s from %(name)s "
             "where %(column_RESOURCE_ID)s = %%s" % self._homeChildTable,
             [self._resourceID]
-        )[0][0]
+        ))[0][0]
         utc = datetime.datetime.strptime(modified, "%Y-%m-%d %H:%M:%S.%f")
-        return datetimeMktime(utc)
+        returnValue(datetimeMktime(utc))
 
+
     def notifierID(self, label="default"):
         if self._notifier:
             return self._notifier.getID(label)
         else:
             return None
 
+
     def notifyChanged(self):
         """
         Trigger a notification of a change
         """
         if self._notifier:
             self._txn.postCommit(self._notifier.notify)
-        
 
+
+
 class CommonObjectResource(LoggingMixIn, FancyEqMixin):
     """
     @ivar _path: The path of the file on disk
@@ -1094,19 +1310,38 @@
 
     _objectTable = None
 
-    def __init__(self, name, parent, resid):
+    def __init__(self, name, parent, resid, uid):
         self._name = name
         self._parentCollection = parent
         self._resourceID = resid
         self._objectText = None
+        self._uid = uid
 
+
+    @inlineCallbacks
+    def _loadPropertyStore(self):
+        props = yield PropertyStore.load(
+            self.uid(),
+            self._txn,
+            self._resourceID
+        )
+        self.initPropertyStore(props)
+        self._propertyStore = props
+
+
+    def properties(self):
+        return self._propertyStore
+
+
     def __repr__(self):
         return "<%s: %s>" % (self.__class__.__name__, self._resourceID)
 
+
     @property
     def _txn(self):
         return self._parentCollection._txn
 
+
     def setComponent(self, component, inserting=False):
         raise NotImplementedError
 
@@ -1115,23 +1350,19 @@
         raise NotImplementedError
 
 
-    def text(self):
-        raise NotImplementedError
+    @inlineCallbacks
+    def componentType(self):
+        returnValue((yield self.component()).mainType())
 
 
     def uid(self):
-        raise NotImplementedError
+        return self._uid
 
-    @cached
-    def properties(self):
-        props = PropertyStore(
-            self.uid(),
-            self._txn,
-            self._resourceID
-        )
-        self.initPropertyStore(props)
-        return props
 
+    def name(self):
+        return self._name
+
+
     def initPropertyStore(self, props):
         """
         A hook for subclasses to override in order to set up their property
@@ -1139,42 +1370,64 @@
 
         @param props: the L{PropertyStore} from C{properties()}.
         """
-        pass
 
+
     # IDataStoreResource
     def contentType(self):
         raise NotImplementedError()
 
+
     def md5(self):
         return None
 
+
+    @inlineCallbacks
     def size(self):
-        size = self._txn.execSQL(
+        size = (yield self._txn.execSQL(
             "select character_length(%(column_TEXT)s) from %(name)s "
             "where %(column_RESOURCE_ID)s = %%s" % self._objectTable,
             [self._resourceID]
-        )[0][0]
-        return size
+        ))[0][0]
+        returnValue(size)
 
 
+    @inlineCallbacks
     def created(self):
-        created = self._txn.execSQL(
+        created = (yield self._txn.execSQL(
             "select %(column_CREATED)s from %(name)s "
             "where %(column_RESOURCE_ID)s = %%s" % self._objectTable,
             [self._resourceID]
-        )[0][0]
+        ))[0][0]
         utc = datetime.datetime.strptime(created, "%Y-%m-%d %H:%M:%S.%f")
-        return datetimeMktime(utc)
+        returnValue(datetimeMktime(utc))
 
+
+    @inlineCallbacks
     def modified(self):
-        modified = self._txn.execSQL(
+        modified = (yield self._txn.execSQL(
             "select %(column_MODIFIED)s from %(name)s "
             "where %(column_RESOURCE_ID)s = %%s" % self._objectTable,
             [self._resourceID]
-        )[0][0]
+        ))[0][0]
         utc = datetime.datetime.strptime(modified, "%Y-%m-%d %H:%M:%S.%f")
-        return datetimeMktime(utc)
+        returnValue(datetimeMktime(utc))
 
+
+    @inlineCallbacks
+    def text(self):
+        if self._objectText is None:
+            text = (yield self._txn.execSQL(
+                "select %(column_TEXT)s from %(name)s where "
+                "%(column_RESOURCE_ID)s = %%s" % self._objectTable,
+                [self._resourceID]
+            ))[0][0]
+            self._objectText = text
+            returnValue(text)
+        else:
+            returnValue(self._objectText)
+
+
+
 class NotificationCollection(LoggingMixIn, FancyEqMixin):
 
     implements(INotificationCollection)
@@ -1192,6 +1445,15 @@
         self._notifications = {}
 
 
+    @inlineCallbacks
+    def _loadPropertyStore(self):
+        self._propertyStore = yield PropertyStore.load(
+            self._uid,
+            self._txn,
+            self._resourceID
+        )
+
+
     def resourceType(self):
         return ResourceType.notification #@UndefinedVariable
 
@@ -1207,17 +1469,24 @@
     def uid(self):
         return self._uid
 
+
+    @inlineCallbacks
     def notificationObjects(self):
-        for name in self.listNotificationObjects():
-            yield self.notificationObjectWithName(name)
+        L = []
+        for name in (yield self.listNotificationObjects()):
+            L.append((yield self.notificationObjectWithName(name)))
+        returnValue(L)
 
+
+    @inlineCallbacks
     def listNotificationObjects(self):
-        rows = self._txn.execSQL(
+        rows = yield self._txn.execSQL(
             "select (NOTIFICATION_UID) from NOTIFICATION "
             "where NOTIFICATION_HOME_RESOURCE_ID = %s",
             [self._resourceID])
-        return sorted(["%s.xml" % row[0] for row in rows])
+        returnValue(sorted(["%s.xml" % row[0] for row in rows]))
 
+
     def _nameToUID(self, name):
         """
         Based on the file-backed implementation, the 'name' is just uid +
@@ -1229,48 +1498,55 @@
     def notificationObjectWithName(self, name):
         return self.notificationObjectWithUID(self._nameToUID(name))
 
+
     @memoized('uid', '_notifications')
+    @inlineCallbacks
     def notificationObjectWithUID(self, uid):
-        rows = self._txn.execSQL(
+        rows = (yield self._txn.execSQL(
             "select RESOURCE_ID from NOTIFICATION "
             "where NOTIFICATION_UID = %s and NOTIFICATION_HOME_RESOURCE_ID = %s",
-            [uid, self._resourceID])
+            [uid, self._resourceID]))
         if rows:
             resourceID = rows[0][0]
-            return NotificationObject(self, resourceID)
+            no = NotificationObject(self, uid, resourceID)
+            yield no._loadPropertyStore()
+            returnValue(no)
         else:
-            return None
+            returnValue(None)
 
 
+    @inlineCallbacks
     def writeNotificationObject(self, uid, xmltype, xmldata):
 
         inserting = False
-        notificationObject = self.notificationObjectWithUID(uid)
+        notificationObject = yield self.notificationObjectWithUID(uid)
         if notificationObject is None:
-            notificationObject = NotificationObject(self, None)
+            notificationObject = NotificationObject(self, uid, None)
             inserting = True
-        notificationObject.setData(uid, xmltype, xmldata, inserting=inserting)
+        yield notificationObject.setData(uid, xmltype, xmldata, inserting=inserting)
         if inserting:
-            self._insertRevision("%s.xml" % (uid,))
+            yield self._insertRevision("%s.xml" % (uid,))
         else:
-            self._updateRevision("%s.xml" % (uid,))
+            yield self._updateRevision("%s.xml" % (uid,))
 
+
     def removeNotificationObjectWithName(self, name):
-        self.removeNotificationObjectWithUID(self._nameToUID(name))
+        return self.removeNotificationObjectWithUID(self._nameToUID(name))
 
 
+    @inlineCallbacks
     def removeNotificationObjectWithUID(self, uid):
-        self._txn.execSQL(
+        yield self._txn.execSQL(
             "delete from NOTIFICATION "
             "where NOTIFICATION_UID = %s and NOTIFICATION_HOME_RESOURCE_ID = %s",
             [uid, self._resourceID]
         )
         self._notifications.pop(uid, None)
-        self._deleteRevision("%s.xml" % (uid,))
+        yield self._deleteRevision("%s.xml" % (uid,))
 
 
     def _initSyncToken(self):
-        self._txn.execSQL("""
+        return self._txn.execSQL("""
             insert into %(name)s
             (%(column_HOME_RESOURCE_ID)s, %(column_RESOURCE_NAME)s, %(column_REVISION)s, %(column_DELETED)s)
             values (%%s, null, nextval('%(sequence)s'), FALSE)
@@ -1278,49 +1554,46 @@
             [self._resourceID,]
         )
 
+
+    @inlineCallbacks
     def syncToken(self):
-        revision = self._txn.execSQL(
+        revision = (yield self._txn.execSQL(
             """
             select max(%(column_REVISION)s) from %(name)s
             where %(column_HOME_RESOURCE_ID)s = %%s and %(column_RESOURCE_NAME)s is not null
             """ % self._revisionsTable,
             [self._resourceID,]
-        )[0][0]
+        ))[0][0]
+
         if revision is None:
-            revision = self._txn.execSQL(
+            revision = (yield self._txn.execSQL(
                 """
                 select %(column_REVISION)s from %(name)s
                 where %(column_HOME_RESOURCE_ID)s = %%s and %(column_RESOURCE_NAME)s is null
                 """ % self._revisionsTable,
                 [self._resourceID,]
-            )[0][0]
+            ))[0][0]
+        returnValue("%s#%s" % (self._resourceID, revision,))
 
-        return "%s#%s" % (self._resourceID, revision,)
 
     def objectResourcesSinceToken(self, token):
         raise NotImplementedError()
 
 
-    def notificationObjectsSinceToken(self, token):
-        changed = []
-        removed = []
-        token = self.syncToken()
-        return (changed, removed, token)
-
-
+    @inlineCallbacks
     def resourceNamesSinceToken(self, token):
         results = [
             (name if name else "", deleted)
             for name, deleted in
-            self._txn.execSQL("""
+            (yield self._txn.execSQL("""
                 select %(column_RESOURCE_NAME)s, %(column_DELETED)s from %(name)s
                 where %(column_REVISION)s > %%s and %(column_HOME_RESOURCE_ID)s = %%s
                 """ % self._revisionsTable,
                 [token, self._resourceID],
-            )
+            ))
         ]
         results.sort(key=lambda x:x[1])
-        
+
         changed = []
         deleted = []
         for name, wasdeleted in results:
@@ -1330,12 +1603,12 @@
                         deleted.append(name)
                 else:
                     changed.append(name)
-        
-        return changed, deleted,
 
+        returnValue((changed, deleted))
+
+
     def _updateSyncToken(self):
-
-        self._txn.execSQL("""
+        return self._txn.execSQL("""
             update %(name)s
             set (%(column_REVISION)s) = (nextval('%(sequence)s'))
             where %(column_HOME_RESOURCE_ID)s = %%s and %(column_RESOURCE_NAME)s is null
@@ -1343,24 +1616,29 @@
             [self._resourceID,]
         )
 
+
     def _insertRevision(self, name):
-        self._changeRevision("insert", name)
+        return self._changeRevision("insert", name)
 
+
     def _updateRevision(self, name):
-        self._changeRevision("update", name)
+        return self._changeRevision("update", name)
 
+
     def _deleteRevision(self, name):
-        self._changeRevision("delete", name)
+        return self._changeRevision("delete", name)
 
+
+    @inlineCallbacks
     def _changeRevision(self, action, name):
 
-        nextrevision = self._txn.execSQL("""
+        nextrevision = yield self._txn.execSQL("""
             select nextval('%(sequence)s')
             """ % self._revisionsTable
         )
 
         if action == "delete":
-            self._txn.execSQL("""
+            yield self._txn.execSQL("""
                 update %(name)s
                 set (%(column_REVISION)s, %(column_DELETED)s) = (%%s, TRUE)
                 where %(column_HOME_RESOURCE_ID)s = %%s and %(column_RESOURCE_NAME)s = %%s
@@ -1368,7 +1646,7 @@
                 [nextrevision, self._resourceID, name]
             )
         elif action == "update":
-            self._txn.execSQL("""
+            yield self._txn.execSQL("""
                 update %(name)s
                 set (%(column_REVISION)s) = (%%s)
                 where %(column_HOME_RESOURCE_ID)s = %%s and %(column_RESOURCE_NAME)s = %%s
@@ -1380,15 +1658,14 @@
             # was deleted. In that case an entry in the REVISIONS table still exists so we have to
             # detect that and do db INSERT or UPDATE as appropriate
 
-            self._txn.execSQL("""
+            found = bool( (yield self._txn.execSQL("""
                 select %(column_HOME_RESOURCE_ID)s from %(name)s
                 where %(column_HOME_RESOURCE_ID)s = %%s and %(column_RESOURCE_NAME)s = %%s
                 """ % self._revisionsTable,
                 [self._resourceID, name, ]
-            )
-            found = self._txn._cursor.rowcount != 0
+            )))
             if found:
-                self._txn.execSQL("""
+                yield self._txn.execSQL("""
                     update %(name)s
                     set (%(column_REVISION)s, %(column_DELETED)s) = (%%s, FALSE)
                     where %(column_HOME_RESOURCE_ID)s = %%s and %(column_RESOURCE_NAME)s = %%s
@@ -1396,7 +1673,7 @@
                     [nextrevision, self._resourceID, name]
                 )
             else:
-                self._txn.execSQL("""
+                yield self._txn.execSQL("""
                     insert into %(name)s
                     (%(column_HOME_RESOURCE_ID)s, %(column_RESOURCE_NAME)s, %(column_REVISION)s, %(column_DELETED)s)
                     values (%%s, %%s, %%s, FALSE)
@@ -1404,21 +1681,21 @@
                     [self._resourceID, name, nextrevision]
                 )
 
-    @cached
+
     def properties(self):
-        return PropertyStore(
-            self._uid,
-            self._txn,
-            self._resourceID
-        )
+        return self._propertyStore
 
+
+
 class NotificationObject(LoggingMixIn, FancyEqMixin):
+
     implements(INotificationObject)
 
     compareAttributes = '_resourceID _home'.split()
 
-    def __init__(self, home, resourceID):
+    def __init__(self, home, uid, resourceID):
         self._home = home
+        self._uid = uid
         self._resourceID = resourceID
 
 
@@ -1435,22 +1712,28 @@
         return self._home
 
 
+    def uid(self):
+        return self._uid
+
+
     def name(self):
         return self.uid() + ".xml"
 
 
+    @inlineCallbacks
     def setData(self, uid, xmltype, xmldata, inserting=False):
 
         xmltypeString = xmltype.toxml()
         if inserting:
-            rows = self._txn.execSQL(
+            rows = yield self._txn.execSQL(
                 "insert into NOTIFICATION (NOTIFICATION_HOME_RESOURCE_ID, NOTIFICATION_UID, XML_TYPE, XML_DATA) "
                 "values (%s, %s, %s, %s) returning RESOURCE_ID",
                 [self._home._resourceID, uid, xmltypeString, xmldata]
             )
             self._resourceID = rows[0][0]
+            yield self._loadPropertyStore()
         else:
-            self._txn.execSQL(
+            yield self._txn.execSQL(
                 "update NOTIFICATION set XML_TYPE = %s, XML_DATA = %s "
                 "where NOTIFICATION_HOME_RESOURCE_ID = %s and NOTIFICATION_UID = %s",
                 [xmltypeString, xmldata, self._home._resourceID, uid])
@@ -1458,33 +1741,34 @@
         self.properties()[PropertyName.fromElement(NotificationType)] = NotificationType(xmltype)
 
 
+    @inlineCallbacks
     def _fieldQuery(self, field):
-        data = self._txn.execSQL(
+        data = yield self._txn.execSQL(
             "select " + field + " from NOTIFICATION "
             "where RESOURCE_ID = %s",
             [self._resourceID]
         )
-        return data[0][0]
+        returnValue(data[0][0])
 
 
     def xmldata(self):
         return self._fieldQuery("XML_DATA")
 
 
-    def uid(self):
-        return self._fieldQuery("NOTIFICATION_UID")
+    def properties(self):
+        return self._propertyStore
 
 
-    @cached
-    def properties(self):
-        props = PropertyStore(
+    @inlineCallbacks
+    def _loadPropertyStore(self):
+        self._propertyStore = yield PropertyStore.load(
             self._home.uid(),
             self._txn,
             self._resourceID
         )
-        self.initPropertyStore(props)
-        return props
+        self.initPropertyStore(self._propertyStore)
 
+
     def initPropertyStore(self, props):
         # Setup peruser special properties
         props.setSpecialProperties(
@@ -1495,6 +1779,7 @@
             ),
         )
 
+
     def contentType(self):
         """
         The content type of NotificationObjects is text/xml.
@@ -1502,32 +1787,40 @@
         return MimeType.fromString("text/xml")
 
 
+    @inlineCallbacks
     def md5(self):
-        return hashlib.md5(self.xmldata()).hexdigest()
+        returnValue(hashlib.md5((yield self.xmldata())).hexdigest())
 
 
+    @inlineCallbacks
     def size(self):
-        size = self._txn.execSQL(
+        size = (yield self._txn.execSQL(
             "select character_length(XML_DATA) from NOTIFICATION "
             "where RESOURCE_ID = %s",
             [self._resourceID]
-        )[0][0]
-        return size
+        ))[0][0]
+        returnValue(size)
 
 
+    @inlineCallbacks
     def created(self):
-        created = self._txn.execSQL(
+        created = (yield self._txn.execSQL(
             "select CREATED from NOTIFICATION "
             "where RESOURCE_ID = %s",
             [self._resourceID]
-        )[0][0]
+        ))[0][0]
         utc = datetime.datetime.strptime(created, "%Y-%m-%d %H:%M:%S.%f")
-        return datetimeMktime(utc)
+        returnValue(datetimeMktime(utc))
 
+
+    @inlineCallbacks
     def modified(self):
-        modified = self._txn.execSQL(
+        modified = (yield self._txn.execSQL(
             "select MODIFIED from NOTIFICATION "
             "where RESOURCE_ID = %s", [self._resourceID]
-        )[0][0]
+        ))[0][0]
         utc = datetime.datetime.strptime(modified, "%Y-%m-%d %H:%M:%S.%f")
-        return datetimeMktime(utc)
+        returnValue(datetimeMktime(utc))
+
+
+

Modified: CalendarServer/trunk/txdav/common/datastore/sql_legacy.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql_legacy.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/txdav/common/datastore/sql_legacy.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -26,14 +26,13 @@
 from twistedcaldav.sharing import SharedCollectionRecord
 
 from twisted.python import hashlib
-from twisted.internet.defer import succeed
+from twisted.internet.defer import succeed, inlineCallbacks, returnValue
 
 from twext.python.log import Logger, LoggingMixIn
 
 from twistedcaldav import carddavxml
 from twistedcaldav.config import config
 from twistedcaldav.dateops import normalizeForIndex
-from twistedcaldav.index import IndexedSearchException, ReservationError
 from twistedcaldav.memcachepool import CachePoolUserMixIn
 from twistedcaldav.notifications import NotificationRecord
 from twistedcaldav.query import calendarqueryfilter, calendarquery, \
@@ -41,10 +40,12 @@
 from twistedcaldav.query.sqlgenerator import sqlgenerator
 from twistedcaldav.sharing import Invite
 
+from txdav.common.icommondatastore import IndexedSearchException, \
+    ReservationError
 from txdav.common.datastore.sql_tables import \
     _BIND_MODE_OWN, _BIND_MODE_READ, _BIND_MODE_WRITE, _BIND_MODE_DIRECT, \
-    _BIND_STATUS_INVITED, _BIND_STATUS_ACCEPTED, _BIND_STATUS_DECLINED, _BIND_STATUS_INVALID,\
-    CALENDAR_BIND_TABLE, CALENDAR_HOME_TABLE, ADDRESSBOOK_HOME_TABLE,\
+    _BIND_STATUS_INVITED, _BIND_STATUS_ACCEPTED, _BIND_STATUS_DECLINED, _BIND_STATUS_INVALID, \
+    CALENDAR_BIND_TABLE, CALENDAR_HOME_TABLE, ADDRESSBOOK_HOME_TABLE, \
     ADDRESSBOOK_BIND_TABLE
 
 log = Logger()
@@ -62,11 +63,18 @@
         self._collection = notificationsCollection
 
 
+    @inlineCallbacks
     def _recordForObject(self, notificationObject):
-        return NotificationRecord(
-            notificationObject.uid(),
-            notificationObject.name(),
-            notificationObject._fieldQuery("XML_TYPE")) if notificationObject else None
+        if notificationObject:
+            returnValue(
+                NotificationRecord(
+                    notificationObject.uid(),
+                    notificationObject.name(),
+                    (yield notificationObject._fieldQuery("XML_TYPE"))
+                )
+            )
+        else:
+            returnValue(None)
 
 
     def recordForName(self, name):
@@ -75,22 +83,22 @@
         )
 
 
+    @inlineCallbacks
     def recordForUID(self, uid):
-        return self._recordForObject(
-            self._collection.notificationObjectWithUID(uid)
-        )
+        returnValue((yield self._recordForObject(
+            (yield self._collection.notificationObjectWithUID(uid))
+        )))
 
 
     def removeRecordForUID(self, uid):
-        self._collection.removeNotificationObjectWithUID(uid)
+        return self._collection.removeNotificationObjectWithUID(uid)
 
 
     def removeRecordForName(self, name):
-        self._collection.removeNotificationObjectWithName(name)
+        return self._collection.removeNotificationObjectWithName(name)
 
 
 
-
 class SQLLegacyInvites(object):
     """
     Emulator for the implicit interface specified by
@@ -106,27 +114,32 @@
         # Since we do multi-table requests we need a dict that combines tables
         self._combinedTable = {}
         for key, value in self._homeTable.iteritems():
-            self._combinedTable["HOME:%s" % (key,)] = value 
+            self._combinedTable["HOME:%s" % (key,)] = value
         for key, value in self._bindTable.iteritems():
-            self._combinedTable["BIND:%s" % (key,)] = value 
+            self._combinedTable["BIND:%s" % (key,)] = value
 
+
     @property
     def _txn(self):
         return self._collection._txn
 
+
     def _getHomeWithUID(self, uid):
         raise NotImplementedError()
 
+
     def create(self):
         "No-op, because the index implicitly always exists in the database."
-        pass
 
+
     def remove(self):
         "No-op, because the index implicitly always exists in the database."
-        pass
 
+
+    @inlineCallbacks
     def allRecords(self):
-        for row in self._txn.execSQL(
+        values = []
+        for row in (yield self._txn.execSQL(
             """
             select
                 INVITE.INVITE_UID,
@@ -147,11 +160,14 @@
                 INVITE.NAME asc
             """ % self._combinedTable,
             [self._collection._resourceID]
-        ):
-            yield self._makeInvite(row)
+        )):
+            values.append(self._makeInvite(row))
+        returnValue(values)
 
+
+    @inlineCallbacks
     def recordForUserID(self, userid):
-        rows = self._txn.execSQL(
+        rows = yield self._txn.execSQL(
             """
             select
                 INVITE.INVITE_UID,
@@ -170,17 +186,19 @@
             """ % self._combinedTable,
             [userid]
         )
-        return self._makeInvite(rows[0]) if rows else None
+        returnValue(self._makeInvite(rows[0]) if rows else None)
 
 
+    @inlineCallbacks
     def recordForPrincipalURL(self, principalURL):
-        for record in self.allRecords():
+        for record in (yield self.allRecords()):
             if record.principalURL == principalURL:
-                return record
+                returnValue(record)
 
 
+    @inlineCallbacks
     def recordForInviteUID(self, inviteUID):
-        rows = self._txn.execSQL(
+        rows = yield self._txn.execSQL(
             """
             select
                 INVITE.INVITE_UID,
@@ -199,8 +217,9 @@
             """ % self._combinedTable,
             [inviteUID]
         )
-        return self._makeInvite(rows[0]) if rows else None
+        returnValue(self._makeInvite(rows[0]) if rows else None)
 
+
     def _makeInvite(self, row):
         [inviteuid, common_name, userid, ownerUID,
             bindMode, bindStatus, summary] = row
@@ -222,6 +241,8 @@
             access, state, summary
         )
 
+
+    @inlineCallbacks
     def addOrUpdateRecord(self, record):
         bindMode = {'read-only': _BIND_MODE_READ,
                     'read-write': _BIND_MODE_WRITE}[record.access]
@@ -235,15 +256,15 @@
         # it will always contain the UID.  The form is '/principals/__uids__/x'
         # (and may contain a trailing slash).
         principalUID = record.principalURL.split("/")[3]
-        shareeHome = self._getHomeWithUID(principalUID)
-        rows = self._txn.execSQL(
+        shareeHome = yield self._getHomeWithUID(principalUID)
+        rows = yield self._txn.execSQL(
             "select RESOURCE_ID, HOME_RESOURCE_ID from INVITE where RECIPIENT_ADDRESS = %s",
             [record.userid]
         )
         if rows:
             [[resourceID, homeResourceID]] = rows
             # Invite(inviteuid, userid, principalURL, common_name, access, state, summary)
-            self._txn.execSQL(
+            yield self._txn.execSQL(
                 """
                 update %(BIND:name)s
                 set %(BIND:column_BIND_MODE)s = %%s,
@@ -254,7 +275,7 @@
                 """ % self._combinedTable,
                 [bindMode, bindStatus, record.summary, resourceID, homeResourceID]
             )
-            self._txn.execSQL("""
+            yield self._txn.execSQL("""
                 update INVITE
                 set NAME = %s, INVITE_UID = %s
                 where RECIPIENT_ADDRESS = %s
@@ -262,7 +283,7 @@
                 [record.name, record.inviteuid, record.userid]
             )
         else:
-            self._txn.execSQL(
+            yield self._txn.execSQL(
                 """
                 insert into INVITE
                 (
@@ -277,7 +298,7 @@
                     shareeHome._resourceID, self._collection._resourceID,
                     record.userid
                 ])
-            self._txn.execSQL(
+            yield self._txn.execSQL(
                 """
                 insert into %(BIND:name)s
                 (
@@ -302,11 +323,13 @@
                     False,
                     False,
                     record.summary
-                ])
+                ]
+            )
 
 
+    @inlineCallbacks
     def removeRecordForUserID(self, userid):
-        self._txn.execSQL(
+        yield self._txn.execSQL(
             """
             delete from %(BIND:name)s using INVITE
             where INVITE.RECIPIENT_ADDRESS = %%s
@@ -315,14 +338,15 @@
             """ % self._combinedTable,
             [userid]
         )
-        self._txn.execSQL(
+        yield self._txn.execSQL(
             "delete from INVITE where RECIPIENT_ADDRESS = %s",
             [userid]
         )
 
 
+    @inlineCallbacks
     def removeRecordForInviteUID(self, inviteUID):
-        self._txn.execSQL(
+        yield self._txn.execSQL(
             """
             delete from %(BIND:name)s using INVITE
             where INVITE.INVITE_UID = %s
@@ -331,11 +355,13 @@
             """ % self._combinedTable,
             [inviteUID]
         )
-        self._txn.execSQL(
+        yield self._txn.execSQL(
             "delete from INVITE where INVITE_UID = %s",
             [inviteUID]
         )
 
+
+
 class SQLLegacyCalendarInvites(SQLLegacyInvites):
     """
     Emulator for the implicit interface specified by
@@ -347,9 +373,12 @@
         self._bindTable = CALENDAR_BIND_TABLE
         super(SQLLegacyCalendarInvites, self).__init__(calendar)
 
+
     def _getHomeWithUID(self, uid):
         return self._txn.calendarHomeWithUID(uid, create=True)
-        
+
+
+
 class SQLLegacyAddressBookInvites(SQLLegacyInvites):
     """
     Emulator for the implicit interface specified by
@@ -361,9 +390,12 @@
         self._bindTable = ADDRESSBOOK_BIND_TABLE
         super(SQLLegacyAddressBookInvites, self).__init__(addressbook)
 
+
     def _getHomeWithUID(self, uid):
         return self._txn.addressbookHomeWithUID(uid, create=True)
 
+
+
 class SQLLegacyShares(object):
 
     _homeTable = None
@@ -390,12 +422,14 @@
         pass
 
 
+    @inlineCallbacks
     def allRecords(self):
         # This should have been a smart join that got all these columns at
         # once, but let's not bother to fix it, since the actual query we
         # _want_ to do (just look for binds in a particular homes) is
         # much simpler anyway; we should just do that.
-        shareRows = self._txn.execSQL(
+        all = []
+        shareRows = yield self._txn.execSQL(
             """
             select %(column_RESOURCE_ID)s, %(column_RESOURCE_NAME)s, %(column_BIND_MODE)s, %(column_MESSAGE)s
             from %(name)s
@@ -407,7 +441,7 @@
         )
         for resourceID, resourceName, bindMode, summary in shareRows:
             if bindMode != _BIND_MODE_DIRECT:
-                [[shareuid]] = self._txn.execSQL(
+                [[shareuid]] = yield self._txn.execSQL(
                     """
                     select INVITE_UID
                     from INVITE
@@ -416,7 +450,7 @@
                     [resourceID, self._home._resourceID]
                 )
                 sharetype = 'I'
-                [[ownerHomeID, ownerResourceName]] = self._txn.execSQL(
+                [[ownerHomeID, ownerResourceName]] = yield self._txn.execSQL(
                     """
                     select %(column_HOME_RESOURCE_ID)s, %(column_RESOURCE_NAME)s
                     from %(name)s
@@ -425,7 +459,7 @@
                     """ % self._bindTable,
                     [resourceID, _BIND_MODE_OWN]
                 )
-                [[ownerUID]] = self._txn.execSQL(
+                [[ownerUID]] = yield self._txn.execSQL(
                     """
                     select %(column_OWNER_UID)s from %(name)s
                     where %(column_RESOURCE_ID)s = %%s
@@ -439,10 +473,10 @@
                 record = SharedCollectionRecord(
                     shareuid, sharetype, hosturl, localname, summary
                 )
-                yield record
+                all.append(record)
             else:
                 sharetype = 'D'
-                [[ownerHomeID, ownerResourceName]] = self._txn.execSQL(
+                [[ownerHomeID, ownerResourceName]] = yield self._txn.execSQL(
                     """
                     select %(column_HOME_RESOURCE_ID)s, %(column_RESOURCE_NAME)s
                     from %(name)s
@@ -451,7 +485,7 @@
                     """ % self._bindTable,
                     [resourceID, _BIND_MODE_OWN]
                 )
-                [[ownerUID]] = self._txn.execSQL(
+                [[ownerUID]] = yield self._txn.execSQL(
                     """
                     select %(column_OWNER_UID)s from %(name)s
                     where %(column_RESOURCE_ID)s = %%s
@@ -466,42 +500,40 @@
                 record = SharedCollectionRecord(
                     synthesisedUID, sharetype, hosturl, localname, summary
                 )
-                yield record
-                
+                all.append(record)
+        returnValue(all)
 
+
+    @inlineCallbacks
     def _search(self, **kw):
         [[key, value]] = kw.items()
-        for record in self.allRecords():
+        for record in (yield self.allRecords()):
             if getattr(record, key) == value:
-                return record
+                returnValue((record))
 
-    def recordForLocalName(self, localname):
-        return self._search(localname=localname)
 
     def recordForShareUID(self, shareUID):
         return self._search(shareuid=shareUID)
 
 
+    @inlineCallbacks
     def addOrUpdateRecord(self, record):
-#        print '*** SHARING***: Adding or updating this record:'
-#        import pprint
-#        pprint.pprint(record.__dict__)
         # record.hosturl -> /.../__uids__/<uid>/<name>
         splithost = record.hosturl.split('/')
         ownerUID = splithost[3]
         ownerCollectionName = splithost[4]
-        ownerHome = self._getHomeWithUID(ownerUID)
-        ownerCollection = ownerHome.childWithName(ownerCollectionName)
+        ownerHome = yield self._getHomeWithUID(ownerUID)
+        ownerCollection = yield ownerHome.childWithName(ownerCollectionName)
         collectionResourceID = ownerCollection._resourceID
 
         if record.sharetype == 'I':
-                
+
             # There needs to be a bind already, one that corresponds to the
             # invitation.  The invitation's UID is the same as the share UID.  I
             # just need to update its 'localname', i.e.
             # XXX_BIND.XXX_RESOURCE_NAME.
-    
-            self._txn.execSQL(
+
+            yield self._txn.execSQL(
                 """
                 update %(name)s
                 set %(column_RESOURCE_NAME)s = %%s
@@ -511,10 +543,9 @@
                 [record.localname, self._home._resourceID, collectionResourceID]
             )
         elif record.sharetype == 'D':
-            
             # There is no bind entry already so add one.
-    
-            self._txn.execSQL(
+
+            yield self._txn.execSQL(
                 """
                 insert into %(name)s
                 (
@@ -539,17 +570,18 @@
                     True,
                     record.summary,
                 ])
-            
-        shareeCollection = self._home.sharedChildWithName(record.localname)
+
+        shareeCollection = yield self._home.sharedChildWithName(record.localname)
         shareeCollection._initSyncToken()
 
+
+    @inlineCallbacks
     def removeRecordForLocalName(self, localname):
-        
-        record = self.recordForLocalName(localname)
-        shareeCollection = self._home.sharedChildWithName(record.localname)
-        shareeCollection._deletedSyncToken()
-        
-        self._txn.execSQL(
+        record = yield self.recordForLocalName(localname)
+        shareeCollection = yield self._home.sharedChildWithName(record.localname)
+        yield shareeCollection._deletedSyncToken()
+
+        returnValue((yield self._txn.execSQL(
             """
             update %(name)s
             set %(column_RESOURCE_NAME)s = NULL
@@ -557,17 +589,18 @@
              and %(column_HOME_RESOURCE_ID)s = %%s
             """ % self._bindTable,
             [localname, self._home._resourceID]
-        )
+        )))
 
 
+    @inlineCallbacks
     def removeRecordForShareUID(self, shareUID):
 
-        record = self.recordForShareUID(shareUID)
-        shareeCollection = self._home.sharedChildWithName(record.localname)
-        shareeCollection._deletedSyncToken()
-        
+        record = yield self.recordForShareUID(shareUID)
+        shareeCollection = yield self._home.sharedChildWithName(record.localname)
+        yield shareeCollection._deletedSyncToken()
+
         if not shareUID.startswith("Direct"):
-            self._txn.execSQL(
+            yield self._txn.execSQL(
                 """
                 update %(name)s
                 set %(column_RESOURCE_NAME)s = NULL
@@ -576,23 +609,24 @@
                  and %(name)s.%(column_HOME_RESOURCE_ID)s = INVITE.HOME_RESOURCE_ID
                  and %(name)s.%(column_RESOURCE_ID)s = INVITE.RESOURCE_ID
                 """ % self._bindTable,
-                [shareUID,]
+                [shareUID, ]
             )
         else:
             # Extract pieces from synthesised UID
             homeID, resourceID = shareUID[len("Direct-"):].split("-")
-            
+
             # Now remove the binding for the direct share
-            self._txn.execSQL(
+            yield self._txn.execSQL(
                 """
                 delete from %(name)s
                 where %(column_HOME_RESOURCE_ID)s = %%s
                  and %(column_RESOURCE_ID)s = %%s
                 """ % self._bindTable,
-                [homeID, resourceID,]
+                [homeID, resourceID, ]
             )
 
 
+
 class SQLLegacyCalendarShares(SQLLegacyShares):
     """
     Emulator for the implicit interface specified by
@@ -603,12 +637,15 @@
         self._homeTable = CALENDAR_HOME_TABLE
         self._bindTable = CALENDAR_BIND_TABLE
         self._urlTopSegment = "calendars"
-    
+
         super(SQLLegacyCalendarShares, self).__init__(home)
 
+
     def _getHomeWithUID(self, uid):
         return self._txn.calendarHomeWithUID(uid, create=True)
-        
+
+
+
 class SQLLegacyAddressBookShares(SQLLegacyShares):
     """
     Emulator for the implicit interface specified by
@@ -624,8 +661,8 @@
 
     def _getHomeWithUID(self, uid):
         return self._txn.addressbookHomeWithUID(uid, create=True)
-        
 
+
 class MemcachedUIDReserver(CachePoolUserMixIn, LoggingMixIn):
     def __init__(self, index, cachePool=None):
         self.index = index
@@ -816,7 +853,38 @@
         return "%%%s%%" % (arg,)
 
 
-class PostgresLegacyIndexEmulator(LoggingMixIn):
+class LegacyIndexHelper(LoggingMixIn, object):
+
+    @inlineCallbacks
+    def isAllowedUID(self, uid, *names):
+        """
+        Checks to see whether to allow an operation which would add the
+        specified UID to the index.  Specifically, the operation may not
+        violate the constraint that UIDs must be unique.
+        @param uid: the UID to check
+        @param names: the names of resources being replaced or deleted by the
+            operation; UIDs associated with these resources are not checked.
+        @return: True if the UID is not in the index and is not reserved,
+            False otherwise.
+        """
+        rname = yield self.resourceNameForUID(uid)
+        returnValue(rname is None or rname in names)
+
+
+    def reserveUID(self, uid):
+        return self.reserver.reserveUID(uid)
+
+
+    def unreserveUID(self, uid):
+        return self.reserver.unreserveUID(uid)
+
+
+    def isReservedUID(self, uid):
+        return self.reserver.isReservedUID(uid)
+
+
+
+class PostgresLegacyIndexEmulator(LegacyIndexHelper):
     """
     Emulator for L{twistedcaldv.index.Index} and
     L{twistedcaldv.index.IndexSchedule}.
@@ -838,27 +906,7 @@
         return self.calendar._txn
 
 
-    def reserveUID(self, uid):
-        if self.calendar._name == "inbox":
-            return succeed(None)
-        else:
-            return self.reserver.reserveUID(uid)
-
-
-    def unreserveUID(self, uid):
-        if self.calendar._name == "inbox":
-            return succeed(None)
-        else:
-            return self.reserver.unreserveUID(uid)
-
-
-    def isReservedUID(self, uid):
-        if self.calendar._name == "inbox":
-            return succeed(False)
-        else:
-            return self.reserver.isReservedUID(uid)
-
-
+    @inlineCallbacks
     def isAllowedUID(self, uid, *names):
         """
         Checks to see whether to allow an operation which would add the
@@ -870,112 +918,140 @@
         @return: True if the UID is not in the index and is not reserved,
             False otherwise.
         """
-        if self.calendar._name == "inbox":
-            return True
-        else:
-            rname = self.resourceNameForUID(uid)
-            return (rname is None or rname in names)
+        rname = yield self.resourceNameForUID(uid)
+        returnValue(rname is None or rname in names)
 
+
+    @inlineCallbacks
     def resourceUIDForName(self, name):
-        obj = self.calendar.calendarObjectWithName(name)
+        obj = yield self.calendar.calendarObjectWithName(name)
         if obj is None:
-            return None
-        return obj.uid()
+            returnValue(None)
+        returnValue(obj.uid())
 
 
+    @inlineCallbacks
     def resourceNameForUID(self, uid):
-        obj = self.calendar.calendarObjectWithUID(uid)
+        obj = yield self.calendar.calendarObjectWithUID(uid)
         if obj is None:
-            return None
-        return obj.name()
+            returnValue(None)
+        returnValue(obj.name())
 
 
+    @inlineCallbacks
     def notExpandedBeyond(self, minDate):
         """
         Gives all resources which have not been expanded beyond a given date
         in the database.  (Unused; see above L{postgresqlgenerator}.
         """
-        return [row[0] for row in self._txn.execSQL(
+        returnValue([row[0] for row in (yield self._txn.execSQL(
             "select RESOURCE_NAME from CALENDAR_OBJECT "
             "where RECURRANCE_MAX < %s and CALENDAR_RESOURCE_ID = %s",
             [normalizeForIndex(minDate), self.calendar._resourceID]
-        )]
+        ))])
 
 
+    @inlineCallbacks
     def reExpandResource(self, name, expand_until):
         """
         Given a resource name, remove it from the database and re-add it
         with a longer expansion.
         """
-        obj = self.calendar.calendarObjectWithName(name)
-        obj.updateDatabase(obj.component(), expand_until=expand_until, reCreate=True)
+        obj = yield self.calendar.calendarObjectWithName(name)
+        yield obj.updateDatabase(
+            (yield obj.component()), expand_until=expand_until, reCreate=True
+        )
 
+
+    @inlineCallbacks
     def testAndUpdateIndex(self, minDate):
         # Find out if the index is expanded far enough
-        names = self.notExpandedBeyond(minDate)
+        names = yield self.notExpandedBeyond(minDate)
 
         # Actually expand recurrence max
         for name in names:
             self.log_info("Search falls outside range of index for %s %s" % (name, minDate))
-            self.reExpandResource(name, minDate)
+            yield self.reExpandResource(name, minDate)
 
+
+    @inlineCallbacks
     def indexedSearch(self, filter, useruid='', fbtype=False):
         """
         Finds resources matching the given qualifiers.
         @param filter: the L{Filter} for the calendar-query to execute.
-        @return: an iterable of tuples for each resource matching the
-            given C{qualifiers}. The tuples are C{(name, uid, type)}, where
-            C{name} is the resource name, C{uid} is the resource UID, and
-            C{type} is the resource iCalendar component type.x
+
+        @return: a L{Deferred} which fires with an iterable of tuples for each
+            resource matching the given C{qualifiers}. The tuples are C{(name,
+            uid, type)}, where C{name} is the resource name, C{uid} is the
+            resource UID, and C{type} is the resource iCalendar component
+            type.
         """
-
         # Make sure we have a proper Filter element and get the partial SQL
         # statement to use.
         if isinstance(filter, calendarqueryfilter.Filter):
-            qualifiers = calendarquery.sqlcalendarquery(filter, self.calendar._resourceID, useruid, generator=postgresqlgenerator)
+            qualifiers = calendarquery.sqlcalendarquery(
+                filter, self.calendar._resourceID, useruid,
+                generator=postgresqlgenerator
+            )
             if qualifiers is not None:
                 # Determine how far we need to extend the current expansion of
-                # events. If we have an open-ended time-range we will expand one
-                # year past the start. That should catch bounded recurrences - unbounded
-                # will have been indexed with an "infinite" value always included.
+                # events. If we have an open-ended time-range we will expand
+                # one year past the start. That should catch bounded
+                # recurrences - unbounded will have been indexed with an
+                # "infinite" value always included.
                 maxDate, isStartDate = filter.getmaxtimerange()
                 if maxDate:
                     maxDate = maxDate.date()
                     if isStartDate:
                         maxDate += datetime.timedelta(days=365)
-                    self.testAndUpdateIndex(maxDate)
+                    yield self.testAndUpdateIndex(maxDate)
             else:
                 # We cannot handler this filter in an indexed search
                 raise IndexedSearchException()
-
         else:
             qualifiers = None
 
         # Perform the search
         if qualifiers is None:
-            rowiter = self._txn.execSQL(
-                "select RESOURCE_NAME, ICALENDAR_UID, ICALENDAR_TYPE from CALENDAR_OBJECT where CALENDAR_RESOURCE_ID = %s",
-                [self.calendar._resourceID, ],
+            rowiter = yield self._txn.execSQL(
+                """
+                select RESOURCE_NAME, ICALENDAR_UID, ICALENDAR_TYPE
+                from CALENDAR_OBJECT where CALENDAR_RESOURCE_ID = %s
+                """,
+                [self.calendar._resourceID],
             )
         else:
             if fbtype:
                 # For a free-busy time-range query we return all instances
-                rowiter = self._txn.execSQL(
-                    """select DISTINCT
-                        CALENDAR_OBJECT.RESOURCE_NAME, CALENDAR_OBJECT.ICALENDAR_UID, CALENDAR_OBJECT.ICALENDAR_TYPE, CALENDAR_OBJECT.ORGANIZER,
-                        TIME_RANGE.FLOATING, TIME_RANGE.START_DATE, TIME_RANGE.END_DATE, TIME_RANGE.FBTYPE, TIME_RANGE.TRANSPARENT, TRANSPARENCY.TRANSPARENT""" +
+                rowiter = yield self._txn.execSQL(
+                    """
+                    select DISTINCT
+                        CALENDAR_OBJECT.RESOURCE_NAME,
+                        CALENDAR_OBJECT.ICALENDAR_UID,
+                        CALENDAR_OBJECT.ICALENDAR_TYPE,
+                        CALENDAR_OBJECT.ORGANIZER,
+                        TIME_RANGE.FLOATING, TIME_RANGE.START_DATE,
+                        TIME_RANGE.END_DATE, TIME_RANGE.FBTYPE,
+                        TIME_RANGE.TRANSPARENT, TRANSPARENCY.TRANSPARENT
+                    """ +
                     qualifiers[0],
                     qualifiers[1]
                 )
             else:
-                rowiter = self._txn.execSQL(
-                    "select DISTINCT CALENDAR_OBJECT.RESOURCE_NAME, CALENDAR_OBJECT.ICALENDAR_UID, CALENDAR_OBJECT.ICALENDAR_TYPE" +
+                rowiter = yield self._txn.execSQL(
+                    """
+                    select
+                        DISTINCT CALENDAR_OBJECT.RESOURCE_NAME,
+                        CALENDAR_OBJECT.ICALENDAR_UID,
+                        CALENDAR_OBJECT.ICALENDAR_TYPE
+                    """ +
                     qualifiers[0],
                     qualifiers[1]
                 )
 
         # Check result for missing resources
 
+        results = []
         for row in rowiter:
             if fbtype:
                 row = list(row)
@@ -983,7 +1059,8 @@
                 row[7] = indexfbtype_to_icalfbtype[row[7]]
                 row[8] = 'T' if row[9] else 'F'
                 del row[9]
-            yield row
+            results.append(row)
+        returnValue(results)
 
 
     def bruteForceSearch(self):
@@ -994,23 +1071,44 @@
         )
 
 
+    @inlineCallbacks
     def resourcesExist(self, names):
-        return list(set(names).intersection(
-            set(self.calendar.listCalendarObjects())))
+        returnValue(list(set(names).intersection(
+            set((yield self.calendar.listCalendarObjects())))))
 
 
+    @inlineCallbacks
     def resourceExists(self, name):
-        return bool(
-            self._txn.execSQL(
+        returnValue((bool(
+            (yield self._txn.execSQL(
                 "select RESOURCE_NAME from CALENDAR_OBJECT where "
                 "RESOURCE_NAME = %s and CALENDAR_RESOURCE_ID = %s",
                 [name, self.calendar._resourceID]
-            )
-        )
+            ))
+        )))
 
 
 
+class PostgresLegacyInboxIndexEmulator(PostgresLegacyIndexEmulator):
+    """
+    UIDs need not be unique in the 'inbox' calendar, so override those
+    behaviors intended to ensure that.
+    """
 
+    def isAllowedUID(self):
+        return succeed(True)
+
+    def reserveUID(self, uid):
+        return succeed(None)
+
+    def unreserveUID(self, uid):
+        return succeed(None)
+
+    def isReservedUID(self, uid):
+        return succeed(False)
+
+
+
 # CARDDAV
 
 class postgresqladbkgenerator(sqlgenerator):
@@ -1077,7 +1175,7 @@
         return "%%%s%%" % (arg,)
 
 
-class PostgresLegacyABIndexEmulator(object):
+class PostgresLegacyABIndexEmulator(LegacyIndexHelper):
     """
     Emulator for L{twistedcaldv.index.Index} and
     L{twistedcaldv.index.IndexSchedule}.
@@ -1100,45 +1198,20 @@
         return self.addressbook._txn
 
 
-    def reserveUID(self, uid):
-        return self.reserver.reserveUID(uid)
-
-
-    def unreserveUID(self, uid):
-        return self.reserver.unreserveUID(uid)
-
-
-    def isReservedUID(self, uid):
-        return self.reserver.isReservedUID(uid)
-
-
-    def isAllowedUID(self, uid, *names):
-        """
-        Checks to see whether to allow an operation which would add the
-        specified UID to the index.  Specifically, the operation may not
-        violate the constraint that UIDs must be unique.
-        @param uid: the UID to check
-        @param names: the names of resources being replaced or deleted by the
-            operation; UIDs associated with these resources are not checked.
-        @return: True if the UID is not in the index and is not reserved,
-            False otherwise.
-        """
-        rname = self.resourceNameForUID(uid)
-        return (rname is None or rname in names)
-
-
+    @inlineCallbacks
     def resourceUIDForName(self, name):
-        obj = self.addressbook.addressbookObjectWithName(name)
+        obj = yield self.addressbook.addressbookObjectWithName(name)
         if obj is None:
-            return None
-        return obj.uid()
+            returnValue(None)
+        returnValue(obj.uid())
 
 
+    @inlineCallbacks
     def resourceNameForUID(self, uid):
-        obj = self.addressbook.addressbookObjectWithUID(uid)
+        obj = yield self.addressbook.addressbookObjectWithUID(uid)
         if obj is None:
-            return None
-        return obj.name()
+            returnValue(None)
+        returnValue(obj.name())
 
 
     def searchValid(self, filter):
@@ -1149,6 +1222,8 @@
 
         return qualifiers is not None
 
+
+    @inlineCallbacks
     def search(self, filter):
         """
         Finds resources matching the given qualifiers.
@@ -1165,20 +1240,20 @@
         else:
             qualifiers = None
         if qualifiers is not None:
-            rowiter = self._txn.execSQL(
+            rowiter = yield self._txn.execSQL(
                 "select DISTINCT ADDRESSBOOK_OBJECT.RESOURCE_NAME, ADDRESSBOOK_OBJECT.VCARD_UID" +
                 qualifiers[0],
                 qualifiers[1]
             )
         else:
-            rowiter = self._txn.execSQL(
+            rowiter = yield self._txn.execSQL(
                 "select RESOURCE_NAME, VCARD_UID from ADDRESSBOOK_OBJECT where ADDRESSBOOK_RESOURCE_ID = %s",
                 [self.addressbook._resourceID, ],
             )
 
-        for row in rowiter:
-            yield row
+        returnValue(list(rowiter))
 
+
     def indexedSearch(self, filter, useruid='', fbtype=False):
         """
         Always raise L{IndexedSearchException}, since these indexes are not
@@ -1195,16 +1270,18 @@
         )
 
 
+    @inlineCallbacks
     def resourcesExist(self, names):
-        return list(set(names).intersection(
-            set(self.addressbook.listAddressbookObjects())))
+        returnValue(list(set(names).intersection(
+            set((yield self.addressbook.listAddressbookObjects())))))
 
 
+    @inlineCallbacks
     def resourceExists(self, name):
-        return bool(
-            self._txn.execSQL(
+        returnValue(bool(
+            (yield self._txn.execSQL(
                 "select RESOURCE_NAME from ADDRESSBOOK_OBJECT where "
                 "RESOURCE_NAME = %s and ADDRESSBOOK_RESOURCE_ID = %s",
                 [name, self.addressbook._resourceID]
-            )
-        )
+            ))
+        ))

Modified: CalendarServer/trunk/txdav/common/datastore/test/test_util.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/test/test_util.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/txdav/common/datastore/test/test_util.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -27,9 +27,9 @@
 from txdav.common.datastore.util import UpgradeToDatabaseService
 from txdav.common.datastore.file import CommonDataStore
 from txdav.common.datastore.test.util import theStoreBuilder, \
-    populateCalendarsFrom
-from txdav.caldav.datastore.test.common import StubNotifierFactory, CommonTests
-from twisted.internet.defer import inlineCallbacks, Deferred
+    populateCalendarsFrom, StubNotifierFactory
+from txdav.caldav.datastore.test.common import CommonTests
+from twisted.internet.defer import inlineCallbacks, Deferred, returnValue
 
 
 class HomeMigrationTests(TestCase):
@@ -63,7 +63,7 @@
         )
         self.upgrader.setServiceParent(self.topService)
         requirements = CommonTests.requirements
-        populateCalendarsFrom(requirements, fileStore)
+        yield populateCalendarsFrom(requirements, fileStore)
         self.filesPath.child("calendars").child(
             "__uids__").child("ho").child("me").child("home1").child(
             ".some-extra-data").setContent("some extra data")
@@ -82,7 +82,9 @@
         self.addCleanup(txn.commit)
         for uid in CommonTests.requirements:
             if CommonTests.requirements[uid] is not None:
-                self.assertNotIdentical(None, txn.calendarHomeWithUID(uid))
+                self.assertNotIdentical(
+                    None, (yield txn.calendarHomeWithUID(uid))
+                )
         # Un-migrated data should be preserved.
         self.assertEquals(self.filesPath.child("calendars-migrated").child(
             "__uids__").child("ho").child("me").child("home1").child(
@@ -98,17 +100,17 @@
         homes.
         """
         startTxn = self.sqlStore.newTransaction("populate empty sample")
-        startTxn.calendarHomeWithUID("home1", create=True)
-        startTxn.commit()
+        yield startTxn.calendarHomeWithUID("home1", create=True)
+        yield startTxn.commit()
         self.topService.startService()
         yield self.subStarted
         vrfyTxn = self.sqlStore.newTransaction("verify sample still empty")
         self.addCleanup(vrfyTxn.commit)
-        home = vrfyTxn.calendarHomeWithUID("home1")
+        home = yield vrfyTxn.calendarHomeWithUID("home1")
         # The default calendar is still there.
-        self.assertNotIdentical(None, home.calendarWithName("calendar"))
+        self.assertNotIdentical(None, (yield home.calendarWithName("calendar")))
         # The migrated calendar isn't.
-        self.assertIdentical(None, home.calendarWithName("calendar_1"))
+        self.assertIdentical(None, (yield home.calendarWithName("calendar_1")))
 
 
     @inlineCallbacks
@@ -117,32 +119,38 @@
         L{UpgradeToDatabaseService.startService} upgrades calendar attachments
         as well.
         """
+
         txn = self.fileStore.newTransaction()
         committed = []
         def maybeCommit():
             if not committed:
                 committed.append(True)
-                txn.commit()
+                return txn.commit()
         self.addCleanup(maybeCommit)
+
+        @inlineCallbacks
         def getSampleObj():
-            return txn.calendarHomeWithUID("home1").calendarWithName(
-                "calendar_1").calendarObjectWithName("1.ics")
-        inObject = getSampleObj()
+            home = (yield txn.calendarHomeWithUID("home1"))
+            calendar = (yield home.calendarWithName("calendar_1"))
+            object = (yield calendar.calendarObjectWithName("1.ics"))
+            returnValue(object)
+
+        inObject = yield getSampleObj()
         someAttachmentName = "some-attachment"
         someAttachmentType = MimeType.fromString("application/x-custom-type")
-        transport = inObject.createAttachmentWithName(
+        transport = yield inObject.createAttachmentWithName(
             someAttachmentName, someAttachmentType
         )
         someAttachmentData = "Here is some data for your attachment, enjoy."
         transport.write(someAttachmentData)
         transport.loseConnection()
-        maybeCommit()
+        yield maybeCommit()
         self.topService.startService()
         yield self.subStarted
         committed = []
         txn = self.sqlStore.newTransaction()
-        outObject = getSampleObj()
-        outAttachment = outObject.attachmentWithName(someAttachmentName)
+        outObject = yield getSampleObj()
+        outAttachment = yield outObject.attachmentWithName(someAttachmentName)
         allDone = Deferred()
         class SimpleProto(Protocol):
             data = ''

Modified: CalendarServer/trunk/txdav/common/datastore/test/util.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/test/util.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/txdav/common/datastore/test/util.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -20,12 +20,15 @@
 """
 
 import gc
+from zope.interface.verify import verifyObject
+from zope.interface.exceptions import BrokenMethodImplementation,\
+    DoesNotImplement
 
 from twext.python.filepath import CachingFilePath
 from twext.python.vcomponent import VComponent
 
 from twisted.internet import reactor
-from twisted.internet.defer import Deferred, succeed
+from twisted.internet.defer import Deferred, succeed, inlineCallbacks
 from twisted.internet.task import deferLater
 from twisted.python import log
 
@@ -33,6 +36,7 @@
 from txdav.base.datastore.subpostgres import PostgresService,\
     DiagnosticConnectionWrapper
 from txdav.common.icommondatastore import NoSuchHomeChildError
+from twistedcaldav.notify import Notifier
 
 
 def allInstancesOf(cls):
@@ -149,6 +153,7 @@
 buildStore = theStoreBuilder.buildStore
 
 
+ at inlineCallbacks
 def populateCalendarsFrom(requirements, store):
     """
     Populate C{store} from C{requirements}.
@@ -162,23 +167,112 @@
     for homeUID in requirements:
         calendars = requirements[homeUID]
         if calendars is not None:
-            home = populateTxn.calendarHomeWithUID(homeUID, True)
+            home = yield populateTxn.calendarHomeWithUID(homeUID, True)
             # We don't want the default calendar or inbox to appear unless it's
             # explicitly listed.
             try:
-                home.removeCalendarWithName("calendar")
-                home.removeCalendarWithName("inbox")
+                yield home.removeCalendarWithName("calendar")
+                yield home.removeCalendarWithName("inbox")
             except NoSuchHomeChildError:
                 pass
             for calendarName in calendars:
                 calendarObjNames = calendars[calendarName]
                 if calendarObjNames is not None:
-                    home.createCalendarWithName(calendarName)
-                    calendar = home.calendarWithName(calendarName)
+                    # XXX should not be yielding!  this SQL will be executed
+                    # first!
+                    yield home.createCalendarWithName(calendarName)
+                    calendar = yield home.calendarWithName(calendarName)
                     for objectName in calendarObjNames:
                         objData = calendarObjNames[objectName]
                         calendar.createCalendarObjectWithName(
                             objectName, VComponent.fromString(objData)
                         )
-    populateTxn.commit()
+    yield populateTxn.commit()
 
+
+def assertProvides(testCase, interface, provider):
+    """
+    Verify that C{provider} properly provides C{interface}
+
+    @type interface: L{zope.interface.Interface}
+    @type provider: C{provider}
+    """
+    try:
+        verifyObject(interface, provider)
+    except BrokenMethodImplementation, e:
+        testCase.fail(e)
+    except DoesNotImplement, e:
+        testCase.fail("%r does not provide %s.%s" %
+                      (provider, interface.__module__, interface.getName()))
+
+
+
+class CommonCommonTests(object):
+    """
+    Common utility functionality for file/store combination tests.
+    """
+
+    lastTransaction = None
+    savedStore = None
+    assertProvides = assertProvides
+
+    def transactionUnderTest(self):
+        """
+        Create a transaction from C{storeUnderTest} and save it as
+        C[lastTransaction}.  Also makes sure to use the same store, saving the
+        value from C{storeUnderTest}.
+        """
+        if self.lastTransaction is not None:
+            return self.lastTransaction
+        if self.savedStore is None:
+            self.savedStore = self.storeUnderTest()
+        self.counter += 1
+        txn = self.lastTransaction = self.savedStore.newTransaction(self.id() + " #" + str(self.counter))
+        return txn
+
+
+    def commit(self):
+        """
+        Commit the last transaction created from C{transactionUnderTest}, and
+        clear it.
+        """
+        result = self.lastTransaction.commit()
+        self.lastTransaction = None
+        return result
+
+
+    def abort(self):
+        """
+        Abort the last transaction created from C[transactionUnderTest}, and
+        clear it.
+        """
+        result = self.lastTransaction.abort()
+        self.lastTransaction = None
+        return result
+
+    def setUp(self):
+        self.counter = 0
+        self.notifierFactory = StubNotifierFactory()
+
+    def tearDown(self):
+        if self.lastTransaction is not None:
+            return self.commit()
+
+
+
+class StubNotifierFactory(object):
+    """
+    For testing push notifications without an XMPP server.
+    """
+
+    def __init__(self):
+        self.reset()
+
+    def newNotifier(self, label="default", id=None, prefix=None):
+        return Notifier(self, label=label, id=id, prefix=prefix)
+
+    def send(self, op, id):
+        self.history.append((op, id))
+
+    def reset(self):
+        self.history = []

Modified: CalendarServer/trunk/txdav/common/datastore/util.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/util.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/txdav/common/datastore/util.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -102,7 +102,8 @@
                 self.fileStore.eachCalendarHome,
                 lambda txn: txn.calendarHomeWithUID,
                 "calendars"),
-            ("addressbook", migrateAddressbookHome, self.fileStore.eachAddressbookHome,
+            ("addressbook", migrateAddressbookHome,
+                self.fileStore.eachAddressbookHome,
                 lambda txn: txn.addressbookHomeWithUID,
                 "addressbooks")
             ]:
@@ -111,17 +112,19 @@
                 self.log_warn("Migrating %s UID %r" % (homeType, uid))
                 sqlTxn = self.sqlStore.newTransaction(migrating=True)
                 homeGetter = destFunc(sqlTxn)
-                if homeGetter(uid, create=False) is not None:
+                if (yield homeGetter(uid, create=False)) is not None:
                     self.log_warn(
                         "%s home %r already existed not migrating" % (
                             homeType, uid))
-                    sqlTxn.abort()
-                    fileTxn.commit()
+                    yield sqlTxn.abort()
+                    yield fileTxn.commit()
                     continue
-                sqlHome = homeGetter(uid, create=True)
+                sqlHome = yield homeGetter(uid, create=True)
+                if sqlHome is None:
+                    raise RuntimeError("THIS SHOULD NOT BE POSSIBLE.")
                 yield migrateFunc(fileHome, sqlHome)
-                fileTxn.commit()
-                sqlTxn.commit()
+                yield fileTxn.commit()
+                yield sqlTxn.commit()
                 # FIXME: need a public remove...HomeWithUID() for de-
                 # provisioning
                 storePath = self.fileStore._path # Documents

Modified: CalendarServer/trunk/txdav/common/icommondatastore.py
===================================================================
--- CalendarServer/trunk/txdav/common/icommondatastore.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/txdav/common/icommondatastore.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -108,6 +108,20 @@
     Uh, oh.
     """
 
+# Indexing / sync tokens
+
+class ReservationError(LookupError):
+    """
+    Attempt to reserve a UID which is already reserved or to unreserve a UID
+    which is not reserved.
+    """
+
+class IndexedSearchException(ValueError):
+    pass
+
+class SyncTokenValidException(ValueError):
+    pass
+
 #
 # Interfaces
 #

Modified: CalendarServer/trunk/txdav/common/inotifications.py
===================================================================
--- CalendarServer/trunk/txdav/common/inotifications.py	2010-10-18 19:19:46 UTC (rev 6445)
+++ CalendarServer/trunk/txdav/common/inotifications.py	2010-10-19 15:39:55 UTC (rev 6446)
@@ -105,8 +105,8 @@
         @param name: a string.
         @type name: C{str}
 
-        @raise NoSuchObjectResourceError: if no such NoSuchObjectResourceError object
-            exists.
+        @raise NoSuchObjectResourceError: if no such NoSuchObjectResourceError
+            object exists.
         """
 
     def removeNotificationObjectWithUID(uid):
@@ -126,17 +126,6 @@
         @return: a string containing a sync token.
         """
 
-    def notificationObjectsSinceToken(token):
-        """
-        Retrieve all notification objects in this notification collection that have
-        changed since the given C{token} was last valid.
-
-        @param token: a sync token.
-        @return: a 3-tuple containing an iterable of
-            L{INotificationObject}s that have changed, an iterable of uids
-            that have been removed, and the current sync token.
-        """
-
     def properties():
         """
         Retrieve the property store for this notification.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20101019/48d5981f/attachment-0001.html>


More information about the calendarserver-changes mailing list