[CalendarServer-changes] [6463] CalendarServer/trunk

source_changes at macosforge.org source_changes at macosforge.org
Mon Oct 25 09:48:53 PDT 2010


Revision: 6463
          http://trac.macosforge.org/projects/calendarserver/changeset/6463
Author:   cdaboo at apple.com
Date:     2010-10-25 09:48:50 -0700 (Mon, 25 Oct 2010)
Log Message:
-----------
Fixing up some missing yields. Re-worked object resource meta-data (md5, created, modified etc) to pre-cache from the DB when read, to
avoid having to use Deferred's to read them later. This allowed a bunch of yield's to be removed. API for object resource creation changed
to add an initFromStore method that handles all the meta-data pre-caching (as well as the property pre-cache too).

Modified Paths:
--------------
    CalendarServer/trunk/twext/web2/dav/resource.py
    CalendarServer/trunk/twext/web2/static.py
    CalendarServer/trunk/twext/web2/stream.py
    CalendarServer/trunk/twistedcaldav/extensions.py
    CalendarServer/trunk/twistedcaldav/method/put_addressbook_common.py
    CalendarServer/trunk/twistedcaldav/method/put_common.py
    CalendarServer/trunk/twistedcaldav/method/report_multiget_common.py
    CalendarServer/trunk/twistedcaldav/resource.py
    CalendarServer/trunk/twistedcaldav/storebridge.py
    CalendarServer/trunk/txdav/base/datastore/file.py
    CalendarServer/trunk/txdav/base/datastore/sql.py
    CalendarServer/trunk/txdav/caldav/datastore/file.py
    CalendarServer/trunk/txdav/caldav/datastore/sql.py
    CalendarServer/trunk/txdav/caldav/datastore/test/common.py
    CalendarServer/trunk/txdav/carddav/datastore/file.py
    CalendarServer/trunk/txdav/carddav/datastore/sql.py
    CalendarServer/trunk/txdav/carddav/datastore/test/common.py
    CalendarServer/trunk/txdav/common/datastore/file.py
    CalendarServer/trunk/txdav/common/datastore/sql.py
    CalendarServer/trunk/txdav/common/datastore/sql_schema_v1.sql
    CalendarServer/trunk/txdav/common/datastore/sql_tables.py

Modified: CalendarServer/trunk/twext/web2/dav/resource.py
===================================================================
--- CalendarServer/trunk/twext/web2/dav/resource.py	2010-10-25 16:38:03 UTC (rev 6462)
+++ CalendarServer/trunk/twext/web2/dav/resource.py	2010-10-25 16:48:50 UTC (rev 6463)
@@ -231,7 +231,7 @@
                     returnValue(davxml.ResourceType.empty) #@UndefinedVariable
 
                 if name == "getetag":
-                    etag = yield self.etag()
+                    etag = self.etag()
                     if etag is None:
                         returnValue(None)
                     returnValue(davxml.GETETag(etag.generate()))
@@ -253,13 +253,13 @@
                         returnValue(davxml.GETContentLength(str(length)))
 
                 if name == "getlastmodified":
-                    lastModified = yield self.lastModified()
+                    lastModified = self.lastModified()
                     if lastModified is None:
                         returnValue(None)
                     returnValue(davxml.GETLastModified.fromDate(lastModified))
 
                 if name == "creationdate":
-                    creationDate = yield self.creationDate()
+                    creationDate = self.creationDate()
                     if creationDate is None:
                         returnValue(None)
                     returnValue(davxml.CreationDate.fromDate(creationDate))

Modified: CalendarServer/trunk/twext/web2/static.py
===================================================================
--- CalendarServer/trunk/twext/web2/static.py	2010-10-25 16:38:03 UTC (rev 6462)
+++ CalendarServer/trunk/twext/web2/static.py	2010-10-25 16:48:50 UTC (rev 6463)
@@ -105,8 +105,8 @@
             http.checkPreconditions(
                 request,
                 entityExists = self.exists(),
-                etag = (yield self.etag()),
-                lastModified = (yield self.lastModified()),
+                etag = self.etag(),
+                lastModified = self.lastModified(),
             )
 
         # Check per-method preconditions
@@ -135,8 +135,8 @@
             # (necessarily) to the resource content, so they depend on the
             # request method, and therefore can't be set here.
             for (header, value) in (
-                ("etag", (yield self.etag())),
-                ("last-modified", (yield self.lastModified())),
+                ("etag", self.etag()),
+                ("last-modified", self.lastModified()),
             ):
                 if value is not None:
                     response.headers.setHeader(header, value)

Modified: CalendarServer/trunk/twext/web2/stream.py
===================================================================
--- CalendarServer/trunk/twext/web2/stream.py	2010-10-25 16:38:03 UTC (rev 6462)
+++ CalendarServer/trunk/twext/web2/stream.py	2010-10-25 16:48:50 UTC (rev 6463)
@@ -654,7 +654,7 @@
                 return defer.fail(f)
             return None
         else:
-            deferred = self.deferred = Deferred()
+            deferred = self.deferred = Deferred(noInlineCallbackDebugging=True)
             if self.producer is not None and (not self.streamingProducer
                                               or self.producerPaused):
                 self.producerPaused = False

Modified: CalendarServer/trunk/twistedcaldav/extensions.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/extensions.py	2010-10-25 16:38:03 UTC (rev 6462)
+++ CalendarServer/trunk/twistedcaldav/extensions.py	2010-10-25 16:48:50 UTC (rev 6463)
@@ -522,7 +522,7 @@
 
         even = Alternator()
         for name in sorted((yield self.listChildren())):
-            child = self.getChild(name)
+            child = (yield maybeDeferred(self.getChild, name))
 
             url, name, size, lastModified, contentType = self.getChildDirectoryEntry(child, name, request)
 

Modified: CalendarServer/trunk/twistedcaldav/method/put_addressbook_common.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/put_addressbook_common.py	2010-10-25 16:38:03 UTC (rev 6462)
+++ CalendarServer/trunk/twistedcaldav/method/put_addressbook_common.py	2010-10-25 16:48:50 UTC (rev 6463)
@@ -33,8 +33,6 @@
 from twext.web2.dav import davxml
 from twext.web2.dav.element.base import dav_namespace
 from twext.web2.dav.http import ErrorResponse
-from twext.web2.dav.resource import TwistedGETContentMD5
-from twext.web2.dav.stream import MD5StreamWrapper
 from twext.web2.dav.util import joinURL, parentForURL
 from twext.web2.http import HTTPError
 from twext.web2.http import StatusResponse
@@ -376,14 +374,9 @@
 
         if self.vcarddata is None:
             self.vcarddata = str(self.vcard)
-        md5 = MD5StreamWrapper(MemoryStream(self.vcarddata))
-        response = (yield self.destination.storeStream(md5))
+        stream = MemoryStream(self.vcarddata)
+        response = (yield self.destination.storeStream(stream))
 
-        # Finish MD5 calculation and write dead property
-        md5.close()
-        md5 = md5.getMD5()
-        self.destination.writeDeadProperty(TwistedGETContentMD5.fromString(md5))
-
         returnValue(response)
 
     @inlineCallbacks

Modified: CalendarServer/trunk/twistedcaldav/method/put_common.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/put_common.py	2010-10-25 16:38:03 UTC (rev 6462)
+++ CalendarServer/trunk/twistedcaldav/method/put_common.py	2010-10-25 16:48:50 UTC (rev 6463)
@@ -35,8 +35,6 @@
 from twext.web2.dav.element.base import dav_namespace
 from twext.web2.dav.element.base import PCDATAElement
 
-from twext.web2.dav.resource import TwistedGETContentMD5
-from twext.web2.dav.stream import MD5StreamWrapper
 from twext.web2.http import HTTPError
 from twext.web2.http import StatusResponse
 from twext.web2.http_headers import generateContentType, MimeType
@@ -769,14 +767,9 @@
 
         if self.calendardata is None:
             self.calendardata = str(self.calendar)
-        md5 = MD5StreamWrapper(MemoryStream(self.calendardata))
-        response = yield self.destination.storeStream(md5)
+        stream = MemoryStream(self.calendardata)
+        response = yield self.destination.storeStream(stream)
 
-        # Finish MD5 calculation and write dead property
-        md5.close()
-        md5 = md5.getMD5()
-        self.destination.writeDeadProperty(TwistedGETContentMD5.fromString(md5))
-
         returnValue(response)
 
     @inlineCallbacks
@@ -937,7 +930,7 @@
                             etags = self.destination.readDeadProperty(TwistedScheduleMatchETags).children
                         else:
                             etags = ()
-                    etags += (davxml.GETETag.fromString((yield self.destination.etag()).tag),)
+                    etags += (davxml.GETETag.fromString(self.destination.etag().tag),)
                     self.destination.writeDeadProperty(TwistedScheduleMatchETags(*etags))
                 else:
                     self.destination.removeDeadProperty(TwistedScheduleMatchETags)                

Modified: CalendarServer/trunk/twistedcaldav/method/report_multiget_common.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/report_multiget_common.py	2010-10-25 16:38:03 UTC (rev 6462)
+++ CalendarServer/trunk/twistedcaldav/method/report_multiget_common.py	2010-10-25 16:48:50 UTC (rev 6463)
@@ -31,7 +31,7 @@
 from twext.web2.dav.util import joinURL
 from twext.web2.http import HTTPError, StatusResponse
 
-from twisted.internet.defer import inlineCallbacks, returnValue
+from twisted.internet.defer import inlineCallbacks, returnValue, maybeDeferred
 
 from twistedcaldav import carddavxml
 from twistedcaldav.caldavxml import caldav_namespace
@@ -171,7 +171,8 @@
             for href in resources:
                 resource_uri = str(href)
                 name = unquote(resource_uri[resource_uri.rfind("/") + 1:])
-                if not self._isChildURI(request, resource_uri) or self.getChild(name) is None:
+                child = (yield maybeDeferred(self.getChild, name))
+                if not self._isChildURI(request, resource_uri) or child is None or not child.exists():
                     responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.NOT_FOUND)))
                 else:
                     valid_names.append(name)

Modified: CalendarServer/trunk/twistedcaldav/resource.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/resource.py	2010-10-25 16:38:03 UTC (rev 6462)
+++ CalendarServer/trunk/twistedcaldav/resource.py	2010-10-25 16:48:50 UTC (rev 6463)
@@ -40,8 +40,7 @@
 from twext.web2.dav.davxml import SyncCollection
 from twext.web2.dav.http import ErrorResponse
 
-from twisted.internet import reactor
-from twisted.internet.defer import Deferred, succeed, maybeDeferred, fail
+from twisted.internet.defer import succeed, maybeDeferred, fail
 from twisted.internet.defer import inlineCallbacks, returnValue
 
 from twext.web2 import responsecode, http, http_headers
@@ -971,61 +970,6 @@
     def findAddressBookCollections(self, depth, request, callback, privileges=None):
         return self.findSpecialCollections(carddavxml.AddressBook, depth, request, callback, privileges)
 
-    def findSpecialCollections(self, type, depth, request, callback, privileges=None):
-        assert depth in ("0", "1", "infinity"), "Invalid depth: %s" % (depth,)
-
-        def checkPrivilegesError(failure):
-            failure.trap(AccessDeniedError)
-
-            reactor.callLater(0, getChild)
-
-        def checkPrivileges(child):
-            if privileges is None:
-                return child
-
-            ca = child.checkPrivileges(request, privileges)
-            ca.addCallback(lambda ign: child)
-            return ca
-
-        def gotChild(child, childpath):
-            if child.isSpecialCollection(type):
-                callback(child, childpath)
-                
-            # No more regular collections
-            #elif child.isCollection():
-            #    if depth == "infinity":
-            #        fc = child.findSpecialCollections(type, depth, request, callback, privileges)
-            #        fc.addCallback(lambda x: reactor.callLater(0, getChild))
-            #        return fc
-
-            reactor.callLater(0, getChild)
-
-        def getChild():
-            try:
-                childname = children.pop()
-            except IndexError:
-                completionDeferred.callback(None)
-            else:
-                childpath = joinURL(basepath, childname)
-                child = request.locateResource(childpath)
-                child.addCallback(checkPrivileges)
-                child.addCallbacks(gotChild, checkPrivilegesError, (childpath,))
-                child.addErrback(completionDeferred.errback)
-
-        completionDeferred = Deferred()
-
-        if depth != "0" and self.isCollection():
-            basepath = request.urlForResource(self)
-            children = []
-            def gotChildren(childNames):
-                children[:] = list(childNames)
-                getChild()
-            maybeDeferred(self.listChildren).addCallback(gotChildren)
-        else:
-            completionDeferred.callback(None)
-
-        return completionDeferred
-
     @inlineCallbacks
     def findSpecialCollectionsFaster(self, type, depth, request, callback, privileges=None):
         assert depth in ("0", "1", "infinity"), "Invalid depth: %s" % (depth,)
@@ -1037,7 +981,7 @@
                 child = (yield request.locateResource(childpath))
                 if privileges:
                     try:
-                        child.checkPrivileges(request, privileges)
+                        yield child.checkPrivileges(request, privileges)
                     except AccessDeniedError:
                         continue
                 if child.isSpecialCollection(type):
@@ -1046,6 +990,8 @@
                     if depth == "infinity":
                         yield child.findSpecialCollectionsFaster(type, depth, request, callback, privileges)                
 
+    findSpecialCollections = findSpecialCollectionsFaster
+
     def createdCalendar(self, request):
         """
         See L{ICalDAVResource.createCalendar}.
@@ -1118,7 +1064,6 @@
 
         returnValue(False)
 
-
     @inlineCallbacks
     def iCalendarForUser(self, request, name=None):
         if name is not None:

Modified: CalendarServer/trunk/twistedcaldav/storebridge.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/storebridge.py	2010-10-25 16:38:03 UTC (rev 6462)
+++ CalendarServer/trunk/twistedcaldav/storebridge.py	2010-10-25 16:48:50 UTC (rev 6463)
@@ -20,8 +20,6 @@
 L{txdav.carddav.iaddressbookstore} and those in L{twistedcaldav}.
 """
 
-import hashlib
-
 from urlparse import urlsplit
 
 from twisted.internet.defer import succeed, inlineCallbacks, returnValue,\
@@ -56,7 +54,6 @@
 from twistedcaldav.scheduling.implicit import ImplicitScheduler
 from twistedcaldav.vcard import Component as VCard
 
-from txdav.common.icommondatastore import NoSuchObjectResourceError
 from txdav.base.propertystore.base import PropertyName
 
 log = Logger()
@@ -162,32 +159,10 @@
         return self._newStoreObject.name() if self._newStoreObject is not None else None
 
 
-    @inlineCallbacks
     def etag(self):
-        # FIXME: far too slow to be used for real, but I needed something to
-        # placate the etag computation in the case where the file doesn't exist
-        # yet (an uncommitted transaction creating this calendar file)
+        return ETag(self._newStoreObject.md5()) if self._newStoreObject is not None else None
 
-        if self._newStoreObject is None:
-            returnValue(None)
 
-        # FIXME: direct tests
-        try:
-            md5 = yield self._newStoreObject.md5()
-            if md5:
-                returnValue(ETag(md5))
-            else:
-                returnValue(ETag(
-                    hashlib.new("md5", (yield self.text())).hexdigest(),
-                    weak=False
-                ))
-        except NoSuchObjectResourceError:
-            # FIXME: a workaround for the fact that DELETE still rudely vanishes
-            # the calendar object out from underneath the store, and doesn't
-            # call storeRemove.
-            returnValue(None)
-
-
     def contentType(self):
         return self._newStoreObject.contentType() if self._newStoreObject is not None else None
 
@@ -342,9 +317,8 @@
         return self._newStoreCalendar.name()
 
 
-    @inlineCallbacks
     def etag(self):
-        returnValue(ETag((yield self._newStoreCalendar.md5())))
+        return ETag(self._newStoreCalendar.md5())
 
 
     def lastModified(self):
@@ -552,7 +526,7 @@
     @inlineCallbacks
     def listChildren(self):
         l = []
-        for attachment in (self._newStoreCalendarObject.attachments()):
+        for attachment in (yield self._newStoreCalendarObject.attachments()):
             l.append(attachment.name())
         returnValue(l)
 
@@ -670,11 +644,9 @@
         self._newStoreAttachment = self._newStoreObject = attachment
 
 
-    @inlineCallbacks
     def etag(self):
         # FIXME: test
-        md5 = yield self._newStoreAttachment.md5()
-        returnValue(ETag(md5))
+        return ETag(self._newStoreAttachment.md5())
 
 
     def contentType(self):
@@ -758,9 +730,8 @@
         return self._newStoreCalendar.name()
 
 
-    @inlineCallbacks
     def etag(self):
-        returnValue(ETag((yield self._newStoreCalendar.md5())))
+        return ETag(self._newStoreCalendar.md5())
 
 
     def lastModified(self):
@@ -1480,9 +1451,8 @@
         return self._newStoreAddressBook.name()
 
 
-    @inlineCallbacks
     def etag(self):
-        returnValue(ETag((yield self._newStoreAddressBook.md5())))
+        return ETag(self._newStoreAddressBook.md5())
 
 
     def lastModified(self):
@@ -2140,29 +2110,9 @@
         return True
 
 
-    @inlineCallbacks
     def etag(self):
-        # FIXME: far too slow to be used for real, but I needed something to
-        # placate the etag computation in the case where the file doesn't exist
-        # yet (an uncommited transaction creating this calendar file)
+        return ETag(self._newStoreObject.md5())
 
-        # FIXME: direct tests
-        try:
-            md5 = yield self._newStoreObject.md5()
-            if md5:
-                returnValue(ETag(md5))
-            else:
-                returnValue(ETag(
-                    hashlib.new("md5", (yield self.text())).hexdigest(),
-                    weak=False
-                ))
-        except NoSuchObjectResourceError:
-            # FIXME: a workaround for the fact that DELETE still rudely vanishes
-            # the calendar object out from underneath the store, and doesn't
-            # call storeRemove.
-            returnValue(None)
-
-
     def contentType(self):
         return self._newStoreObject.contentType()
 

Modified: CalendarServer/trunk/txdav/base/datastore/file.py
===================================================================
--- CalendarServer/trunk/txdav/base/datastore/file.py	2010-10-25 16:38:03 UTC (rev 6462)
+++ CalendarServer/trunk/txdav/base/datastore/file.py	2010-10-25 16:48:50 UTC (rev 6463)
@@ -28,8 +28,8 @@
 from twext.web2.dav.element.rfc2518 import GETContentType
 from twext.web2.dav.resource import TwistedGETContentMD5
 
+from twisted.python import hashlib
 
-
 from zope.interface.declarations import implements
 
 def isValidName(name):
@@ -223,7 +223,15 @@
         try:
             return str(self.properties()[PropertyName.fromElement(TwistedGETContentMD5)])
         except KeyError:
-            return None
+            # FIXME: Strictly speaking we should not need to read the data as the md5 property should always be
+            # present. However, our unit tests use static files for their data store and those currently
+            # do not include the md5 xattr.
+            try:
+                data = self._path.open().read()
+            except IOError:
+                return None
+            md5 = hashlib.md5(data).hexdigest()
+            return md5
 
     def size(self):
         """

Modified: CalendarServer/trunk/txdav/base/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/base/datastore/sql.py	2010-10-25 16:38:03 UTC (rev 6462)
+++ CalendarServer/trunk/txdav/base/datastore/sql.py	2010-10-25 16:48:50 UTC (rev 6463)
@@ -18,7 +18,7 @@
 Logic common to SQL implementations.
 """
 
-from twisted.internet.defer import Deferred
+from twisted.internet.defer import Deferred, succeed
 from inspect import getargspec
 
 def _getarg(argname, argspec, args, kw):
@@ -54,19 +54,21 @@
 
 
 
-def memoized(keyArgument, memoAttribute):
+def memoized(keyArgument, memoAttribute, deferredResult=True):
     """
     Decorator which memoizes the result of a method on that method's instance.
 
     @param keyArgument: The name of the 'key' argument.
-
     @type keyArgument: C{str}
 
     @param memoAttribute: The name of the attribute on the instance which
         should be used for memoizing the result of this method; the attribute
         itself must be a dictionary.
-
     @type memoAttribute: C{str}
+
+    @param deferredResult: Whether the result must be a deferred.
+    @type keyArgument: C{bool}
+
     """
     def decorate(thunk):
         # cheater move to try to get the right argspec from inlineCallbacks.
@@ -83,25 +85,17 @@
             memo = getattr(self, memoAttribute)
             key = _getarg(keyArgument, spec, a, kw)
             if key in memo:
-                result = memo[key]
-            else:
-                result = thunk(*a, **kw)
-                if result is not None:
-                    memo[key] = result
+                memoed = memo[key]
+                return succeed(memoed) if deferredResult else memoed
+            result = thunk(*a, **kw)
             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
+                def memoResult(finalResult):
+                    if finalResult is not None:
+                        memo[key] = finalResult
+                    return finalResult
+                result.addCallback(memoResult)
+            elif result is not None:
+                memo[key] = result
             return result
         return outer
     return decorate
-
-

Modified: CalendarServer/trunk/txdav/caldav/datastore/file.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/file.py	2010-10-25 16:38:03 UTC (rev 6462)
+++ CalendarServer/trunk/txdav/caldav/datastore/file.py	2010-10-25 16:48:50 UTC (rev 6463)
@@ -69,6 +69,9 @@
 
 from zope.interface import implements
 
+contentTypeKey = PropertyName.fromElement(GETContentType)
+md5key = PropertyName.fromElement(TwistedGETContentMD5)
+
 CalendarStore = CommonDataStore
 
 CalendarStoreTransaction = CommonStoreTransaction
@@ -250,14 +253,19 @@
             if self._path.exists():
                 backup = hidden(self._path.temporarySibling())
                 self._path.moveTo(backup)
+            
+            componentText = str(component)
             fh = self._path.open("w")
             try:
                 # FIXME: concurrency problem; if this write is interrupted
                 # halfway through, the underlying file will be corrupt.
-                fh.write(str(component))
+                fh.write(componentText)
             finally:
                 fh.close()
 
+            md5 = hashlib.md5(componentText).hexdigest()
+            self.properties()[md5key] = TwistedGETContentMD5.fromString(md5)
+
             # Now re-write the original properties on the updated file
             self.properties().flush()
 
@@ -427,9 +435,6 @@
 
 
 
-contentTypeKey = PropertyName.fromElement(GETContentType)
-md5key = PropertyName.fromElement(TwistedGETContentMD5)
-
 class AttachmentStorageTransport(object):
 
     implements(ITransport)

Modified: CalendarServer/trunk/txdav/caldav/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/sql.py	2010-10-25 16:38:03 UTC (rev 6462)
+++ CalendarServer/trunk/txdav/caldav/datastore/sql.py	2010-10-25 16:48:50 UTC (rev 6463)
@@ -227,8 +227,12 @@
 class CalendarObject(CommonObjectResource):
     implements(ICalendarObject)
 
-    _objectTable = CALENDAR_OBJECT_TABLE
+    def __init__(self, calendar, name, uid):
 
+        super(CalendarObject, self).__init__(calendar, name, uid)
+        self._objectTable = CALENDAR_OBJECT_TABLE
+
+
     @property
     def _calendar(self):
         return self._parentCollection
@@ -294,14 +298,20 @@
             organizer = ""
 
         # CALENDAR_OBJECT table update
+        self._md5 = hashlib.md5(componentText).hexdigest()
+        self._size = len(componentText)
         if inserting:
-            self._resourceID = (yield self._txn.execSQL(
+            self._resourceID, self._created, self._modified  = (
+                yield self._txn.execSQL(
                 """
                 insert into CALENDAR_OBJECT
-                (CALENDAR_RESOURCE_ID, RESOURCE_NAME, ICALENDAR_TEXT, ICALENDAR_UID, ICALENDAR_TYPE, ATTACHMENTS_MODE, ORGANIZER, RECURRANCE_MAX)
+                (CALENDAR_RESOURCE_ID, RESOURCE_NAME, ICALENDAR_TEXT, ICALENDAR_UID, ICALENDAR_TYPE, ATTACHMENTS_MODE, ORGANIZER, RECURRANCE_MAX, MD5)
                  values
-                (%s, %s, %s, %s, %s, %s, %s, %s)
-                 returning RESOURCE_ID
+                (%s, %s, %s, %s, %s, %s, %s, %s, %s)
+                returning
+                 RESOURCE_ID,
+                 CREATED,
+                 MODIFIED
                 """,
                 # FIXME: correct ATTACHMENTS_MODE based on X-APPLE-
                 # DROPBOX
@@ -314,16 +324,18 @@
                     _ATTACHMENTS_MODE_WRITE,
                     organizer,
                     normalizeForIndex(instances.limit) if instances.limit else None,
+                    self._md5,
                 ]
-            ))[0][0]
+            ))[0]
         else:
             yield self._txn.execSQL(
                 """
                 update CALENDAR_OBJECT set
-                (ICALENDAR_TEXT, ICALENDAR_UID, ICALENDAR_TYPE, ATTACHMENTS_MODE, ORGANIZER, RECURRANCE_MAX, MODIFIED)
+                (ICALENDAR_TEXT, ICALENDAR_UID, ICALENDAR_TYPE, ATTACHMENTS_MODE, ORGANIZER, RECURRANCE_MAX, MD5, MODIFIED)
                  =
-                (%s, %s, %s, %s, %s, %s, timezone('UTC', CURRENT_TIMESTAMP))
-                 where RESOURCE_ID = %s
+                (%s, %s, %s, %s, %s, %s, %s, timezone('UTC', CURRENT_TIMESTAMP))
+                where RESOURCE_ID = %s
+                returning MODIFIED
                 """,
                 # should really be filling out more fields: ORGANIZER,
                 # ORGANIZER_OBJECT, a correct ATTACHMENTS_MODE based on X-APPLE-
@@ -335,7 +347,8 @@
                     _ATTACHMENTS_MODE_WRITE,
                     organizer,
                     normalizeForIndex(instances.limit) if instances.limit else None,
-                    self._resourceID
+                    self._md5,
+                    self._resourceID,
                 ]
             )
 
@@ -485,12 +498,9 @@
     @inlineCallbacks
     def attachmentWithName(self, name):
         attachment = Attachment(self, name)
-        if (yield attachment._populate()):
-            returnValue(attachment)
-        else:
-            returnValue(None)
+        attachment = (yield attachment.initFromStore())
+        returnValue(attachment)
 
-
     @inlineCallbacks
     def attendeesCanManageAttachments(self):
         returnValue((yield self.component()).hasPropertyInAnyComponent(
@@ -569,15 +579,23 @@
     @inlineCallbacks
     def loseConnection(self):
         self.attachment._path.setContent(self.buf)
-        contentTypeString = generateContentType(self.contentType)
-        yield self._txn.execSQL(
+        self.attachment._contentType = self.contentType
+        self.attachment._md5 = self.hash.hexdigest()
+        self.attachment._size = len(self.buf)
+        self.attachment._created, self.attachment._modified = (yield self._txn.execSQL(
             """
             update ATTACHMENT set CONTENT_TYPE = %s, SIZE = %s, MD5 = %s,
-            MODIFIED = timezone('UTC', CURRENT_TIMESTAMP) WHERE PATH = %s
+             MODIFIED = timezone('UTC', CURRENT_TIMESTAMP)
+            where PATH = %s
+            returning CREATED, MODIFIED
             """,
-            [contentTypeString, len(self.buf),
-             self.hash.hexdigest(), self.attachment.name()]
-        )
+            [
+                generateContentType(self.contentType),
+                self.attachment._size,
+                self.attachment._md5,
+                self.attachment.name()
+            ]
+        ))[0]
 
 
 
@@ -596,7 +614,7 @@
 
 
     @inlineCallbacks
-    def _populate(self):
+    def initFromStore(self):
         """
         Execute necessary SQL queries to retrieve attributes.
 
@@ -609,13 +627,13 @@
             [self._name]
         )
         if not rows:
-            returnValue(False)
+            returnValue(None)
         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"))
-        returnValue(True)
+        returnValue(self)
 
 
     def name(self):

Modified: CalendarServer/trunk/txdav/caldav/datastore/test/common.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/test/common.py	2010-10-25 16:38:03 UTC (rev 6462)
+++ CalendarServer/trunk/txdav/caldav/datastore/test/common.py	2010-10-25 16:48:50 UTC (rev 6463)
@@ -295,13 +295,16 @@
 
 
     @inlineCallbacks
-    def test_notificationObjectModified(self):
+    def test_notificationObjectMetaData(self):
         """
-        The objects retrieved from the notification home have a C{modified}
-        method which returns the timestamp of their last modification.
+        The objects retrieved from the notification home have various
+        methods which return metadata values.
         """
         notification = yield self.notificationUnderTest()
-        self.assertIsInstance((yield notification.modified()), int)
+        self.assertIsInstance(notification.md5(), basestring)
+        self.assertIsInstance(notification.size(), int)
+        self.assertIsInstance(notification.created(), int)
+        self.assertIsInstance(notification.modified(), int)
 
 
     @inlineCallbacks
@@ -650,6 +653,21 @@
 
 
     @inlineCallbacks
+    def test_calendarObjectMetaData(self):
+        """
+        The objects retrieved from the calendar have a variou
+        methods which return metadata values.
+        """
+        calendar = yield self.calendarObjectUnderTest()
+        self.assertIsInstance(calendar.name(), basestring)
+        self.assertIsInstance(calendar.uid(), basestring)
+        self.assertIsInstance(calendar.md5(), basestring)
+        self.assertIsInstance(calendar.size(), int)
+        self.assertIsInstance(calendar.created(), int)
+        self.assertIsInstance(calendar.modified(), int)
+
+
+    @inlineCallbacks
     def test_component(self):
         """
         L{ICalendarObject.component} returns a L{VComponent} describing the

Modified: CalendarServer/trunk/txdav/carddav/datastore/file.py
===================================================================
--- CalendarServer/trunk/txdav/carddav/datastore/file.py	2010-10-25 16:38:03 UTC (rev 6462)
+++ CalendarServer/trunk/txdav/carddav/datastore/file.py	2010-10-25 16:48:50 UTC (rev 6463)
@@ -29,8 +29,11 @@
 
 from errno import ENOENT
 
-from twext.web2.dav.element.rfc2518 import ResourceType
+from twext.web2.dav.element.rfc2518 import ResourceType, GETContentType
+from twext.web2.dav.resource import TwistedGETContentMD5
 
+from twisted.python import hashlib
+
 from twistedcaldav.sharing import InvitesDatabase
 from twistedcaldav.vcard import Component as VComponent, InvalidVCardDataError
 from txdav.carddav.datastore.index_file import AddressBookIndex as OldIndex
@@ -50,6 +53,9 @@
 
 from zope.interface import implements
 
+contentTypeKey = PropertyName.fromElement(GETContentType)
+md5key = PropertyName.fromElement(TwistedGETContentMD5)
+
 AddressBookStore = CommonDataStore
 
 AddressBookStoreTransaction = CommonStoreTransaction
@@ -177,14 +183,19 @@
             if self._path.exists():
                 backup = hidden(self._path.temporarySibling())
                 self._path.moveTo(backup)
+
+            componentText = str(component)
             fh = self._path.open("w")
             try:
                 # FIXME: concurrency problem; if this write is interrupted
                 # halfway through, the underlying file will be corrupt.
-                fh.write(str(component))
+                fh.write(componentText)
             finally:
                 fh.close()
 
+            md5 = hashlib.md5(componentText).hexdigest()
+            self.properties()[md5key] = TwistedGETContentMD5.fromString(md5)
+
             # Now re-write the original properties on the updated file
             self.properties().flush()
 

Modified: CalendarServer/trunk/txdav/carddav/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/carddav/datastore/sql.py	2010-10-25 16:38:03 UTC (rev 6462)
+++ CalendarServer/trunk/txdav/carddav/datastore/sql.py	2010-10-25 16:48:50 UTC (rev 6463)
@@ -28,6 +28,7 @@
 from zope.interface.declarations import implements
 
 from twisted.internet.defer import inlineCallbacks, returnValue
+from twisted.python import hashlib
 
 from twext.web2.dav.element.rfc2518 import ResourceType
 from twext.web2.http_headers import MimeType
@@ -160,9 +161,9 @@
 
     implements(IAddressBookObject)
 
-    def __init__(self, name, addressbook, resid, uid):
+    def __init__(self, addressbook, name, uid):
 
-        super(AddressBookObject, self).__init__(name, addressbook, resid, uid)
+        super(AddressBookObject, self).__init__(addressbook, name, uid)
         self._objectTable = ADDRESSBOOK_OBJECT_TABLE
 
 
@@ -201,35 +202,44 @@
         self._objectText = componentText
 
         # ADDRESSBOOK_OBJECT table update
+        self._md5 = hashlib.md5(componentText).hexdigest()
+        self._size = len(componentText)
         if inserting:
-            self._resourceID = (yield self._txn.execSQL(
+            self._resourceID, self._created, self._modified = (
+                yield self._txn.execSQL(
                 """
                 insert into ADDRESSBOOK_OBJECT
-                (ADDRESSBOOK_RESOURCE_ID, RESOURCE_NAME, VCARD_TEXT, VCARD_UID)
+                (ADDRESSBOOK_RESOURCE_ID, RESOURCE_NAME, VCARD_TEXT, VCARD_UID, MD5)
                  values
-                (%s, %s, %s, %s)
-                 returning RESOURCE_ID
+                (%s, %s, %s, %s, %s)
+                returning
+                 RESOURCE_ID,
+                 CREATED,
+                 MODIFIED
                 """,
                 [
                     self._addressbook._resourceID,
                     self._name,
                     componentText,
                     component.resourceUID(),
+                    self._md5,
                 ]
-            ))[0][0]
+            ))[0]
         else:
             yield self._txn.execSQL(
                 """
                 update ADDRESSBOOK_OBJECT set
-                (VCARD_TEXT, VCARD_UID, MODIFIED)
+                (VCARD_TEXT, VCARD_UID, MD5, MODIFIED)
                  =
-                (%s, %s, timezone('UTC', CURRENT_TIMESTAMP))
-                 where RESOURCE_ID = %s
+                (%s, %s, %s, timezone('UTC', CURRENT_TIMESTAMP))
+                where RESOURCE_ID = %s
+                returning MODIFIED
                 """,
                 [
                     componentText,
                     component.resourceUID(),
-                    self._resourceID
+                    self._md5,
+                    self._resourceID,
                 ]
             )
 

Modified: CalendarServer/trunk/txdav/carddav/datastore/test/common.py
===================================================================
--- CalendarServer/trunk/txdav/carddav/datastore/test/common.py	2010-10-25 16:38:03 UTC (rev 6462)
+++ CalendarServer/trunk/txdav/carddav/datastore/test/common.py	2010-10-25 16:48:50 UTC (rev 6463)
@@ -538,6 +538,21 @@
 
 
     @inlineCallbacks
+    def test_addressbookObjectMetaData(self):
+        """
+        The objects retrieved from the addressbook have various
+        methods which return metadata values.
+        """
+        adbk = yield self.addressbookObjectUnderTest()
+        self.assertIsInstance(adbk.name(), basestring)
+        self.assertIsInstance(adbk.uid(), basestring)
+        self.assertIsInstance(adbk.md5(), basestring)
+        self.assertIsInstance(adbk.size(), int)
+        self.assertIsInstance(adbk.created(), int)
+        self.assertIsInstance(adbk.modified(), int)
+
+
+    @inlineCallbacks
     def test_component(self):
         """
         L{IAddressBookObject.component} returns a L{VComponent} describing the

Modified: CalendarServer/trunk/txdav/common/datastore/file.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/file.py	2010-10-25 16:38:03 UTC (rev 6462)
+++ CalendarServer/trunk/txdav/common/datastore/file.py	2010-10-25 16:48:50 UTC (rev 6463)
@@ -23,8 +23,10 @@
 from twext.web2.dav.element.rfc2518 import ResourceType, GETContentType, HRef
 from twext.web2.dav.element.rfc5842 import ResourceID
 from twext.web2.http_headers import generateContentType, MimeType
+from twext.web2.dav.resource import TwistedGETContentMD5
 
 from twisted.python.util import FancyEqMixin
+from twisted.python import hashlib
 
 from twistedcaldav import customxml
 from twistedcaldav.customxml import NotificationType
@@ -945,6 +947,12 @@
         return self._parentCollection
 
 
+    def created(self):
+        if not self._path.exists():
+            from twisted.internet import reactor
+            return int(reactor.seconds())
+        return super(NotificationObject, self).created()
+
     def modified(self):
         if not self._path.exists():
             from twisted.internet import reactor
@@ -961,6 +969,7 @@
         )
 
         self._xmldata = xmldata
+        md5 = hashlib.md5(xmldata).hexdigest()
 
         def do():
             backup = None
@@ -991,6 +1000,7 @@
         props = self.properties()
         props[PropertyName(*GETContentType.qname())] = GETContentType.fromString(generateContentType(MimeType("text", "xml", params={"charset":"utf-8"})))
         props[PropertyName.fromElement(NotificationType)] = NotificationType(xmltype)
+        props[PropertyName.fromElement(TwistedGETContentMD5)] = TwistedGETContentMD5.fromString(md5)
 
 
         # FIXME: the property store's flush() method may already have been

Modified: CalendarServer/trunk/txdav/common/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql.py	2010-10-25 16:38:03 UTC (rev 6462)
+++ CalendarServer/trunk/txdav/common/datastore/sql.py	2010-10-25 16:48:50 UTC (rev 6463)
@@ -619,7 +619,7 @@
         else:
             notifier = None
         child = self._childClass(self, name, resourceID, notifier)
-        yield child._loadPropertyStore()
+        yield child.initFromStore()
         returnValue(child)
 
 
@@ -863,6 +863,8 @@
         self._home = home
         self._name = name
         self._resourceID = resourceID
+        self._created = None
+        self._modified = None
         self._objects = {}
         self._notifier = notifier
 
@@ -870,6 +872,21 @@
         self._invites = None # Derived classes need to set this
 
 
+    @inlineCallbacks
+    def initFromStore(self):
+        """
+        Initialise this object from the store. We read in and cache all the extra metadata
+        from the DB to avoid having to do DB queries for those individually later.
+        """
+
+        self._created, self._modified = (yield self._txn.execSQL(
+            "select %(column_CREATED)s, %(column_MODIFIED)s from %(name)s "
+            "where %(column_RESOURCE_ID)s = %%s" % self._homeChildTable,
+            [self._resourceID]
+        ))[0]
+
+        yield self._loadPropertyStore()
+        
     @property
     def _txn(self):
         return self._home._txn
@@ -940,48 +957,26 @@
 
 
     @memoized('name', '_objects')
-    @inlineCallbacks
     def objectResourceWithName(self, name):
-        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:
-            returnValue(None)
-        [resid, uid] = rows[0]
-        returnValue((yield self._makeObjectResource(name, resid, uid)))
+        return self._makeObjectResource(name, None)
 
 
+    @memoized('uid', '_objects')
+    def objectResourceWithUID(self, uid):
+        return self._makeObjectResource(None, uid)
+
+
     @inlineCallbacks
-    def _makeObjectResource(self, name, resid, uid):
+    def _makeObjectResource(self, name, uid):
         """
-        Create an instance of C{self._objectResourceClass}.
+        We create the empty object first then have it initialize itself from the store
         """
-        objectResource = yield self._objectResourceClass(
-            name, self, resid, uid
-        )
-        yield objectResource._loadPropertyStore()
+        objectResource = self._objectResourceClass(self, name, uid)
+        objectResource = (yield objectResource.initFromStore())
         returnValue(objectResource)
 
 
-    @memoized('uid', '_objects')
     @inlineCallbacks
-    def objectResourceWithUID(self, uid):
-        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:
-            returnValue(None)
-        resid = rows[0][0]
-        name = rows[0][1]
-        returnValue((yield self._makeObjectResource(name, resid, uid)))
-
-
-    @inlineCallbacks
     def createObjectResourceWithName(self, name, component):
         if name.startswith("."):
             raise ObjectResourceNameNotAllowedError(name)
@@ -995,9 +990,7 @@
         if rows:
             raise ObjectResourceNameAlreadyExistsError()
 
-        objectResource = (
-            yield self._makeObjectResource(name, None, component.resourceUID())
-        )
+        objectResource = self._objectResourceClass(self, name, None)
         yield objectResource.setComponent(component, inserting=True)
 
         # Note: setComponent triggers a notification, so we don't need to
@@ -1261,26 +1254,14 @@
         return 0
 
 
-    @inlineCallbacks
     def created(self):
-        created = (yield self._txn.execSQL(
-            "select %(column_CREATED)s from %(name)s "
-            "where %(column_RESOURCE_ID)s = %%s" % self._homeChildTable,
-            [self._resourceID]
-        ))[0][0]
-        utc = datetime.datetime.strptime(created, "%Y-%m-%d %H:%M:%S.%f")
-        returnValue(datetimeMktime(utc))
+        utc = datetime.datetime.strptime(self._created, "%Y-%m-%d %H:%M:%S.%f")
+        return datetimeMktime(utc)
 
 
-    @inlineCallbacks
     def modified(self):
-        modified = (yield self._txn.execSQL(
-            "select %(column_MODIFIED)s from %(name)s "
-            "where %(column_RESOURCE_ID)s = %%s" % self._homeChildTable,
-            [self._resourceID]
-        ))[0][0]
-        utc = datetime.datetime.strptime(modified, "%Y-%m-%d %H:%M:%S.%f")
-        returnValue(datetimeMktime(utc))
+        utc = datetime.datetime.strptime(self._modified, "%Y-%m-%d %H:%M:%S.%f")
+        return datetimeMktime(utc)
 
 
     def notifierID(self, label="default"):
@@ -1310,15 +1291,73 @@
 
     _objectTable = None
 
-    def __init__(self, name, parent, resid, uid):
+    def __init__(self, parent, name, uid):
+        self._parentCollection = parent
+        self._resourceID = None
         self._name = name
-        self._parentCollection = parent
-        self._resourceID = resid
+        self._uid = uid
+        self._md5 = None
+        self._size = None
+        self._created = None
+        self._modified = None
         self._objectText = None
-        self._uid = uid
 
 
     @inlineCallbacks
+    def initFromStore(self):
+        """
+        Initialise this object from the store. We read in and cache all the extra metadata
+        from the DB to avoid having to do DB queries for those individually later. Either the
+        name or uid is present, so we have to tweak the query accordingly.
+        
+        @return: L{self} if object exists in the DB, else C{None}
+        """
+        
+        if self._name:
+            rows = yield self._txn.execSQL("""
+                select 
+                  %(column_RESOURCE_ID)s,
+                  %(column_RESOURCE_NAME)s,
+                  %(column_UID)s,
+                  %(column_MD5)s,
+                  character_length(%(column_TEXT)s),
+                  %(column_CREATED)s,
+                  %(column_MODIFIED)s
+                from %(name)s
+                where %(column_RESOURCE_NAME)s = %%s and %(column_PARENT_RESOURCE_ID)s = %%s
+                """ % self._objectTable,
+                [self._name, self._parentCollection._resourceID]
+            )
+        else:
+            rows = yield self._txn.execSQL("""
+                select 
+                  %(column_RESOURCE_ID)s,
+                  %(column_RESOURCE_NAME)s,
+                  %(column_UID)s,
+                  %(column_MD5)s,
+                  character_length(%(column_TEXT)s),
+                  %(column_CREATED)s,
+                  %(column_MODIFIED)s
+                from %(name)s
+                where %(column_UID)s = %%s and %(column_PARENT_RESOURCE_ID)s = %%s
+                """ % self._objectTable,
+                [self._uid, self._parentCollection._resourceID]
+            )
+        if rows:
+            (self._resourceID,
+             self._name,
+             self._uid,
+             self._md5,
+             self._size,
+             self._created,
+             self._modified,) = tuple(rows[0])
+            yield self._loadPropertyStore()
+            returnValue(self)
+        else:
+            returnValue(None)
+
+
+    @inlineCallbacks
     def _loadPropertyStore(self):
         props = yield PropertyStore.load(
             self.uid(),
@@ -1378,39 +1417,21 @@
 
 
     def md5(self):
-        return None
+        return self._md5
 
 
-    @inlineCallbacks
     def size(self):
-        size = (yield self._txn.execSQL(
-            "select character_length(%(column_TEXT)s) from %(name)s "
-            "where %(column_RESOURCE_ID)s = %%s" % self._objectTable,
-            [self._resourceID]
-        ))[0][0]
-        returnValue(size)
+        return self._size
 
 
-    @inlineCallbacks
     def created(self):
-        created = (yield self._txn.execSQL(
-            "select %(column_CREATED)s from %(name)s "
-            "where %(column_RESOURCE_ID)s = %%s" % self._objectTable,
-            [self._resourceID]
-        ))[0][0]
-        utc = datetime.datetime.strptime(created, "%Y-%m-%d %H:%M:%S.%f")
-        returnValue(datetimeMktime(utc))
+        utc = datetime.datetime.strptime(self._created, "%Y-%m-%d %H:%M:%S.%f")
+        return datetimeMktime(utc)
 
 
-    @inlineCallbacks
     def modified(self):
-        modified = (yield self._txn.execSQL(
-            "select %(column_MODIFIED)s from %(name)s "
-            "where %(column_RESOURCE_ID)s = %%s" % self._objectTable,
-            [self._resourceID]
-        ))[0][0]
-        utc = datetime.datetime.strptime(modified, "%Y-%m-%d %H:%M:%S.%f")
-        returnValue(datetimeMktime(utc))
+        utc = datetime.datetime.strptime(self._modified, "%Y-%m-%d %H:%M:%S.%f")
+        return datetimeMktime(utc)
 
 
     @inlineCallbacks
@@ -1502,17 +1523,13 @@
     @memoized('uid', '_notifications')
     @inlineCallbacks
     def notificationObjectWithUID(self, uid):
-        rows = (yield self._txn.execSQL(
-            "select RESOURCE_ID from NOTIFICATION "
-            "where NOTIFICATION_UID = %s and NOTIFICATION_HOME_RESOURCE_ID = %s",
-            [uid, self._resourceID]))
-        if rows:
-            resourceID = rows[0][0]
-            no = NotificationObject(self, uid, resourceID)
-            yield no._loadPropertyStore()
-            returnValue(no)
-        else:
-            returnValue(None)
+        """
+        We create the empty object first then have it initialize itself from the store
+        """
+        
+        no = NotificationObject(self, uid)
+        no = (yield no.initFromStore())
+        returnValue(no)
 
 
     @inlineCallbacks
@@ -1521,7 +1538,7 @@
         inserting = False
         notificationObject = yield self.notificationObjectWithUID(uid)
         if notificationObject is None:
-            notificationObject = NotificationObject(self, uid, None)
+            notificationObject = NotificationObject(self, uid)
             inserting = True
         yield notificationObject.setData(uid, xmltype, xmldata, inserting=inserting)
         if inserting:
@@ -1693,15 +1710,47 @@
 
     compareAttributes = '_resourceID _home'.split()
 
-    def __init__(self, home, uid, resourceID):
+    def __init__(self, home, uid):
         self._home = home
         self._uid = uid
-        self._resourceID = resourceID
+        self._resourceID = None
+        self._md5 = None
+        self._size = None
+        self._created = None
+        self._modified = None
 
-
     def __repr__(self):
         return "<%s: %s>" % (self.__class__.__name__, self._resourceID)
 
+    @inlineCallbacks
+    def initFromStore(self):
+        """
+        Initialise this object from the store. We read in and cache all the extra metadata
+        from the DB to avoid having to do DB queries for those individually later.
+        
+        @return: L{self} if object exists in the DB, else C{None}
+        """
+        rows = (yield self._txn.execSQL("""
+            select
+                RESOURCE_ID,
+                MD5,
+                character_length(XML_DATA),
+                CREATED,
+                MODIFIED
+            from NOTIFICATION
+            where NOTIFICATION_UID = %s and NOTIFICATION_HOME_RESOURCE_ID = %s
+            """,
+            [self._uid, self._home._resourceID]))
+        if rows:
+            (self._resourceID,
+             self._md5,
+             self._size,
+             self._created,
+             self._modified,) = tuple(rows[0])
+            yield self._loadPropertyStore()
+            returnValue(self)
+        else:
+            returnValue(None)
 
     @property
     def _txn(self):
@@ -1722,21 +1771,37 @@
 
     @inlineCallbacks
     def setData(self, uid, xmltype, xmldata, inserting=False):
+        """
+        Set the object resource data and update and cached metadata.
+        """
 
         xmltypeString = xmltype.toxml()
+        self._md5 = hashlib.md5(xmldata).hexdigest()
+        self._size = len(xmldata)
         if inserting:
-            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]
+            rows = yield self._txn.execSQL("""
+                insert into NOTIFICATION
+                  (NOTIFICATION_HOME_RESOURCE_ID, NOTIFICATION_UID, XML_TYPE, XML_DATA, MD5)
+                values
+                  (%s, %s, %s, %s, %s) 
+                returning
+                  RESOURCE_ID,
+                  CREATED,
+                  MODIFIED
+                """,
+                [self._home._resourceID, uid, xmltypeString, xmldata, self._md5]
             )
-            self._resourceID = rows[0][0]
+            self._resourceID, self._created, self._modified = rows[0]
             yield self._loadPropertyStore()
         else:
-            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])
+            rows = yield self._txn.execSQL("""
+                update NOTIFICATION
+                set XML_TYPE = %s, XML_DATA = %s, MD5 = %s
+                where NOTIFICATION_HOME_RESOURCE_ID = %s and NOTIFICATION_UID = %s
+                returning MODIFIED
+                """,
+                [xmltypeString, xmldata, self._md5, self._home._resourceID, uid])
+            self._modified = rows[0][0]
 
         self.properties()[PropertyName.fromElement(NotificationType)] = NotificationType(xmltype)
 
@@ -1787,40 +1852,22 @@
         return MimeType.fromString("text/xml")
 
 
-    @inlineCallbacks
     def md5(self):
-        returnValue(hashlib.md5((yield self.xmldata())).hexdigest())
+        return self._md5
 
 
-    @inlineCallbacks
     def size(self):
-        size = (yield self._txn.execSQL(
-            "select character_length(XML_DATA) from NOTIFICATION "
-            "where RESOURCE_ID = %s",
-            [self._resourceID]
-        ))[0][0]
-        returnValue(size)
+        return self._size
 
 
-    @inlineCallbacks
     def created(self):
-        created = (yield self._txn.execSQL(
-            "select CREATED from NOTIFICATION "
-            "where RESOURCE_ID = %s",
-            [self._resourceID]
-        ))[0][0]
-        utc = datetime.datetime.strptime(created, "%Y-%m-%d %H:%M:%S.%f")
-        returnValue(datetimeMktime(utc))
+        utc = datetime.datetime.strptime(self._created, "%Y-%m-%d %H:%M:%S.%f")
+        return datetimeMktime(utc)
 
 
-    @inlineCallbacks
     def modified(self):
-        modified = (yield self._txn.execSQL(
-            "select MODIFIED from NOTIFICATION "
-            "where RESOURCE_ID = %s", [self._resourceID]
-        ))[0][0]
-        utc = datetime.datetime.strptime(modified, "%Y-%m-%d %H:%M:%S.%f")
-        returnValue(datetimeMktime(utc))
+        utc = datetime.datetime.strptime(self._modified, "%Y-%m-%d %H:%M:%S.%f")
+        return datetimeMktime(utc)
 
 
 

Modified: CalendarServer/trunk/txdav/common/datastore/sql_schema_v1.sql
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql_schema_v1.sql	2010-10-25 16:38:03 UTC (rev 6462)
+++ CalendarServer/trunk/txdav/common/datastore/sql_schema_v1.sql	2010-10-25 16:48:50 UTC (rev 6463)
@@ -55,6 +55,7 @@
   NOTIFICATION_UID              varchar(255) not null,
   XML_TYPE                      varchar      not null,
   XML_DATA                      varchar      not null,
+  MD5                           char(32)      not null,
   CREATED                       timestamp default timezone('UTC', CURRENT_TIMESTAMP),
   MODIFIED                      timestamp default timezone('UTC', CURRENT_TIMESTAMP),
 
@@ -126,6 +127,7 @@
   ORGANIZER            varchar(255),
   ORGANIZER_OBJECT     integer      references CALENDAR_OBJECT,
   RECURRANCE_MAX       date,        -- maximum date that recurrences have been expanded to.
+  MD5                  char(32)      not null,
   CREATED              timestamp default timezone('UTC', CURRENT_TIMESTAMP),
   MODIFIED             timestamp default timezone('UTC', CURRENT_TIMESTAMP),
 
@@ -292,6 +294,7 @@
   RESOURCE_NAME           varchar(255) not null,
   VCARD_TEXT              text         not null,
   VCARD_UID               varchar(255) not null,
+  MD5                     char(32)     not null,
   CREATED                 timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
   MODIFIED                timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
 

Modified: CalendarServer/trunk/txdav/common/datastore/sql_tables.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql_tables.py	2010-10-25 16:38:03 UTC (rev 6462)
+++ CalendarServer/trunk/txdav/common/datastore/sql_tables.py	2010-10-25 16:48:50 UTC (rev 6463)
@@ -113,6 +113,7 @@
     "column_RESOURCE_NAME"      : "RESOURCE_NAME",
     "column_TEXT"               : "ICALENDAR_TEXT",
     "column_UID"                : "ICALENDAR_UID",
+    "column_MD5"                : "MD5",
     "column_CREATED"            : "CREATED",
     "column_MODIFIED"           : "MODIFIED",
 }
@@ -124,6 +125,7 @@
     "column_RESOURCE_NAME"      : "RESOURCE_NAME",
     "column_TEXT"               : "VCARD_TEXT",
     "column_UID"                : "VCARD_UID",
+    "column_MD5"                : "MD5",
     "column_CREATED"            : "CREATED",
     "column_MODIFIED"           : "MODIFIED",
 }
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20101025/2932618e/attachment-0001.html>


More information about the calendarserver-changes mailing list