[CalendarServer-changes] [6425] CalendarServer/branches/users/glyph/more-deferreds-7

source_changes at macosforge.org source_changes at macosforge.org
Thu Oct 14 02:48:55 PDT 2010


Revision: 6425
          http://trac.macosforge.org/projects/calendarserver/changeset/6425
Author:   glyph at apple.com
Date:     2010-10-14 02:48:50 -0700 (Thu, 14 Oct 2010)
Log Message:
-----------
trial txdav passing with fully async i/o (still some work to do in twistedcaldav)

Modified Paths:
--------------
    CalendarServer/branches/users/glyph/more-deferreds-7/twistedcaldav/fileops.py
    CalendarServer/branches/users/glyph/more-deferreds-7/twistedcaldav/resource.py
    CalendarServer/branches/users/glyph/more-deferreds-7/twistedcaldav/storebridge.py
    CalendarServer/branches/users/glyph/more-deferreds-7/txdav/__init__.py
    CalendarServer/branches/users/glyph/more-deferreds-7/txdav/base/datastore/sql.py
    CalendarServer/branches/users/glyph/more-deferreds-7/txdav/base/datastore/subpostgres.py
    CalendarServer/branches/users/glyph/more-deferreds-7/txdav/base/propertystore/sql.py
    CalendarServer/branches/users/glyph/more-deferreds-7/txdav/base/propertystore/test/base.py
    CalendarServer/branches/users/glyph/more-deferreds-7/txdav/base/propertystore/test/test_sql.py
    CalendarServer/branches/users/glyph/more-deferreds-7/txdav/caldav/datastore/file.py
    CalendarServer/branches/users/glyph/more-deferreds-7/txdav/caldav/datastore/index_file.py
    CalendarServer/branches/users/glyph/more-deferreds-7/txdav/caldav/datastore/sql.py
    CalendarServer/branches/users/glyph/more-deferreds-7/txdav/caldav/datastore/test/common.py
    CalendarServer/branches/users/glyph/more-deferreds-7/txdav/caldav/datastore/test/test_sql.py
    CalendarServer/branches/users/glyph/more-deferreds-7/txdav/caldav/datastore/test/test_util.py
    CalendarServer/branches/users/glyph/more-deferreds-7/txdav/caldav/datastore/util.py
    CalendarServer/branches/users/glyph/more-deferreds-7/txdav/carddav/datastore/sql.py
    CalendarServer/branches/users/glyph/more-deferreds-7/txdav/carddav/datastore/test/common.py
    CalendarServer/branches/users/glyph/more-deferreds-7/txdav/carddav/datastore/test/test_sql.py
    CalendarServer/branches/users/glyph/more-deferreds-7/txdav/carddav/datastore/util.py
    CalendarServer/branches/users/glyph/more-deferreds-7/txdav/common/datastore/sql.py
    CalendarServer/branches/users/glyph/more-deferreds-7/txdav/common/datastore/sql_legacy.py
    CalendarServer/branches/users/glyph/more-deferreds-7/txdav/common/datastore/test/test_util.py
    CalendarServer/branches/users/glyph/more-deferreds-7/txdav/common/datastore/test/util.py
    CalendarServer/branches/users/glyph/more-deferreds-7/txdav/common/datastore/util.py

Modified: CalendarServer/branches/users/glyph/more-deferreds-7/twistedcaldav/fileops.py
===================================================================
--- CalendarServer/branches/users/glyph/more-deferreds-7/twistedcaldav/fileops.py	2010-10-13 22:16:11 UTC (rev 6424)
+++ CalendarServer/branches/users/glyph/more-deferreds-7/twistedcaldav/fileops.py	2010-10-14 09:48:50 UTC (rev 6425)
@@ -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/branches/users/glyph/more-deferreds-7/twistedcaldav/resource.py
===================================================================
--- CalendarServer/branches/users/glyph/more-deferreds-7/twistedcaldav/resource.py	2010-10-13 22:16:11 UTC (rev 6424)
+++ CalendarServer/branches/users/glyph/more-deferreds-7/twistedcaldav/resource.py	2010-10-14 09:48:50 UTC (rev 6425)
@@ -2550,8 +2550,6 @@
 # Utilities
 ##
 
-def isCalendarHomeCollectionResource(resource):
-    return isinstance(resource, CalendarHomeResource)
 
 def isCalendarCollectionResource(resource):
     try:
@@ -2561,6 +2559,7 @@
     else:
         return resource.isCalendarCollection()
 
+
 def isPseudoCalendarCollectionResource(resource):
     try:
         resource = ICalDAVResource(resource)
@@ -2569,8 +2568,6 @@
     else:
         return resource.isPseudoCalendarCollection()
 
-def isAddressBookHomeCollectionResource(resource):
-    return isinstance(resource, AddressBookHomeResource)
 
 def isAddressBookCollectionResource(resource):
     try:

Modified: CalendarServer/branches/users/glyph/more-deferreds-7/twistedcaldav/storebridge.py
===================================================================
--- CalendarServer/branches/users/glyph/more-deferreds-7/twistedcaldav/storebridge.py	2010-10-13 22:16:11 UTC (rev 6424)
+++ CalendarServer/branches/users/glyph/more-deferreds-7/twistedcaldav/storebridge.py	2010-10-14 09:48:50 UTC (rev 6425)
@@ -423,7 +423,7 @@
         l = []
         for everyCalendar in (yield self._newStoreHome.calendars()):
             for everyObject in (yield everyCalendar.calendarObjects()):
-                l.append(everyObject.dropboxID())
+                l.append((yield everyObject.dropboxID()))
         returnValue(l)
 
 
@@ -466,8 +466,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,
@@ -478,7 +479,7 @@
                 self._newStoreCalendarObject,
                 attachment, principalCollections=self.principalCollections())
         self.propagateTransaction(result)
-        return result
+        returnValue(result)
 
 
     @inlineCallbacks
@@ -523,63 +524,66 @@
         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.
         """
-        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 = (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
-                )
-                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(),
-                ))
+            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(),
+            ))
 
-            return davxml.ACL(*tuple(newACEs + originalACEs))
-        d.addCallback(moreACLs)
-        return d
+        returnValue(davxml.ACL(*tuple(newACEs + originalACEs)))
 
 
 
@@ -591,6 +595,7 @@
         self.attachmentName = attachmentName
         self._newStoreObject = None
 
+
     def isCollection(self):
         return False
 
@@ -605,28 +610,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):
@@ -890,7 +896,7 @@
             yield self.downgradeFromShare(request)
 
         # Actually delete it.
-        self._newStoreParentHome.removeCalendarWithName(
+        yield self._newStoreParentHome.removeCalendarWithName(
             self._newStoreCalendar.name()
         )
         self.__class__ = ProtoCalendarCollectionResource

Modified: CalendarServer/branches/users/glyph/more-deferreds-7/txdav/__init__.py
===================================================================
--- CalendarServer/branches/users/glyph/more-deferreds-7/txdav/__init__.py	2010-10-13 22:16:11 UTC (rev 6424)
+++ CalendarServer/branches/users/glyph/more-deferreds-7/txdav/__init__.py	2010-10-14 09:48:50 UTC (rev 6425)
@@ -1,3 +1,4 @@
+# -*- test-case-name: txdav -*-
 ##
 # Copyright (c) 2010 Apple Inc. All rights reserved.
 #

Modified: CalendarServer/branches/users/glyph/more-deferreds-7/txdav/base/datastore/sql.py
===================================================================
--- CalendarServer/branches/users/glyph/more-deferreds-7/txdav/base/datastore/sql.py	2010-10-13 22:16:11 UTC (rev 6424)
+++ CalendarServer/branches/users/glyph/more-deferreds-7/txdav/base/datastore/sql.py	2010-10-14 09:48:50 UTC (rev 6425)
@@ -13,6 +13,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 ##
+from twisted.internet.defer import Deferred
 
 """
 Logic common to SQL implementations.
@@ -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/branches/users/glyph/more-deferreds-7/txdav/base/datastore/subpostgres.py
===================================================================
--- CalendarServer/branches/users/glyph/more-deferreds-7/txdav/base/datastore/subpostgres.py	2010-10-13 22:16:11 UTC (rev 6424)
+++ CalendarServer/branches/users/glyph/more-deferreds-7/txdav/base/datastore/subpostgres.py	2010-10-14 09:48:50 UTC (rev 6425)
@@ -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/branches/users/glyph/more-deferreds-7/txdav/base/propertystore/sql.py
===================================================================
--- CalendarServer/branches/users/glyph/more-deferreds-7/txdav/base/propertystore/sql.py	2010-10-13 22:16:11 UTC (rev 6424)
+++ CalendarServer/branches/users/glyph/more-deferreds-7/txdav/base/propertystore/sql.py	2010-10-14 09:48:50 UTC (rev 6425)
@@ -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/branches/users/glyph/more-deferreds-7/txdav/base/propertystore/test/base.py
===================================================================
--- CalendarServer/branches/users/glyph/more-deferreds-7/txdav/base/propertystore/test/base.py	2010-10-13 22:16:11 UTC (rev 6424)
+++ CalendarServer/branches/users/glyph/more-deferreds-7/txdav/base/propertystore/test/base.py	2010-10-14 09:48:50 UTC (rev 6425)
@@ -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
@@ -49,6 +50,8 @@
         except BrokenMethodImplementation, e:
             self.fail(e)
 
+
+    @inlineCallbacks
     def test_set_get_contains(self):
 
         name = propertyName("test")
@@ -56,7 +59,7 @@
 
         # 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)
 
@@ -66,6 +69,8 @@
         self.assertEquals(self.propertyStore.get(name, None), value)
         self.failUnless(name in self.propertyStore)
 
+
+    @inlineCallbacks
     def test_delete_get_contains(self):
 
         # Test with commit after change
@@ -73,10 +78,10 @@
         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)
@@ -86,13 +91,15 @@
         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):
 
         name = propertyName("test")
@@ -100,35 +107,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), 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):
 
+
+    @inlineCallbacks
+    def test_peruserShadow(self):
+
         name = propertyName("shadow")
 
         self.propertyStore1.setSpecialProperties((name,), ())
@@ -138,34 +147,35 @@
         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):
 
         name = propertyName("global")
@@ -177,32 +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):
 
         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
@@ -210,6 +220,7 @@
         self.assertEquals(set(self.propertyStore.keys()), names)
         self.assertEquals(len(self.propertyStore), len(names))
 
+
     def test_delete_none(self):
 
         def doDelete():
@@ -217,9 +228,9 @@
 
         self.assertRaises(KeyError, doDelete)
 
+
     def test_keyInPropertyName(self):
 
-
         def doGet():
             self.propertyStore["xyzzy"]
 
@@ -237,6 +248,8 @@
         self.assertRaises(TypeError, doDelete)
         self.assertRaises(TypeError, doContains)
 
+
+    @inlineCallbacks
     def test_flush(self):
 
         name = propertyName("test")
@@ -247,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)
@@ -258,23 +271,27 @@
         #
         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)
 
+
+    @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):
 
         name = propertyName("shadow")
@@ -285,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/branches/users/glyph/more-deferreds-7/txdav/base/propertystore/test/test_sql.py
===================================================================
--- CalendarServer/branches/users/glyph/more-deferreds-7/txdav/base/propertystore/test/test_sql.py	2010-10-13 22:16:11 UTC (rev 6424)
+++ CalendarServer/branches/users/glyph/more-deferreds-7/txdav/base/propertystore/test/test_sql.py	2010-10-14 09:48:50 UTC (rev 6425)
@@ -42,10 +42,10 @@
         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")
 
 
@@ -60,42 +60,44 @@
         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

Modified: CalendarServer/branches/users/glyph/more-deferreds-7/txdav/caldav/datastore/file.py
===================================================================
--- CalendarServer/branches/users/glyph/more-deferreds-7/txdav/caldav/datastore/file.py	2010-10-13 22:16:11 UTC (rev 6424)
+++ CalendarServer/branches/users/glyph/more-deferreds-7/txdav/caldav/datastore/file.py	2010-10-14 09:48:50 UTC (rev 6425)
@@ -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.
@@ -101,6 +102,7 @@
         for child in self.listCalendars():
             yield self.calendarWithName(child)
 
+
     def listCalendars(self):
         """
         Return a generator of the child resource names.
@@ -111,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
@@ -333,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
@@ -360,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(
@@ -409,6 +426,7 @@
         )
 
 
+
 contentTypeKey = PropertyName.fromElement(GETContentType)
 md5key = PropertyName.fromElement(TwistedGETContentMD5)
 
@@ -458,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):
@@ -475,6 +494,7 @@
     def store(self, contentType):
         return AttachmentStorageTransport(self, contentType)
 
+
     def retrieve(self, protocol):
         # FIXME: makeConnection
         # FIXME: actually stream
@@ -483,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())
 
 
 

Modified: CalendarServer/branches/users/glyph/more-deferreds-7/txdav/caldav/datastore/index_file.py
===================================================================
--- CalendarServer/branches/users/glyph/more-deferreds-7/txdav/caldav/datastore/index_file.py	2010-10-13 22:16:11 UTC (rev 6424)
+++ CalendarServer/branches/users/glyph/more-deferreds-7/txdav/caldav/datastore/index_file.py	2010-10-14 09:48:50 UTC (rev 6425)
@@ -42,7 +42,7 @@
 
 from vobject.icalendar import utc
 
-from twisted.internet.defer import maybeDeferred, succeed
+from twisted.internet.defer import maybeDeferred, succeed, inlineCallbacks
 
 from twext.python.log import Logger, LoggingMixIn
 

Modified: CalendarServer/branches/users/glyph/more-deferreds-7/txdav/caldav/datastore/sql.py
===================================================================
--- CalendarServer/branches/users/glyph/more-deferreds-7/txdav/caldav/datastore/sql.py	2010-10-13 22:16:11 UTC (rev 6424)
+++ CalendarServer/branches/users/glyph/more-deferreds-7/txdav/caldav/datastore/sql.py	2010-10-14 09:48:50 UTC (rev 6425)
@@ -14,7 +14,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 ##
-from twisted.internet.defer import inlineCallbacks, returnValue
 
 __all__ = [
     "CalendarHome",
@@ -26,6 +25,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
@@ -89,13 +89,14 @@
                     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")
 
 
 
@@ -218,11 +219,8 @@
 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
@@ -241,6 +239,8 @@
 
         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.
@@ -284,7 +284,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)
@@ -304,7 +304,7 @@
                     organizer,
                     normalizeForIndex(instances.limit) if instances.limit else None,
                 ]
-            )[0][0]
+            ))[0][0]
         else:
             self._txn.execSQL(
                 """
@@ -337,6 +337,7 @@
                     self._resourceID,
                 ],
             )
+        self._uid = component.resourceUID()
 
 
         # CALENDAR_OBJECT table update
@@ -346,7 +347,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)
@@ -364,7 +365,7 @@
                     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(
@@ -387,7 +388,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)
@@ -405,7 +406,7 @@
                     icalfbtype_to_indexfbtype["UNKNOWN"],
                     True,
                 ],
-            )[0][0]
+            ))[0][0]
             peruserdata = component.perUserTransparency(None)
             for useruid, transp in peruserdata:
                 self._txn.execSQL(
@@ -422,34 +423,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:
@@ -458,56 +446,66 @@
             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))
 
+
     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])
+        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)
 
+
     def attendeesCanManageAttachments(self):
         return 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(
@@ -557,8 +555,10 @@
         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",
+            """
+            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()]
         )
 
@@ -576,24 +576,25 @@
         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])
         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/branches/users/glyph/more-deferreds-7/txdav/caldav/datastore/test/common.py
===================================================================
--- CalendarServer/branches/users/glyph/more-deferreds-7/txdav/caldav/datastore/test/common.py	2010-10-13 22:16:11 UTC (rev 6424)
+++ CalendarServer/branches/users/glyph/more-deferreds-7/txdav/caldav/datastore/test/common.py	2010-10-14 09:48:50 UTC (rev 6425)
@@ -19,7 +19,8 @@
 Tests for common calendar store API functions.
 """
 
-from twisted.internet.defer import Deferred, inlineCallbacks, returnValue
+from twisted.internet.defer import Deferred, inlineCallbacks, returnValue,\
+    maybeDeferred
 from twisted.internet.protocol import Protocol
 
 from txdav.idav import IPropertyStore, IDataStore, AlreadyFinishedError
@@ -253,60 +254,65 @@
         )
 
 
+    @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())
 
 
@@ -364,7 +370,7 @@
         """
         home = yield self.homeUnderTest()
         calendar = yield home.calendarWithName("calendar_1")
-        calendar.rename("some_other_name")
+        yield calendar.rename("some_other_name")
         @inlineCallbacks
         def positiveAssertions():
             self.assertEquals(calendar.name(), "some_other_name")
@@ -402,7 +408,7 @@
         home = yield self.homeUnderTest()
         name = "new"
         self.assertIdentical((yield home.calendarWithName(name)), None)
-        home.createCalendarWithName(name)
+        yield home.createCalendarWithName(name)
         self.assertNotIdentical((yield home.calendarWithName(name)), None)
         @inlineCallbacks
         def checkProperties():
@@ -443,9 +449,9 @@
         """
         home = yield self.homeUnderTest()
         for name in home1_calendarNames:
-            self.assertRaises(
-                HomeChildNameAlreadyExistsError,
-                home.createCalendarWithName, name
+            yield self.failUnlessFailure(
+                maybeDeferred(home.createCalendarWithName, name),
+                HomeChildNameAlreadyExistsError
             )
 
 
@@ -460,7 +466,7 @@
         # FIXME: test transactions
         for name in home1_calendarNames:
             self.assertNotIdentical((yield home.calendarWithName(name)), None)
-            home.removeCalendarWithName(name)
+            yield home.removeCalendarWithName(name)
             self.assertEquals((yield home.calendarWithName(name)), None)
 
         yield self.commit()
@@ -485,8 +491,10 @@
         Attempt to remove an non-existing calendar should raise.
         """
         home = yield self.homeUnderTest()
-        self.assertRaises(NoSuchHomeChildError,
-                          home.removeCalendarWithName, "xyzzy")
+        yield self.failUnlessFailure(
+            maybeDeferred(home.removeCalendarWithName, "xyzzy"),
+            NoSuchHomeChildError
+        )
 
 
     @inlineCallbacks
@@ -570,7 +578,7 @@
             uid = (u'uid' + name.rstrip(".ics"))
             self.assertNotIdentical((yield calendar.calendarObjectWithUID(uid)),
                                     None)
-            calendar.removeCalendarObjectWithUID(uid)
+            yield calendar.removeCalendarObjectWithUID(uid)
             self.assertEquals(
                 (yield calendar.calendarObjectWithUID(uid)),
                 None
@@ -604,7 +612,7 @@
             self.assertNotIdentical(
                 (yield calendar.calendarObjectWithName(name)), None
             )
-            calendar.removeCalendarObjectWithName(name)
+            yield calendar.removeCalendarObjectWithName(name)
             self.assertIdentical(
                 (yield calendar.calendarObjectWithName(name)), None
             )
@@ -616,9 +624,9 @@
         Attempt to remove an non-existing calendar object should raise.
         """
         calendar = yield self.calendarUnderTest()
-        self.assertRaises(
-            NoSuchObjectResourceError,
-            calendar.removeCalendarObjectWithName, "xyzzy"
+        yield self.failUnlessFailure(
+            maybeDeferred(calendar.removeCalendarObjectWithName, "xyzzy"),
+            NoSuchObjectResourceError
         )
 
 
@@ -647,7 +655,7 @@
         L{ICalendarObject.component} returns a L{VComponent} describing the
         calendar data underlying that calendar object.
         """
-        component = (yield self.calendarObjectUnderTest()).component()
+        component = yield (yield self.calendarObjectUnderTest()).component()
 
         self.failUnless(
             isinstance(component, VComponent),
@@ -665,7 +673,7 @@
         L{ICalendarObject.iCalendarText} returns a C{str} describing the same
         data provided by L{ICalendarObject.component}.
         """
-        text = (yield 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)
@@ -738,7 +746,7 @@
         home = yield self.homeUnderTest()
         allCalendars = yield home.calendars()
         before = set(x.name() for x in allCalendars)
-        home.createCalendarWithName("new-name")
+        yield home.createCalendarWithName("new-name")
         allCalendars = yield home.calendars()
         after = set(x.name() for x in allCalendars)
         self.assertEquals(before | set(['new-name']), after)
@@ -752,12 +760,14 @@
         """
         calendar1 = yield self.calendarUnderTest()
         name = "4.ics"
-        self.assertIdentical((yield 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 = yield calendar1.calendarObjectWithName(name)
-        self.assertEquals(calendarObject.component(), component)
+        self.assertEquals((yield calendarObject.component()), component)
 
         yield self.commit()
 
@@ -771,6 +781,7 @@
         )
 
 
+    @inlineCallbacks
     def test_createCalendarObjectWithName_exists(self):
         """
         L{ICalendar.createCalendarObjectWithName} raises
@@ -779,10 +790,9 @@
         """
         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
         )
 
 
@@ -793,10 +803,10 @@
         L{InvalidCalendarComponentError} if presented with invalid iCalendar
         text.
         """
-        self.assertRaises(
+        yield self.failUnlessFailure(
+            maybeDeferred((yield self.calendarUnderTest()).createCalendarObjectWithName,
+            "new", VComponent.fromString(event4notCalDAV_text)),
             InvalidObjectResourceError,
-            (yield self.calendarUnderTest()).createCalendarObjectWithName,
-            "new", VComponent.fromString(event4notCalDAV_text)
         )
 
     @inlineCallbacks
@@ -806,10 +816,10 @@
         presented with invalid iCalendar text.
         """
         calendarObject = yield self.calendarObjectUnderTest()
-        self.assertRaises(
+        yield self.failUnlessFailure(
+            maybeDeferred(calendarObject.setComponent,
+                          VComponent.fromString(event4notCalDAV_text)),
             InvalidObjectResourceError,
-            calendarObject.setComponent,
-            VComponent.fromString(event4notCalDAV_text)
         )
 
 
@@ -822,9 +832,9 @@
         calendar1 = yield self.calendarUnderTest()
         component = VComponent.fromString(event4_text)
         calendarObject = yield calendar1.calendarObjectWithName("1.ics")
-        self.assertRaises(
+        yield self.failUnlessFailure(
+            maybeDeferred(calendarObject.setComponent, component),
             InvalidObjectResourceError,
-            calendarObject.setComponent, component
         )
 
 
@@ -868,12 +878,12 @@
         calendarObject = yield calendar1.calendarObjectWithName("1.ics")
         oldComponent = 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 = yield calendar1.calendarObjectWithName("1.ics")
-        self.assertEquals(calendarObject.component(), component)
+        self.assertEquals((yield calendarObject.component()), component)
 
         yield self.commit()
 
@@ -927,7 +937,7 @@
         calendar object which has been created but not committed.
         """
         calendar = yield self.calendarUnderTest()
-        calendar.createCalendarObjectWithName(
+        yield calendar.createCalendarObjectWithName(
             "4.ics", VComponent.fromString(event4_text)
         )
         newEvent = yield calendar.calendarObjectWithName("4.ics")
@@ -957,7 +967,7 @@
             (yield self.calendarObjectUnderTest()).properties()[propertyName],
             propertyContent)
         obj = yield self.calendarObjectUnderTest()
-        event1_text = obj.iCalendarText()
+        event1_text = yield obj.iCalendarText()
         event1_text_withDifferentSubject = event1_text.replace(
             "SUMMARY:CalDAV protocol updates",
             "SUMMARY:Changed"
@@ -1022,12 +1032,12 @@
         -APPLE-DROPBOX property, if available.
         """
         cal = yield self.calendarUnderTest()
-        cal.createCalendarObjectWithName("drop.ics", VComponent.fromString(
+        yield cal.createCalendarObjectWithName("drop.ics", VComponent.fromString(
                 self.eventWithDropbox
             )
         )
         obj = yield cal.calendarObjectWithName("drop.ics")
-        self.assertEquals(obj.dropboxID(), "some-dropbox-id")
+        self.assertEquals((yield obj.dropboxID()), "some-dropbox-id")
 
 
     @inlineCallbacks
@@ -1058,7 +1068,9 @@
         Common logic for attachment-creation tests.
         """
         obj = yield self.calendarObjectUnderTest()
-        t = obj.createAttachmentWithName("new.attachment", MimeType("text", "x-fixture"))
+        t = yield obj.createAttachmentWithName(
+            "new.attachment", MimeType("text", "x-fixture")
+        )
         t.write("new attachment")
         t.write(" text")
         t.loseConnection()
@@ -1071,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
@@ -1081,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']
         )
 
@@ -1120,9 +1132,9 @@
             obj.removeAttachmentWithName("new.attachment")
             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)
 
 
@@ -1147,8 +1159,9 @@
         L{ICalendarHome.calendarWithName} or L{ICalendarHome.calendars}.
         """
         obj = yield self.calendarObjectUnderTest()
-        t = obj.createAttachmentWithName("new.attachment",
-                                         MimeType("text", "plain"))
+        t = yield obj.createAttachmentWithName(
+            "new.attachment", MimeType("text", "plain")
+        )
         t.write("new attachment text")
         t.loseConnection()
         yield self.commit()
@@ -1170,8 +1183,15 @@
         yield self.calendarObjectUnderTest()
         txn = self.lastTransaction
         yield self.commit()
-        self.assertRaises(AlreadyFinishedError, txn.commit)
-        self.assertRaises(AlreadyFinishedError, txn.abort)
+        
+        yield self.failUnlessFailure(
+            maybeDeferred(txn.commit),
+            AlreadyFinishedError
+        )
+        yield self.failUnlessFailure(
+            maybeDeferred(txn.abort),
+            AlreadyFinishedError
+        )
 
 
     @inlineCallbacks
@@ -1224,7 +1244,7 @@
         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)

Modified: CalendarServer/branches/users/glyph/more-deferreds-7/txdav/caldav/datastore/test/test_sql.py
===================================================================
--- CalendarServer/branches/users/glyph/more-deferreds-7/txdav/caldav/datastore/test/test_sql.py	2010-10-13 22:16:11 UTC (rev 6424)
+++ CalendarServer/branches/users/glyph/more-deferreds-7/txdav/caldav/datastore/test/test_sql.py	2010-10-14 09:48:50 UTC (rev 6425)
@@ -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
@@ -63,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=[]):
@@ -104,6 +108,7 @@
         self.addCleanup(txn.commit)
         return txn
 
+
     @inlineCallbacks
     def test_attachmentPath(self):
         """
@@ -112,7 +117,9 @@
         L{ICalendarObject.attachmentWithName}.
         """
         yield self.createAttachmentTest(lambda x: x)
-        attachmentRoot = (yield self.calendarObjectUnderTest())._txn._store.attachmentsPath
+        attachmentRoot = (
+            yield self.calendarObjectUnderTest()
+        )._txn._store.attachmentsPath
         attachmentPath = attachmentRoot.child("ho").child("me").child("home1")
         attachmentPath = attachmentPath.child(
             (yield self.calendarObjectUnderTest()).uid()).child(
@@ -131,8 +138,9 @@
         toHome = yield self.transactionUnderTest().calendarHomeWithUID(
             "new-home", create=True)
         toCalendar = yield toHome.calendarWithName("calendar")
-        _migrateCalendar(fromCalendar, toCalendar, lambda x: x.component())
-        self.assertCalendarsSimilar(fromCalendar, toCalendar)
+        yield _migrateCalendar(fromCalendar, toCalendar,
+                               lambda x: x.component())
+        yield self.assertCalendarsSimilar(fromCalendar, toCalendar)
 
 
     @inlineCallbacks
@@ -157,7 +165,7 @@
         toHome = yield self.transactionUnderTest().calendarHomeWithUID(
             "new-home", create=True
         )
-        migrateHome(fromHome, toHome, lambda x: x.component())
+        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()
@@ -183,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)
 
@@ -248,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)
-        
+        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"
@@ -302,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"
@@ -346,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/branches/users/glyph/more-deferreds-7/txdav/caldav/datastore/test/test_util.py
===================================================================
--- CalendarServer/branches/users/glyph/more-deferreds-7/txdav/caldav/datastore/test/test_util.py	2010-10-13 22:16:11 UTC (rev 6424)
+++ CalendarServer/branches/users/glyph/more-deferreds-7/txdav/caldav/datastore/test/test_util.py	2010-10-14 09:48:50 UTC (rev 6425)
@@ -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/branches/users/glyph/more-deferreds-7/txdav/caldav/datastore/util.py
===================================================================
--- CalendarServer/branches/users/glyph/more-deferreds-7/txdav/caldav/datastore/util.py	2010-10-13 22:16:11 UTC (rev 6424)
+++ CalendarServer/branches/users/glyph/more-deferreds-7/txdav/caldav/datastore/util.py	2010-10-14 09:48:50 UTC (rev 6425)
@@ -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
@@ -122,10 +125,10 @@
     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 = yield outCalendar.calendarObjectWithName(
@@ -133,10 +136,10 @@
             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,15 @@
         (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())
     inCalendars = yield inHome.calendars()
     for calendar in inCalendars:
         name = calendar.name()
         if name == "outbox":
             continue
-        outHome.createCalendarWithName(name)
+        yield outHome.createCalendarWithName(name)
         outCalendar = yield outHome.calendarWithName(name)
         try:
             yield _migrateCalendar(calendar, outCalendar, getComponent)

Modified: CalendarServer/branches/users/glyph/more-deferreds-7/txdav/carddav/datastore/sql.py
===================================================================
--- CalendarServer/branches/users/glyph/more-deferreds-7/txdav/carddav/datastore/sql.py	2010-10-13 22:16:11 UTC (rev 6424)
+++ CalendarServer/branches/users/glyph/more-deferreds-7/txdav/carddav/datastore/sql.py	2010-10-14 09:48:50 UTC (rev 6425)
@@ -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
 
 __all__ = [
     "AddressBookHome",
@@ -44,6 +45,8 @@
 
 from zope.interface.declarations import implements
 
+
+
 class AddressBookHome(CommonHome):
 
     implements(IAddressBookHome)
@@ -58,15 +61,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 +106,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,36 +138,43 @@
             ),
         )
 
+
     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)
         else:
@@ -165,6 +182,8 @@
 
         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 +197,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 +211,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 +228,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/branches/users/glyph/more-deferreds-7/txdav/carddav/datastore/test/common.py
===================================================================
--- CalendarServer/branches/users/glyph/more-deferreds-7/txdav/carddav/datastore/test/common.py	2010-10-13 22:16:11 UTC (rev 6424)
+++ CalendarServer/branches/users/glyph/more-deferreds-7/txdav/carddav/datastore/test/common.py	2010-10-14 09:48:50 UTC (rev 6425)
@@ -17,7 +17,7 @@
 """
 Tests for common addressbook store API functions.
 """
-from twisted.internet.defer import inlineCallbacks, returnValue
+from twisted.internet.defer import inlineCallbacks, returnValue, maybeDeferred
 
 from txdav.idav import IPropertyStore, IDataStore
 from txdav.base.propertystore.base import PropertyName
@@ -178,12 +178,13 @@
         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
@@ -207,7 +208,7 @@
 
     @inlineCallbacks
     def test_notifierID(self):
-        home = self.homeUnderTest()
+        home = yield self.homeUnderTest()
         self.assertEquals(home.notifierID(), "CardDAV|home1")
         addressbook = yield home.addressbookWithName("addressbook_1")
         self.assertEquals(addressbook.notifierID(), "CardDAV|home1")
@@ -243,7 +244,7 @@
         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 = yield home.addressbookWithName(name)
             if addressbook is None:
@@ -257,9 +258,9 @@
         """
         L{IAddressBook.rename} changes the name of the L{IAddressBook}.
         """
-        home = self.homeUnderTest()
+        home = yield self.homeUnderTest()
         addressbook = yield home.addressbookWithName("addressbook_1")
-        addressbook.rename("some_other_name")
+        yield addressbook.rename("some_other_name")
         @inlineCallbacks
         def positiveAssertions():
             self.assertEquals(addressbook.name(), "some_other_name")
@@ -292,10 +293,10 @@
         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((yield home.addressbookWithName(name)), None)
-        home.createAddressBookWithName(name)
+        yield home.createAddressBookWithName(name)
         self.assertNotIdentical((yield home.addressbookWithName(name)), None)
         def checkProperties():
             addressbookProperties = (yield home.addressbookWithName(name)).properties()
@@ -314,7 +315,7 @@
             [("update", "CardDAV|home1")])
 
         # Make sure it's available in a new transaction; i.e. test the commit.
-        home = self.homeUnderTest()
+        home = yield self.homeUnderTest()
         self.assertNotIdentical((yield home.addressbookWithName(name)), None)
 
         # FIXME: These two lines aren't in the calendar common tests:
@@ -326,6 +327,7 @@
         checkProperties()
 
 
+    @inlineCallbacks
     def test_createAddressBookWithName_exists(self):
         """
         L{IAddressBookHome.createAddressBookWithName} raises
@@ -333,9 +335,10 @@
         existing address book.
         """
         for name in home1_addressbookNames:
-            self.assertRaises(
+            yield self.failUnlessFailure(
+                maybeDeferred(
+                    (yield self.homeUnderTest()).createAddressBookWithName, name),
                 HomeChildNameAlreadyExistsError,
-                self.homeUnderTest().createAddressBookWithName, name
             )
 
 
@@ -345,11 +348,11 @@
         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((yield home.addressbookWithName(name)), None)
-            home.removeAddressBookWithName(name)
+            yield home.removeAddressBookWithName(name)
             self.assertEquals((yield home.addressbookWithName(name)), None)
 
         yield self.commit()
@@ -368,13 +371,16 @@
         )
 
 
+    @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
@@ -460,7 +466,7 @@
                 (yield addressbook.addressbookObjectWithUID(uid)),
                 None
             )
-            addressbook.removeAddressBookObjectWithUID(uid)
+            yield addressbook.removeAddressBookObjectWithUID(uid)
             self.assertEquals(
                 (yield addressbook.addressbookObjectWithUID(uid)),
                 None
@@ -481,7 +487,7 @@
             self.assertNotIdentical(
                 (yield addressbook.addressbookObjectWithName(name)), None
             )
-            addressbook.removeAddressBookObjectWithName(name)
+            yield addressbook.removeAddressBookObjectWithName(name)
             self.assertIdentical(
                 (yield addressbook.addressbookObjectWithName(name)), None
             )
@@ -507,9 +513,9 @@
         Attempt to remove an non-existing addressbook object should raise.
         """
         addressbook = yield self.addressbookUnderTest()
-        self.assertRaises(
-            NoSuchObjectResourceError,
-            addressbook.removeAddressBookObjectWithName, "xyzzy"
+        yield self.failUnlessFailure(
+            maybeDeferred(addressbook.removeAddressBookObjectWithName, "xyzzy"),
+            NoSuchObjectResourceError
         )
 
 
@@ -537,7 +543,7 @@
         L{IAddressBookObject.component} returns a L{VComponent} describing the
         addressbook data underlying that addressbook object.
         """
-        component = (yield self.addressbookObjectUnderTest()).component()
+        component = yield (yield self.addressbookObjectUnderTest()).component()
 
         self.failUnless(
             isinstance(component, VComponent),
@@ -554,7 +560,7 @@
         L{IAddressBookObject.iAddressBookText} returns a C{str} describing the same
         data provided by L{IAddressBookObject.component}.
         """
-        text = (yield 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)
@@ -614,10 +620,10 @@
         L{IAddressBookHome.addressbooks} includes addressbooks recently added with
         L{IAddressBookHome.createAddressBookWithName}.
         """
-        home = self.homeUnderTest()
+        home = yield self.homeUnderTest()
         allAddressbooks = yield home.addressbooks()
         before = set(x.name() for x in allAddressbooks)
-        home.createAddressBookWithName("new-name")
+        yield home.createAddressBookWithName("new-name")
         allAddressbooks = yield home.addressbooks()
         after = set(x.name() for x in allAddressbooks)
         self.assertEquals(before | set(['new-name']), after)
@@ -633,10 +639,10 @@
         name = "4.vcf"
         self.assertIdentical((yield addressbook1.addressbookObjectWithName(name)), None)
         component = VComponent.fromString(vcard4_text)
-        addressbook1.createAddressBookObjectWithName(name, component)
+        yield addressbook1.createAddressBookObjectWithName(name, component)
 
         addressbookObject = yield addressbook1.addressbookObjectWithName(name)
-        self.assertEquals(addressbookObject.component(), component)
+        self.assertEquals((yield addressbookObject.component()), component)
 
         yield self.commit()
 
@@ -657,10 +663,11 @@
         L{AddressBookObjectNameAlreadyExistsError} if a addressbook object with the
         given name already exists in that addressbook.
         """
-        self.assertRaises(
-            ObjectResourceNameAlreadyExistsError,
-            (yield self.addressbookUnderTest()).createAddressBookObjectWithName,
-            "1.vcf", VComponent.fromString(vcard4_text)
+        self.failUnlessFailure(
+            maybeDeferred((yield self.addressbookUnderTest())
+                .createAddressBookObjectWithName,
+                "1.vcf", VComponent.fromString(vcard4_text)),
+            ObjectResourceNameAlreadyExistsError
         )
 
 
@@ -671,10 +678,11 @@
         L{InvalidAddressBookComponentError} if presented with invalid iAddressBook
         text.
         """
-        self.assertRaises(
-            InvalidObjectResourceError,
-            (yield self.addressbookUnderTest()).createAddressBookObjectWithName,
-            "new", VComponent.fromString(vcard4notCardDAV_text)
+        yield self.failUnlessFailure(
+            maybeDeferred((yield self.addressbookUnderTest())
+                .createAddressBookObjectWithName,
+                "new", VComponent.fromString(vcard4notCardDAV_text)),
+            InvalidObjectResourceError
         )
 
 
@@ -685,33 +693,34 @@
         presented with invalid iAddressBook text.
         """
         addressbookObject = (yield self.addressbookObjectUnderTest())
-        self.assertRaises(
-            InvalidObjectResourceError,
-            addressbookObject.setComponent,
-            VComponent.fromString(vcard4notCardDAV_text)
+        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 = yield self.addressbookUnderTest()
         component = VComponent.fromString(vcard4_text)
         addressbookObject = yield addressbook1.addressbookObjectWithName("1.vcf")
-        self.assertRaises(
-            InvalidObjectResourceError,
-            addressbookObject.setComponent, component
+        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"
@@ -742,14 +751,14 @@
 
         addressbook1 = yield self.addressbookUnderTest()
         addressbookObject = yield addressbook1.addressbookObjectWithName("1.vcf")
-        oldComponent = addressbookObject.component()
+        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 = yield addressbook1.addressbookObjectWithName("1.vcf")
-        self.assertEquals(addressbookObject.component(), component)
+        self.assertEquals((yield addressbookObject.component()), component)
 
         yield self.commit()
 
@@ -762,6 +771,7 @@
             ]
         )
 
+
     def checkPropertiesMethod(self, thunk):
         """
         Verify that the given object has a properties method that returns an
@@ -771,11 +781,12 @@
         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
@@ -801,7 +812,7 @@
         addressbook object which has been created but not committed.
         """
         addressbook = yield self.addressbookUnderTest()
-        addressbook.createAddressBookObjectWithName(
+        yield addressbook.createAddressBookObjectWithName(
             "4.vcf", VComponent.fromString(vcard4_text)
         )
         newEvent = yield addressbook.addressbookObjectWithName("4.vcf")
@@ -833,7 +844,7 @@
             ],
             propertyContent)
         obj = yield self.addressbookObjectUnderTest()
-        vcard1_text = obj.vCardText()
+        vcard1_text = yield obj.vCardText()
         vcard1_text_withDifferentNote = vcard1_text.replace(
             "NOTE:CardDAV protocol updates",
             "NOTE:Changed"
@@ -870,7 +881,7 @@
         Addressbook objects in one user's addressbook should not show up in another
         user's via uid or name queries.
         """
-        home1 = self.homeUnderTest()
+        home1 = yield self.homeUnderTest()
         home2 = yield self.transactionUnderTest().addressbookHomeWithUID(
             "home2", create=True)
         addressbook1 = yield home1.addressbookWithName("addressbook_1")
@@ -899,7 +910,7 @@
         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)

Modified: CalendarServer/branches/users/glyph/more-deferreds-7/txdav/carddav/datastore/test/test_sql.py
===================================================================
--- CalendarServer/branches/users/glyph/more-deferreds-7/txdav/carddav/datastore/test/test_sql.py	2010-10-13 22:16:11 UTC (rev 6424)
+++ CalendarServer/branches/users/glyph/more-deferreds-7/txdav/carddav/datastore/test/test_sql.py	2010-10-14 09:48:50 UTC (rev 6425)
@@ -19,8 +19,6 @@
 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
@@ -32,7 +30,7 @@
 
 from twisted.trial import unittest
 from twisted.internet.defer import inlineCallbacks, returnValue
-from twisted.internet.threads import deferToThread
+
 from twistedcaldav.vcard import Component as VCard
 
 
@@ -56,15 +54,15 @@
                 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)
                             )
 
@@ -89,7 +87,7 @@
         @inlineCallbacks
         def namesAndComponents(x, filter=lambda x:x.component()):
             fromObjs = yield x.addressbookObjects()
-            returnValue(dict([(fromObj.name(), filter(fromObj))
+            returnValue(dict([(fromObj.name(), (yield filter(fromObj)))
                               for fromObj in fromObjs]))
         if bAddressbookFilter is not None:
             extra = [bAddressbookFilter]
@@ -137,8 +135,8 @@
         toHome = yield self.transactionUnderTest().addressbookHomeWithUID(
             "new-home", create=True)
         toAddressbook = yield toHome.addressbookWithName("addressbook")
-        _migrateAddressbook(fromAddressbook, toAddressbook,
-                            lambda x: x.component())
+        yield _migrateAddressbook(fromAddressbook, toAddressbook,
+                                  lambda x: x.component())
         yield self.assertAddressbooksSimilar(fromAddressbook, toAddressbook)
 
 
@@ -165,7 +163,7 @@
         toHome = yield self.transactionUnderTest().addressbookHomeWithUID(
             "new-home", create=True
         )
-        migrateHome(fromHome, toHome, lambda x: x.component())
+        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()
@@ -173,7 +171,7 @@
         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)
@@ -192,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)
-        yield 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() # FIXME: CONCURRENT
-            return home_uid1_2
-        d1 = deferToThread(_defer_home_uid1_2)
-
-        def _pause_home_uid1_1():
-            time.sleep(1)
-            txn1.commit() # FIXME: CONCURRENT
-        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)
-        yield 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
@@ -255,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)
         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;;;
@@ -283,11 +230,12 @@
 END:VCARD
 """.replace("\n", "\r\n")
             ))
-            txn1.commit() # FIXME: CONCURRENT
-        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;;;
@@ -301,8 +249,8 @@
 END:VCARD
 """.replace("\n", "\r\n")
             ))
-            txn2.commit() # FIXME: CONCURRENT
-        d2 = deferToThread(_defer2)
+            yield txn2.commit() # FIXME: CONCURRENT
+        d2 = _defer2()
 
         yield d1
         yield d2

Modified: CalendarServer/branches/users/glyph/more-deferreds-7/txdav/carddav/datastore/util.py
===================================================================
--- CalendarServer/branches/users/glyph/more-deferreds-7/txdav/carddav/datastore/util.py	2010-10-13 22:16:11 UTC (rev 6424)
+++ CalendarServer/branches/users/glyph/more-deferreds-7/txdav/carddav/datastore/util.py	2010-10-14 09:48:50 UTC (rev 6425)
@@ -29,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.
@@ -62,6 +63,7 @@
         raise InvalidObjectResourceError(e)
 
 
+
 @inlineCallbacks
 def _migrateAddressbook(inAddressbook, outAddressbook, getComponent):
     """
@@ -78,9 +80,9 @@
     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.
@@ -99,12 +101,12 @@
 
 @inlineCallbacks
 def migrateHome(inHome, outHome, getComponent=lambda x:x.component()):
-    outHome.removeAddressBookWithName("addressbook")
+    yield outHome.removeAddressBookWithName("addressbook")
     outHome.properties().update(inHome.properties())
-    inAddressbooks = inHome.addressbooks()
+    inAddressbooks = yield inHome.addressbooks()
     for addressbook in inAddressbooks:
         name = addressbook.name()
-        outHome.createAddressBookWithName(name)
+        yield outHome.createAddressBookWithName(name)
         outAddressbook = yield outHome.addressbookWithName(name)
         try:
             yield _migrateAddressbook(addressbook, outAddressbook, getComponent)
@@ -112,3 +114,4 @@
             log.error("  Failed to migrate address book: %s/%s" % (inHome.name(), name,))
 
 
+

Modified: CalendarServer/branches/users/glyph/more-deferreds-7/txdav/common/datastore/sql.py
===================================================================
--- CalendarServer/branches/users/glyph/more-deferreds-7/txdav/common/datastore/sql.py	2010-10-13 22:16:11 UTC (rev 6424)
+++ CalendarServer/branches/users/glyph/more-deferreds-7/txdav/common/datastore/sql.py	2010-10-14 09:48:50 UTC (rev 6425)
@@ -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
 
@@ -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,94 @@
             return None
 
 
+    def execSQL(self, *args, **kw):
+        def reportResult(results):
+            sys.stdout.write("\n".join([
+                "",
+                "SQL: %r %r" % (args, kw),
+                "Results: %r" % (results,),
+                "",
+                ]))
+            return results
+        return self._holder.submit(
+            lambda : self._reallyExecSQL(*args, **kw)
+        )#.addBoth(reportResult)
+
+
     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 +371,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 +417,9 @@
         Run things after 'commit.'
         """
         self._postCommitOperations.append(operation)
-        # FIXME: implement.
 
+
+
 class CommonHome(LoggingMixIn):
 
     _childClass = None
@@ -335,15 +470,19 @@
         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)
 
 
+    @inlineCallbacks
     def listChildren(self):
         """
         Retrieve the names of the children in this home.
@@ -352,7 +491,7 @@
         """
         # FIXME: not specified on the interface or exercised by the tests, but
         # required by clients of the implementation!
-        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 %(column_BIND_MODE)s = %%s " % self._bindTable,
@@ -360,57 +499,60 @@
             [self._resourceID, _BIND_MODE_OWN]
         )
         names = [row[0] for row in rows]
-        return names
+        returnValue(names)
 
 
     @memoized('name', '_children')
+    @inlineCallbacks
     def childWithName(self, name):
         """
         Retrieve the child with the given C{name} contained in this
         home.
 
         @param name: a string.
-        @return: an L{ICalendar} or C{None} if no such child
-            exists.
+        @return: an L{ICalendar} or C{None} if no such child exists.
         """
-        data = self._txn.execSQL(
+        data = yield self._txn.execSQL(
             "select %(column_RESOURCE_ID)s from %(name)s where "
             "%(column_RESOURCE_NAME)s = %%s and %(column_HOME_RESOURCE_ID)s = %%s "
             "and %(column_BIND_MODE)s = %%s" % self._bindTable,
             [name, self._resourceID, _BIND_MODE_OWN]
         )
         if not data:
-            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,
@@ -421,7 +563,7 @@
              _BIND_STATUS_ACCEPTED]
         )
 
-        newChild = self.childWithName(name)
+        newChild = yield self.childWithName(name)
         newChild.properties()[
             PropertyName.fromElement(ResourceType)
         ] = newChild.resourceType()
@@ -435,14 +577,14 @@
         pass
 
 
+    @inlineCallbacks
     def removeChildWithName(self, name):
-        
-        child = self.childWithName(name)
+        child = yield self.childWithName(name)
         if not child:
             raise NoSuchHomeChildError()
         child._deletedSyncToken()
 
-        self._txn.execSQL(
+        yield self._txn.execSQL(
             "delete from %(name)s where %(column_RESOURCE_ID)s = %%s" % self._childTable,
             [child._resourceID]
         )
@@ -453,16 +595,19 @@
         child.notifyChanged()
 
 
+    @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
             """ % self._revisionsTable,
             [self._resourceID,]
         )[0][0]
-        return "%s#%s" % (self._resourceID, revision)
+        returnValue("%s#%s" % (self._resourceID, revision))
 
+
+    @inlineCallbacks
     def resourceNamesSinceToken(self, token, depth):
         results = [
             (
@@ -471,14 +616,14 @@
                 deleted
             )
             for path, collection, name, deleted 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 (%(REV:name)s.%(REV:column_RESOURCE_ID)s = %(BIND:name)s.%(BIND:column_RESOURCE_ID)s)
                 where %(REV:column_REVISION)s > %%s and %(REV:name)s.%(REV:column_HOME_RESOURCE_ID)s = %%s
                 """ % self._revisionBindJoinTable,
                 [token, self._resourceID],
-            )
+            ))
         ]
         
         deleted = []
@@ -497,16 +642,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):
@@ -586,16 +737,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,
@@ -618,67 +772,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,
@@ -693,8 +872,9 @@
         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,
@@ -709,29 +889,33 @@
         self.notifyChanged()
 
 
+    @inlineCallbacks
     def syncToken(self):
-        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,)
+        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])
         
@@ -745,7 +929,7 @@
                 else:
                     changed.append(name)
         
-        return changed, deleted,
+        returnValue((changed, deleted))
 
     def _initSyncToken(self):
         
@@ -815,9 +999,11 @@
     def _deleteRevision(self, name):
         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
         )
@@ -839,7 +1025,7 @@
             )
         elif action == "update":
             self._txn.execSQL("""
-                update %(name)s
+              ;  update %(name)s
                 set (%(column_REVISION)s) = (%%s)
                 where %(column_RESOURCE_ID)s = %%s and %(column_RESOURCE_NAME)s = %%s
                 """ % self._revisionsTable,
@@ -857,13 +1043,12 @@
             # 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("""
                     update %(name)s
@@ -888,16 +1073,21 @@
                 [nextrevision, self._resourceID,]
             )
 
-    @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
@@ -966,19 +1156,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
 
@@ -987,23 +1196,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
@@ -1011,15 +1216,17 @@
 
         @param props: the L{PropertyStore} from C{properties()}.
         """
-        pass
 
+
     # IDataStoreResource
     def contentType(self):
         raise NotImplementedError()
 
+
     def md5(self):
         return None
 
+
     def size(self):
         size = self._txn.execSQL(
             "select character_length(%(column_TEXT)s) from %(name)s "
@@ -1038,6 +1245,7 @@
         utc = datetime.datetime.strptime(created, "%Y-%m-%d %H:%M:%S.%f")
         return datetimeMktime(utc)
 
+
     def modified(self):
         modified = self._txn.execSQL(
             "select %(column_MODIFIED)s from %(name)s "
@@ -1047,6 +1255,22 @@
         utc = datetime.datetime.strptime(modified, "%Y-%m-%d %H:%M:%S.%f")
         return 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)
@@ -1064,6 +1288,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
 
@@ -1083,12 +1316,14 @@
         for name in self.listNotificationObjects():
             yield self.notificationObjectWithName(name)
 
+
+    @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):
         """
@@ -1101,32 +1336,38 @@
     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, 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)
             inserting = True
-        notificationObject.setData(uid, xmltype, xmldata, inserting=inserting)
+        yield notificationObject.setData(uid, xmltype, xmldata, inserting=inserting)
         if inserting:
             self._insertRevision("%s.xml" % (uid,))
         else:
             self._updateRevision("%s.xml" % (uid,))
 
+
     def removeNotificationObjectWithName(self, name):
         self.removeNotificationObjectWithUID(self._nameToUID(name))
 
@@ -1150,16 +1391,19 @@
             [self._resourceID,]
         )
 
+
+    @inlineCallbacks
     def syncToken(self):
-        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]
-        return "%s#%s" % (self._resourceID, revision,)
+        returnValue("%s#%s" % (self._resourceID, revision,))
 
+
     def objectResourcesSinceToken(self, token):
         raise NotImplementedError()
 
@@ -1171,16 +1415,17 @@
         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])
         
@@ -1194,8 +1439,9 @@
                 else:
                     changed.append(name)
         
-        return changed, deleted,
+        returnValue((changed, deleted))
 
+
     def _updateSyncToken(self):
 
         self._txn.execSQL("""
@@ -1215,9 +1461,11 @@
     def _deleteRevision(self, name):
         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
         )
@@ -1257,13 +1505,12 @@
             # 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("""
                     update %(name)s
@@ -1288,15 +1535,14 @@
                 [nextrevision, self._resourceID]
             )
 
-    @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()
@@ -1323,18 +1569,20 @@
         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])
@@ -1342,13 +1590,14 @@
         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):
@@ -1359,16 +1608,20 @@
         return self._fieldQuery("NOTIFICATION_UID")
 
 
-    @cached
     def properties(self):
-        props = PropertyStore(
+        return self._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(
@@ -1390,28 +1643,35 @@
         return hashlib.md5(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/branches/users/glyph/more-deferreds-7/txdav/common/datastore/sql_legacy.py
===================================================================
--- CalendarServer/branches/users/glyph/more-deferreds-7/txdav/common/datastore/sql_legacy.py	2010-10-13 22:16:11 UTC (rev 6424)
+++ CalendarServer/branches/users/glyph/more-deferreds-7/txdav/common/datastore/sql_legacy.py	2010-10-14 09:48:50 UTC (rev 6425)
@@ -126,8 +126,11 @@
         "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,
@@ -148,11 +151,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,
@@ -171,7 +177,7 @@
             """ % self._combinedTable,
             [userid]
         )
-        return self._makeInvite(rows[0]) if rows else None
+        returnValue(self._makeInvite(rows[0]) if rows else None)
 
 
     @inlineCallbacks
@@ -181,8 +187,9 @@
                 returnValue(record)
 
 
+    @inlineCallbacks
     def recordForInviteUID(self, inviteUID):
-        rows = self._txn.execSQL(
+        rows = yield self._txn.execSQL(
             """
             select
                 INVITE.INVITE_UID,
@@ -201,8 +208,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
@@ -224,6 +232,8 @@
             access, state, summary
         )
 
+
+    @inlineCallbacks
     def addOrUpdateRecord(self, record):
         bindMode = {'read-only': _BIND_MODE_READ,
                     'read-write': _BIND_MODE_WRITE}[record.access]
@@ -238,7 +248,7 @@
         # (and may contain a trailing slash).
         principalUID = record.principalURL.split("/")[3]
         shareeHome = self._getHomeWithUID(principalUID)
-        rows = self._txn.execSQL(
+        rows = yield self._txn.execSQL(
             "select RESOURCE_ID, HOME_RESOURCE_ID from INVITE where RECIPIENT_ADDRESS = %s",
             [record.userid]
         )
@@ -394,12 +404,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
@@ -411,7 +423,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
@@ -420,7 +432,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
@@ -429,7 +441,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
@@ -443,10 +455,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
@@ -455,7 +467,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
@@ -470,14 +482,16 @@
                 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 recordForShareUID(self, shareUID):
@@ -494,7 +508,7 @@
         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.
@@ -892,25 +906,29 @@
         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(
+            obj.component(), expand_until=expand_until, reCreate=True
+        )
 
 
     @inlineCallbacks
@@ -921,7 +939,7 @@
         # 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
@@ -1027,14 +1045,15 @@
             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]
-            )
-        )
+            ))
+        )))
 
 
 
@@ -1171,6 +1190,8 @@
 
         return qualifiers is not None
 
+
+    @inlineCallbacks
     def search(self, filter):
         """
         Finds resources matching the given qualifiers.
@@ -1187,20 +1208,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
@@ -1223,11 +1244,12 @@
             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/branches/users/glyph/more-deferreds-7/txdav/common/datastore/test/test_util.py
===================================================================
--- CalendarServer/branches/users/glyph/more-deferreds-7/txdav/common/datastore/test/test_util.py	2010-10-13 22:16:11 UTC (rev 6424)
+++ CalendarServer/branches/users/glyph/more-deferreds-7/txdav/common/datastore/test/test_util.py	2010-10-14 09:48:50 UTC (rev 6425)
@@ -82,7 +82,9 @@
         self.addCleanup(txn.commit)
         for uid in CommonTests.requirements:
             if CommonTests.requirements[uid] is not None:
-                self.assertNotIdentical(None, (yield 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(
@@ -117,6 +119,7 @@
         L{UpgradeToDatabaseService.startService} upgrades calendar attachments
         as well.
         """
+
         txn = self.fileStore.newTransaction()
         committed = []
         def maybeCommit():
@@ -124,28 +127,30 @@
                 committed.append(True)
                 return txn.commit()
         self.addCleanup(maybeCommit)
+
         @inlineCallbacks
         def 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 = yield getSampleObj()
-        outAttachment = outObject.attachmentWithName(someAttachmentName)
+        outAttachment = yield outObject.attachmentWithName(someAttachmentName)
         allDone = Deferred()
         class SimpleProto(Protocol):
             data = ''

Modified: CalendarServer/branches/users/glyph/more-deferreds-7/txdav/common/datastore/test/util.py
===================================================================
--- CalendarServer/branches/users/glyph/more-deferreds-7/txdav/common/datastore/test/util.py	2010-10-13 22:16:11 UTC (rev 6424)
+++ CalendarServer/branches/users/glyph/more-deferreds-7/txdav/common/datastore/test/util.py	2010-10-14 09:48:50 UTC (rev 6425)
@@ -171,14 +171,16 @@
             # 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)
+                    # 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]

Modified: CalendarServer/branches/users/glyph/more-deferreds-7/txdav/common/datastore/util.py
===================================================================
--- CalendarServer/branches/users/glyph/more-deferreds-7/txdav/common/datastore/util.py	2010-10-13 22:16:11 UTC (rev 6424)
+++ CalendarServer/branches/users/glyph/more-deferreds-7/txdav/common/datastore/util.py	2010-10-14 09:48:50 UTC (rev 6425)
@@ -101,7 +101,8 @@
             ("calendar", migrateCalendarHome,
                 self.fileStore.eachCalendarHome,
                 lambda txn: txn.calendarHomeWithUID),
-            ("addressbook", migrateAddressbookHome, self.fileStore.eachAddressbookHome,
+            ("addressbook", migrateAddressbookHome,
+                self.fileStore.eachAddressbookHome,
                 lambda txn: txn.addressbookHomeWithUID)
             ]:
             for fileTxn, fileHome in eachFunc():
@@ -109,7 +110,7 @@
                 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))
@@ -117,6 +118,8 @@
                     yield fileTxn.commit()
                     continue
                 sqlHome = yield homeGetter(uid, create=True)
+                if sqlHome is None:
+                    raise RuntimeError("THIS SHOULD NOT BE POSSIBLE.")
                 yield migrateFunc(fileHome, sqlHome)
                 yield fileTxn.commit()
                 yield sqlTxn.commit()
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20101014/1b1478d5/attachment-0001.html>


More information about the calendarserver-changes mailing list