[CalendarServer-changes] [6579] CalendarServer/trunk

source_changes at macosforge.org source_changes at macosforge.org
Tue Nov 9 08:36:37 PST 2010


Revision: 6579
          http://trac.macosforge.org/projects/calendarserver/changeset/6579
Author:   cdaboo at apple.com
Date:     2010-11-09 08:36:32 -0800 (Tue, 09 Nov 2010)
Log Message:
-----------
Get rid of remaining Proto* classes from storebridge. Do some more re-factoring of common code in storebridge.
Re-factor some code in the new store.

Modified Paths:
--------------
    CalendarServer/trunk/twistedcaldav/method/report_addressbook_query.py
    CalendarServer/trunk/twistedcaldav/method/report_calendar_query.py
    CalendarServer/trunk/twistedcaldav/method/report_common.py
    CalendarServer/trunk/twistedcaldav/resource.py
    CalendarServer/trunk/twistedcaldav/storebridge.py
    CalendarServer/trunk/txdav/caldav/datastore/file.py
    CalendarServer/trunk/txdav/caldav/datastore/sql.py
    CalendarServer/trunk/txdav/carddav/datastore/file.py
    CalendarServer/trunk/txdav/carddav/datastore/sql.py
    CalendarServer/trunk/txdav/carddav/datastore/test/common.py
    CalendarServer/trunk/txdav/common/datastore/file.py
    CalendarServer/trunk/txdav/common/datastore/sql.py

Modified: CalendarServer/trunk/twistedcaldav/method/report_addressbook_query.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/report_addressbook_query.py	2010-11-08 17:26:37 UTC (rev 6578)
+++ CalendarServer/trunk/twistedcaldav/method/report_addressbook_query.py	2010-11-09 16:36:32 UTC (rev 6579)
@@ -231,10 +231,9 @@
                         )
                         for child, child_uri in ok_resources:
                             child_uri_name = child_uri[child_uri.rfind("/") + 1:]
-                            child_path_name = urllib.unquote(child_uri_name)
                             
                             if generate_address_data or not index_query_ok:
-                                vcard = yield addrresource.vCard(child_path_name)
+                                vcard = yield child.vCard()
                                 assert vcard is not None, "vCard %s is missing from address book collection %r" % (child_uri_name, self)
                             else:
                                 vcard = None

Modified: CalendarServer/trunk/twistedcaldav/method/report_calendar_query.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/report_calendar_query.py	2010-11-08 17:26:37 UTC (rev 6578)
+++ CalendarServer/trunk/twistedcaldav/method/report_calendar_query.py	2010-11-09 16:36:32 UTC (rev 6579)
@@ -20,8 +20,6 @@
 
 __all__ = ["report_urn_ietf_params_xml_ns_caldav_calendar_query"]
 
-import urllib
-
 from twext.python.log import Logger
 from twext.web2.dav.http import ErrorResponse
 
@@ -195,10 +193,9 @@
                 
                 for child, child_uri in ok_resources:
                     child_uri_name = child_uri[child_uri.rfind("/") + 1:]
-                    child_path_name = urllib.unquote(child_uri_name)
                     
                     if generate_calendar_data or not index_query_ok:
-                        calendar = (yield calresource.iCalendarForUser(request, child_path_name))
+                        calendar = (yield child.iCalendarForUser(request))
                         assert calendar is not None, "Calendar %s is missing from calendar collection %r" % (child_uri_name, self)
                     else:
                         calendar = None

Modified: CalendarServer/trunk/twistedcaldav/method/report_common.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/report_common.py	2010-11-08 17:26:37 UTC (rev 6578)
+++ CalendarServer/trunk/twistedcaldav/method/report_common.py	2010-11-09 16:36:32 UTC (rev 6579)
@@ -533,7 +533,7 @@
                     raise NumberOfMatchesWithinLimits(max_number_of_matches)
                 
         else:
-            calendar = (yield calresource.iCalendarForUser(request, name))
+            calendar = (yield child.iCalendarForUser(request))
             
             # The calendar may come back as None if the resource is being changed, or was deleted
             # between our initial index query and getting here. For now we will ignore this error, but in

Modified: CalendarServer/trunk/twistedcaldav/resource.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/resource.py	2010-11-08 17:26:37 UTC (rev 6578)
+++ CalendarServer/trunk/twistedcaldav/resource.py	2010-11-09 16:36:32 UTC (rev 6579)
@@ -1081,14 +1081,7 @@
         returnValue(False)
 
     @inlineCallbacks
-    def iCalendarForUser(self, request, name=None):
-        if name is not None:
-            # FIXME: this is really the caller's job; why am I looking up sub-
-            # resources?
-            returnValue(
-                (yield (yield request.locateChildResource(self, name)
-                    ).iCalendarForUser(request))
-            )
+    def iCalendarForUser(self, request):
 
         caldata = yield self.iCalendar()
 
@@ -1131,7 +1124,7 @@
 
 
     @inlineCallbacks
-    def vCard(self, name=None):
+    def vCard(self):
         """
         See L{ICalDAVResource.vCard}.
 
@@ -1143,7 +1136,7 @@
         methods.
         """
         try:
-            vcard_data = yield self.vCardText(name)
+            vcard_data = yield self.vCardText()
         except InternalDataStoreError:
             returnValue(None)
 

Modified: CalendarServer/trunk/twistedcaldav/storebridge.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/storebridge.py	2010-11-08 17:26:37 UTC (rev 6578)
+++ CalendarServer/trunk/twistedcaldav/storebridge.py	2010-11-09 16:36:32 UTC (rev 6579)
@@ -28,7 +28,6 @@
 from twisted.python.log import err as logDefaultException
 from twisted.python.util import FancyEqMixin
 
-from twext.python import vcomponent
 from twext.python.log import Logger
 
 from twext.web2 import responsecode
@@ -54,6 +53,8 @@
 from twistedcaldav.resource import CalDAVResource, GlobalAddressBookResource
 from twistedcaldav.schedule import ScheduleInboxResource
 from twistedcaldav.scheduling.implicit import ImplicitScheduler
+from twistedcaldav.ical import Component as VCalendar
+from twistedcaldav.ical import Property as VProperty
 from twistedcaldav.vcard import Component as VCard
 
 from txdav.base.propertystore.base import PropertyName
@@ -157,8 +158,11 @@
 
 class _NewStoreFileMetaDataHelper(object):
 
+    def exists(self):
+        return self._newStoreObject is not None
+
     def name(self):
-        return self._newStoreObject.name() if self._newStoreObject is not None else None
+        return self._newStoreObject.name() if self._newStoreObject is not None else self._name
 
 
     def etag(self):
@@ -192,7 +196,6 @@
     """
 
     _childClass = None
-    _protoChildClass = None
 
     def _initializeWithHomeChild(self, child, home):
         """
@@ -244,23 +247,18 @@
     @inlineCallbacks
     def makeChild(self, name):
         """
-        Create a L{CalendarObjectResource} or L{ProtoCalendarObjectResource}
-        based on a calendar object name.
+        Create a L{CalendarObjectResource} based on a calendar object name.
         """
 
         if self._newStoreObject:
             newStoreObject = yield self._newStoreObject.objectResourceWithName(name)
     
-            if newStoreObject is not None:
-                similar = self._childClass(
-                    newStoreObject,
-                    principalCollections=self._principalCollections
-                )
-            else:
-                similar = self._protoChildClass(
-                    self._newStoreObject, name,
-                    principalCollections=self._principalCollections
-                )
+            similar = self._childClass(
+                newStoreObject,
+                self._newStoreObject,
+                name,
+                principalCollections=self._principalCollections
+            )
     
             self.propagateTransaction(similar)
             returnValue(similar)
@@ -395,6 +393,9 @@
 
         # Actually delete it.
         yield self._newStoreObject.remove()
+        
+        # Re-initialize to get stuff setup again now we have no object
+        self._initializeWithHomeChild(None, self._newStoreParentHome)
 
         # FIXME: handle exceptions, possibly like this:
 
@@ -457,7 +458,6 @@
         """
 
         self._childClass = CalendarObjectResource
-        self._protoChildClass = ProtoCalendarObjectResource
         super(CalendarCollectionResource, self).__init__(*args, **kw)
         self._initializeWithHomeChild(calendar, home)
         self._name = calendar.name() if calendar else name
@@ -487,8 +487,8 @@
         # FIXME: uncached: implement cache in the storage layer
 
         # Generate a monolithic calendar
-        calendar = vcomponent.VComponent("VCALENDAR")
-        calendar.addProperty(vcomponent.VProperty("VERSION", "2.0"))
+        calendar = VCalendar("VCALENDAR")
+        calendar.addProperty(VProperty("VERSION", "2.0"))
 
         # Do some optimisation of access control calculation by determining any
         # inherited ACLs outside of the child resource loop and supply those to
@@ -515,7 +515,7 @@
                 # Get the access filtered view of the data
                 caldata = yield child.iCalendarTextFiltered(isowner, accessPrincipal.principalUID() if accessPrincipal else "")
                 try:
-                    subcalendar = vcomponent.VComponent.fromString(caldata)
+                    subcalendar = VCalendar.fromString(caldata)
                 except ValueError:
                     continue
                 assert subcalendar.name() == "VCALENDAR"
@@ -610,7 +610,6 @@
     def __init__(self, *a, **kw):
 
         self._childClass = CalendarObjectResource
-        self._protoChildClass = ProtoCalendarObjectResource
         super(StoreScheduleInboxResource, self).__init__(*a, **kw)
         self.parent.propagateTransaction(self)
 
@@ -778,15 +777,12 @@
     @inlineCallbacks
     def getChild(self, name):
         attachment = yield self._newStoreCalendarObject.attachmentWithName(name)
-        if attachment is None:
-            result = ProtoCalendarAttachment(
-                self._newStoreCalendarObject,
-                name,
-                principalCollections=self.principalCollections())
-        else:
-            result = CalendarAttachment(
-                self._newStoreCalendarObject,
-                attachment, principalCollections=self.principalCollections())
+        result = CalendarAttachment(
+            self._newStoreCalendarObject,
+            attachment,
+            name,
+            principalCollections=self.principalCollections()
+        )
         self.propagateTransaction(result)
         returnValue(result)
 
@@ -900,94 +896,54 @@
 
 
 
-class ProtoCalendarAttachment(_NewStoreFileMetaDataHelper, _GetChildHelper):
-
-    def __init__(self, calendarObject, attachmentName, **kw):
-        super(ProtoCalendarAttachment, self).__init__(**kw)
-        self.calendarObject = calendarObject
-        self.attachmentName = attachmentName
-        self._newStoreObject = None
-
-
-    def isCollection(self):
-        return False
-
-
-    def http_DELETE(self, request):
-        return NO_CONTENT
-
-
-    # FIXME: Permissions should dictate a different response, sometimes.
-    def http_GET(self, request):
-        return NOT_FOUND
-
-
-    @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 = yield self.calendarObject.createAttachmentWithName(
-            self.attachmentName,
-            content_type,
-        )
-        yield readStream(request.stream, t.write)
-        self._newStoreObject = yield self.calendarObject.attachmentWithName(
-            self.attachmentName
-        )
-        yield t.loseConnection()
-        returnValue(CREATED)
-
-    http_MKCOL = None
-    http_MKCALENDAR = None
-
-
-
 class CalendarAttachment(_NewStoreFileMetaDataHelper, _GetChildHelper):
 
-    def __init__(self, calendarObject, attachment, **kw):
+    def __init__(self, calendarObject, attachment, attachmentName, **kw):
         super(CalendarAttachment, self).__init__(**kw)
         self._newStoreCalendarObject = calendarObject
         self._newStoreAttachment = self._newStoreObject = attachment
+        self.attachmentName = attachmentName
 
 
-    def etag(self):
-        # FIXME: test
-        return ETag(self._newStoreAttachment.md5())
-
-
-    def contentType(self):
-        # FIXME: test
-        return self._newStoreAttachment.contentType()
-
-
     def getChild(self, name):
         return None
 
 
     @requiresPermissions(davxml.WriteContent())
+    @inlineCallbacks
     def http_PUT(self, request):
         # FIXME: direct test
-        # FIXME: refactor with ProtoCalendarAttachment.http_PUT
         # FIXME: CDT test to make sure that permissions are enforced.
 
         content_type = request.headers.getHeader("content-type")
         if content_type is None:
             content_type = MimeType("application", "octet-stream")
 
-        t = self._newStoreAttachment.store(content_type)
-        @inlineCallbacks
-        def done(ignored):
+        if self._newStoreAttachment:
+            t = self._newStoreAttachment.store(content_type)
+            yield readStream(request.stream, t.write)
             yield t.loseConnection()
             returnValue(NO_CONTENT)
-        return readStream(request.stream, t.write).addCallback(done)
+        else:
+            t = yield self._newStoreCalendarObject.createAttachmentWithName(
+                self.attachmentName,
+                content_type,
+            )
+            yield readStream(request.stream, t.write)
+            self._newStoreAttachment = self._newStoreObject = yield self._newStoreCalendarObject.attachmentWithName(
+                self.attachmentName
+            )
+            yield t.loseConnection()
+            returnValue(CREATED)
 
 
     @requiresPermissions(davxml.Read())
     def http_GET(self, request):
+
+        if not self.exists():
+            log.debug("Resource not found: %s" % (self,))
+            raise HTTPError(responsecode.NOT_FOUND)
+
         stream = ProducerStream()
         class StreamProtocol(Protocol):
             def dataReceived(self, data):
@@ -1001,11 +957,14 @@
     @requiresPermissions(fromParent=[davxml.Unbind()])
     @inlineCallbacks
     def http_DELETE(self, request):
+        if not self.exists():
+            log.debug("Resource not found: %s" % (self,))
+            raise HTTPError(responsecode.NOT_FOUND)
+
         yield self._newStoreCalendarObject.removeAttachmentWithName(
             self._newStoreAttachment.name()
         )
-        del self._newStoreCalendarObject
-        self.__class__ = ProtoCalendarAttachment
+        self._newStoreAttachment = self._newStoreCalendarObject = None
         returnValue(NO_CONTENT)
 
 
@@ -1031,58 +990,177 @@
     def isCollection(self):
         return False
 
+class _CommonObjectResource(_NewStoreFileMetaDataHelper, CalDAVResource, FancyEqMixin):
+
+    _componentFromStream = None
+
+    def __init__(self, storeObject, parentObject, name, *args, **kw):
+        """
+        Construct a L{_CommonObjectResource} from an L{CommonObjectResource}.
+
+        @param storeObject: The storage for the object.
+        @type storeObject: L{txdav.common.CommonObjectResource}
+        """
+        super(_CommonObjectResource, self).__init__(*args, **kw)
+        self._initializeWithObject(storeObject, parentObject)
+        self._name = name
+        self._metadata = {}
+
+
+    def _initializeWithObject(self, storeObject, parentObject):
+        self._newStoreParent = parentObject
+        self._newStoreObject = storeObject
+        self._dead_properties = _NewStorePropertiesWrapper(
+            self._newStoreObject.properties()
+        ) if self._newStoreObject else NonePropertyStore(self)
+
+
+    def isCollection(self):
+        return False
+
+
+    def quotaSize(self, request):
+        return succeed(self._newStoreObject.size())
+
+
+    def text(self):
+        return self._newStoreObject.text()
+
+    def component(self):
+        return self._newStoreObject.component()
+
+    @inlineCallbacks
+    def render(self, request):
+        if not self.exists():
+            log.debug("Resource not found: %s" % (self,))
+            raise HTTPError(responsecode.NOT_FOUND)
+
+        output = yield self.text()
+
+        response = Response(200, {}, output)
+        response.headers.setHeader("content-type", self.contentType())
+        returnValue(response)
+
+
+    @requiresPermissions(fromParent=[davxml.Unbind()])
+    def http_DELETE(self, request):
+        """
+        Override http_DELETE to validate 'depth' header. 
+        """
+        if not self.exists():
+            log.err("Resource not found: %s" % (self,))
+            raise HTTPError(responsecode.NOT_FOUND)
+
+        return self.storeRemove(request, True, request.uri)
+
+
+    @inlineCallbacks
+    def storeStream(self, stream):
+
+        # FIXME: direct tests
+        component = self._componentFromStream(
+            (yield allDataFromStream(stream))
+        )
+        if self._newStoreObject:
+            yield self._newStoreObject.setComponent(component)
+            returnValue(NO_CONTENT)
+        else:
+            self._newStoreObject = (yield self._newStoreParent.createObjectResourceWithName(
+                self.name(), component, self._metadata
+            ))
+
+            # Re-initialize to get stuff setup again now we have no object
+            self._initializeWithObject(self._newStoreObject, self._newStoreParent)
+
+            returnValue(CREATED)
+
+
+    @inlineCallbacks
+    def storeRemove(self, request, implicitly, where):
+        """
+        Delete this object.
+
+        @param request: Unused by this implementation; present for signature
+            compatibility with L{CalendarCollectionResource.storeRemove}.
+
+        @type request: L{twext.web2.iweb.IRequest}
+
+        @return: an HTTP response suitable for sending to a client (or
+            including in a multi-status).
+
+         @rtype: something adaptable to L{twext.web2.iweb.IResponse}
+        """
+
+        # Do delete
+
+        yield self._newStoreParent.removeObjectResourceWithName(
+            self._newStoreObject.name()
+        )
+
+        # Re-initialize to get stuff setup again now we have no object
+        self._initializeWithObject(None, self._newStoreParent)
+
+        returnValue(NO_CONTENT)
+
+
 class _CalendarObjectMetaDataMixin(object):
+    """
+    Dynamically create the required meta-data for an object resource 
+    """
 
     def _get_accessMode(self):
-        return self._newStoreObject.accessMode
+        return self._newStoreObject.accessMode if self._newStoreObject else self._metadata.get("accessMode", None)
 
     def _set_accessMode(self, value):
-        self._newStoreObject.accessMode = value
+        if self._newStoreObject:
+            self._newStoreObject.accessMode = value
+        else:
+            self._metadata["accessMode"] = value
 
     accessMode = property(_get_accessMode, _set_accessMode)
 
     def _get_isScheduleObject(self):
-        return self._newStoreObject.isScheduleObject
+        return self._newStoreObject.isScheduleObject if self._newStoreObject else self._metadata.get("isScheduleObject", None)
 
     def _set_isScheduleObject(self, value):
-        self._newStoreObject.isScheduleObject = value
+        if self._newStoreObject:
+            self._newStoreObject.isScheduleObject = value
+        else:
+            self._metadata["isScheduleObject"] = value
 
     isScheduleObject = property(_get_isScheduleObject, _set_isScheduleObject)
 
     def _get_scheduleEtags(self):
-        return self._newStoreObject.scheduleEtags
+        return self._newStoreObject.scheduleEtags if self._newStoreObject else self._metadata.get("scheduleEtags", None)
 
     def _set_scheduleEtags(self, value):
-        self._newStoreObject.scheduleEtags = value
+        if self._newStoreObject:
+            self._newStoreObject.scheduleEtags = value
+        else:
+            self._metadata["scheduleEtags"] = value
 
     scheduleEtags = property(_get_scheduleEtags, _set_scheduleEtags)
 
     def _get_hasPrivateComment(self):
-        return self._newStoreObject.hasPrivateComment
+        return self._newStoreObject.hasPrivateComment if self._newStoreObject else self._metadata.get("hasPrivateComment", None)
 
     def _set_hasPrivateComment(self, value):
-        self._newStoreObject.hasPrivateComment = value
+        if self._newStoreObject:
+            self._newStoreObject.hasPrivateComment = value
+        else:
+            self._metadata["hasPrivateComment"] = value
 
     hasPrivateComment = property(_get_hasPrivateComment, _set_hasPrivateComment)
 
-class CalendarObjectResource(_NewStoreFileMetaDataHelper, _CalendarObjectMetaDataMixin, CalDAVResource, FancyEqMixin):
+class CalendarObjectResource(_CalendarObjectMetaDataMixin, _CommonObjectResource):
     """
     A resource wrapping a calendar object.
     """
 
     compareAttributes = '_newStoreObject'.split()
 
-    def __init__(self, calendarObject, *args, **kw):
-        """
-        Construct a L{CalendarObjectResource} from an L{ICalendarObject}.
+    _componentFromStream = VCalendar.fromString
 
-        @param calendarObject: The storage for the calendar object.
-        @type calendarObject: L{txdav.caldav.icalendarstore.ICalendarObject}
-        """
-        super(CalendarObjectResource, self).__init__(*args, **kw)
-        self._initializeWithObject(calendarObject)
-
-
     @inlineCallbacks
     def inNewTransaction(self, request):
         """
@@ -1101,60 +1179,20 @@
         homeUID = ownerHome.uid()
         txn = ownerHome.transaction().store().newTransaction(
             "new transaction for " + self._newStoreObject.name())
-        newObject = ((yield (yield (yield txn.calendarHomeWithUID(homeUID))
+        newParent = (yield (yield txn.calendarHomeWithUID(homeUID))
                              .calendarWithName(calendarName))
-                             .calendarObjectWithName(objectName)))
+        newObject = (yield newParent.calendarObjectWithName(objectName))
         request._newStoreTransaction = txn
         request._resourcesByURL.clear()
         request._urlsByResource.clear()
-        self._initializeWithObject(newObject)
+        self._initializeWithObject(newObject, newParent)
         returnValue(txn)
 
 
-    def isCollection(self):
-        return False
+    iCalendarText = _CommonObjectResource.text
+    iCalendar = _CommonObjectResource.component
 
 
-    def exists(self):
-        # FIXME: Tests
-        return True
-
-
-    def quotaSize(self, request):
-        return succeed(self._newStoreObject.size())
-
-
-    def iCalendarText(self):
-        return self._newStoreObject.iCalendarText()
-
-
-    def iCalendar(self):
-        return self._newStoreObject.component()
-
-
-    def text(self):
-        return self.iCalendarText()
-
-
-    @requiresPermissions(fromParent=[davxml.Unbind()])
-    def http_DELETE(self, request):
-        """
-        Override http_DELETE to validate 'depth' header. 
-        """
-        return self.storeRemove(request, True, request.uri)
-
-
-    @inlineCallbacks
-    def storeStream(self, stream):
-
-        # FIXME: direct tests
-        component = vcomponent.VComponent.fromString(
-            (yield allDataFromStream(stream))
-        )
-        yield self._newStoreObject.setComponent(component)
-        returnValue(NO_CONTENT)
-
-
     def validIfScheduleMatch(self, request):
         """
         Check to see if the given request's C{If-Schedule-Tag-Match} header
@@ -1234,20 +1272,8 @@
             if lock:
                 yield lock.acquire()
 
-            storeCalendar = self._newStoreObject._calendar
-            # Do delete
+            yield super(CalendarObjectResource, self).storeRemove(request, implicitly, where)
 
-            # FIXME: public attribute please.  Should ICalendar maybe just have
-            # a delete() method?
-            yield storeCalendar.removeCalendarObjectWithName(
-                self._newStoreObject.name()
-            )
-
-            # FIXME: clean this up with a 'transform' method
-            self._newStoreParentCalendar = storeCalendar
-            del self._newStoreObject
-            self.__class__ = ProtoCalendarObjectResource
-
             # Do scheduling
             if not isinbox and implicitly:
                 yield scheduler.doImplicitScheduling()
@@ -1265,74 +1291,6 @@
         returnValue(NO_CONTENT)
 
 
-    def _initializeWithObject(self, calendarObject):
-        self._newStoreObject = calendarObject
-        self._dead_properties = _NewStorePropertiesWrapper(
-            self._newStoreObject.properties()
-        )
-
-
-    @classmethod
-    def transform(cls, self, calendarObject):
-        self.__class__ = cls
-        self._initializeWithObject(calendarObject)
-
-
-
-class ProtoCalendarObjectResource(_CalendarObjectMetaDataMixin, CalDAVResource, FancyEqMixin):
-
-    compareAttributes = '_newStoreParentCalendar'.split()
-
-    def __init__(self, parentCalendar, name, *a, **kw):
-        """
-        We need to create an "empty" resource object here because resource meta-data does get
-        changed before the actual calendar data is written. So we need some kind of "container" for
-        that to ensure those meta-data values actually get pushed to the store when the resource is
-        created.
-        """
-        super(ProtoCalendarObjectResource, self).__init__(*a, **kw)
-        self._newStoreParentCalendar = parentCalendar
-        self._newStoreObject = self._newStoreParentCalendar.emptyObjectWithName(name)
-        self._name = name
-
-
-    @inlineCallbacks
-    def storeStream(self, stream):
-        # FIXME: direct tests 
-        component = vcomponent.VComponent.fromString(
-            (yield allDataFromStream(stream))
-        )
-        yield self._newStoreParentCalendar.createCalendarObjectWithName(
-            self.name(), component, objectResource=self._newStoreObject
-        )
-        CalendarObjectResource.transform(
-            self,
-            (yield self._newStoreParentCalendar.calendarObjectWithName(
-                self.name()
-            ))
-        )
-        returnValue(CREATED)
-
-
-    def createSimilarFile(self, name):
-        return None
-
-
-    def isCollection(self):
-        return False
-
-    def exists(self):
-        # FIXME: tests
-        return False
-
-
-    def name(self):
-        return self._name
-
-    def quotaSize(self, request):
-        return succeed(0)
-
-
 class AddressBookCollectionResource(_CommonHomeChildCollectionMixin, CalDAVResource):
     """
     Wrapper around a L{txdav.carddav.iaddressbook.IAddressBook}.
@@ -1345,7 +1303,6 @@
         """
 
         self._childClass = AddressBookObjectResource
-        self._protoChildClass = ProtoAddressBookObjectResource
         super(AddressBookCollectionResource, self).__init__(*args, **kw)
         self._initializeWithHomeChild(addressbook, home)
         self._name = addressbook.name() if addressbook else name
@@ -1379,165 +1336,18 @@
     """
     pass
 
-class AddressBookObjectResource(_NewStoreFileMetaDataHelper, CalDAVResource, FancyEqMixin):
+class AddressBookObjectResource(_CommonObjectResource):
     """
     A resource wrapping a addressbook object.
     """
 
     compareAttributes = '_newStoreObject'.split()
 
-    def __init__(self, Object, *args, **kw):
-        """
-        Construct a L{AddressBookObjectResource} from an L{IAddressBookObject}.
+    _componentFromStream = VCard.fromString
 
-        @param Object: The storage for the addressbook object.
-        @type Object: L{txdav.carddav.iaddressbookstore.IAddressBookObject}
-        """
-        super(AddressBookObjectResource, self).__init__(*args, **kw)
-        self._initializeWithObject(Object)
+    vCardText = _CommonObjectResource.text
 
 
-    def isCollection(self):
-        return False
-
-
-    def exists(self):
-        # FIXME: Tests
-        return True
-
-
-    def quotaSize(self, request):
-        return succeed(self._newStoreObject.size())
-
-
-    def vCardText(self, ignored=None):
-        assert ignored is None, "This is a addressbook object, not a addressbook"
-        return self._newStoreObject.vCardText()
-
-
-    def text(self):
-        return self.vCardText()
-
-
-    @inlineCallbacks
-    def render(self, request):
-        output = yield self.vCardText()
-
-        response = Response(200, {}, output)
-        response.headers.setHeader("content-type", self.contentType())
-        returnValue(response)
-
-
-    @requiresPermissions(fromParent=[davxml.Unbind()])
-    def http_DELETE(self, request):
-        """
-        Override http_DELETE to validate 'depth' header. 
-        """
-        return self.storeRemove(request, True, request.uri)
-
-
-    @inlineCallbacks
-    def storeStream(self, stream):
-
-        # FIXME: direct tests
-        component = VCard.fromString(
-            (yield allDataFromStream(stream))
-        )
-        yield self._newStoreObject.setComponent(component)
-        returnValue(NO_CONTENT)
-
-
-    @inlineCallbacks
-    def storeRemove(self, request, viaRequest, where):
-        """
-        Remove this addressbook object.
-        """
-
-        try:
-
-            storeAddressBook = self._newStoreObject._addressbook
-
-            # Do delete
-
-            # FIXME: public attribute please
-            yield storeAddressBook.removeAddressBookObjectWithName(
-                self._newStoreObject.name()
-            )
-
-            # FIXME: clean this up with a 'transform' method
-            self._newStoreParentAddressBook = storeAddressBook
-            del self._newStoreObject
-            self.__class__ = ProtoAddressBookObjectResource
-
-        except MemcacheLockTimeoutError:
-            raise HTTPError(StatusResponse(CONFLICT, "Resource: %s currently in use on the server." % (where,)))
-
-        returnValue(NO_CONTENT)
-
-
-    def _initializeWithObject(self, Object):
-        self._newStoreObject = Object
-        self._dead_properties = _NewStorePropertiesWrapper(
-            self._newStoreObject.properties()
-        )
-
-
-    @classmethod
-    def transform(cls, self, Object):
-        self.__class__ = cls
-        self._initializeWithObject(Object)
-
-
-
-class ProtoAddressBookObjectResource(CalDAVResource, FancyEqMixin):
-
-    compareAttributes = '_newStoreParentAddressBook'.split()
-
-    def __init__(self, parentAddressBook, name, *a, **kw):
-        super(ProtoAddressBookObjectResource, self).__init__(*a, **kw)
-        self._newStoreParentAddressBook = parentAddressBook
-        self._name = name
-
-
-    @inlineCallbacks
-    def storeStream(self, stream):
-        # FIXME: direct tests 
-        component = VCard.fromString(
-            (yield allDataFromStream(stream))
-        )
-        yield self._newStoreParentAddressBook.createAddressBookObjectWithName(
-            self.name(), component
-        )
-        AddressBookObjectResource.transform(
-            self,
-            (yield self._newStoreParentAddressBook.addressbookObjectWithName(
-                self.name())
-            )
-        )
-        returnValue(CREATED)
-
-
-    def createSimilarFile(self, name):
-        return None
-
-
-    def isCollection(self):
-        return False
-
-
-    def exists(self):
-        # FIXME: tests
-        return False
-
-
-    def name(self):
-        return self._name
-
-    def quotaSize(self, request):
-        # FIXME: tests, workingness
-        return succeed(0)
-
-
 class _NotificationChildHelper(object):
     """
     Methods for things which are like notification objects.
@@ -1599,13 +1409,7 @@
             yield self._newStoreNotifications.notificationObjectWithName(name)
         )
 
-        if newStoreObject is not None:
-            similar = StoreNotificationObjectFile(newStoreObject, self)
-        else:
-            # FIXME: creation in http_PUT should talk to a specific resource
-            # type; this is the domain of StoreCalendarObjectResource.
-            # similar = ProtoCalendarObjectFile(self._newStoreCalendar, path)
-            similar = ProtoStoreNotificationObjectFile(self._newStoreNotifications, self)
+        similar = StoreNotificationObjectFile(newStoreObject, self)
 
         # FIXME: tests should be failing without this line.
         # Specifically, http_PUT won't be committing its transaction properly.
@@ -1748,7 +1552,7 @@
 
 
 
-class StoreNotificationObjectFile(NotificationResource):
+class StoreNotificationObjectFile(_NewStoreFileMetaDataHelper, NotificationResource):
     """
     A resource wrapping a calendar object.
     """
@@ -1764,35 +1568,17 @@
         self._initializeWithObject(notificationObject)
 
 
+    def _initializeWithObject(self, notificationObject):
+        self._newStoreObject = notificationObject
+        self._dead_properties = _NewStorePropertiesWrapper(
+            self._newStoreObject.properties()
+        ) if self._newStoreObject else NonePropertyStore(self)
+
+
     def isCollection(self):
         return False
 
 
-    def exists(self):
-        # FIXME: Tests
-        return True
-
-
-    def etag(self):
-        return ETag(self._newStoreObject.md5())
-
-    def contentType(self):
-        return self._newStoreObject.contentType()
-
-    def contentLength(self):
-        return self._newStoreObject.size()
-
-    def lastModified(self):
-        return self._newStoreObject.modified()
-
-    def creationDate(self):
-        return self._newStoreObject.created()
-
-
-    def newStoreProperties(self):
-        return self._newStoreObject.properties()
-
-
     def quotaSize(self, request):
         return succeed(self._newStoreObject.size())
 
@@ -1805,6 +1591,10 @@
     @requiresPermissions(davxml.Read())
     @inlineCallbacks
     def http_GET(self, request):
+        if not self.exists():
+            log.err("Resource not found: %s" % (self,))
+            raise HTTPError(responsecode.NOT_FOUND)
+
         returnValue(
             Response(OK, {"content-type":self.contentType()},
                      MemoryStream((yield self.text())))
@@ -1816,6 +1606,10 @@
         """
         Override http_DELETE to validate 'depth' header. 
         """
+        if not self.exists():
+            log.err("Resource not found: %s" % (self,))
+            raise HTTPError(responsecode.NOT_FOUND)
+
         return self.storeRemove(request, request.uri)
 
 
@@ -1835,50 +1629,9 @@
                 self._newStoreObject.name()
             )
 
-            # FIXME: clean this up with a 'transform' method
-            self._newStoreParentNotifications = storeNotifications
-            del self._newStoreObject
-            self.__class__ = ProtoStoreNotificationObjectFile
+            self._initializeWithObject(None)
 
         except MemcacheLockTimeoutError:
             raise HTTPError(StatusResponse(CONFLICT, "Resource: %s currently in use on the server." % (where,)))
 
         returnValue(NO_CONTENT)
-
-
-    def _initializeWithObject(self, notificationObject):
-        self._newStoreObject = notificationObject
-        self._dead_properties = _NewStorePropertiesWrapper(
-            self._newStoreObject.properties()
-        )
-
-
-    @classmethod
-    def transform(cls, self, notificationObject):
-        self.__class__ = cls
-        self._initializeWithObject(notificationObject)
-
-
-
-class ProtoStoreNotificationObjectFile(NotificationResource):
-
-    def __init__(self, parentNotifications, *a, **kw):
-        super(ProtoStoreNotificationObjectFile, self).__init__(*a, **kw)
-        self._newStoreParentNotifications = parentNotifications
-
-
-    def isCollection(self):
-        return False
-
-
-    def exists(self):
-        # FIXME: tests
-        return False
-
-
-    def quotaSize(self, request):
-        # FIXME: tests, workingness
-        return succeed(0)
-
-
-

Modified: CalendarServer/trunk/txdav/caldav/datastore/file.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/file.py	2010-11-08 17:26:37 UTC (rev 6578)
+++ CalendarServer/trunk/txdav/caldav/datastore/file.py	2010-11-09 16:36:32 UTC (rev 6579)
@@ -229,9 +229,16 @@
     """
     implements(ICalendarObject)
 
-    def __init__(self, name, calendar):
+    def __init__(self, name, calendar, metadata=None):
         super(CalendarObject, self).__init__(name, calendar)
         self._attachments = {}
+        
+        if metadata is None:
+            metadata = {}
+        self.accessMode = metadata.get("accessMode", "")
+        self.isScheduleObject = metadata.get("isScheduleObject", False)
+        self.scheduleEtags = metadata.get("scheduleEtags", "")
+        self.hasPrivateComment = metadata.get("hasPrivateComment", False)
 
 
     @property
@@ -366,7 +373,11 @@
         return str(self.properties().get(PropertyName.fromElement(TwistedCalendarAccessProperty), ""))
 
     def _set_accessMode(self, value):
-        self.properties()[PropertyName.fromElement(TwistedCalendarAccessProperty)] = TwistedCalendarAccessProperty(value)
+        pname = PropertyName.fromElement(TwistedCalendarAccessProperty)
+        if value:
+            self.properties()[pname] = TwistedCalendarAccessProperty(value)
+        elif pname in self.properties():
+            del self.properties()[pname]
 
     accessMode = property(_get_accessMode, _set_accessMode)
 
@@ -374,7 +385,11 @@
         return str(self.properties().get(PropertyName.fromElement(TwistedSchedulingObjectResource), "false")) == "true"
 
     def _set_isScheduleObject(self, value):
-        self.properties()[PropertyName.fromElement(TwistedSchedulingObjectResource)] = TwistedSchedulingObjectResource.fromString("true" if value else "false")
+        pname = PropertyName.fromElement(TwistedSchedulingObjectResource)
+        if value:
+            self.properties()[pname] = TwistedSchedulingObjectResource.fromString("true" if value else "false")
+        elif pname in self.properties():
+            del self.properties()[pname]
 
     isScheduleObject = property(_get_isScheduleObject, _set_isScheduleObject)
 

Modified: CalendarServer/trunk/txdav/caldav/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/sql.py	2010-11-08 17:26:37 UTC (rev 6578)
+++ CalendarServer/trunk/txdav/caldav/datastore/sql.py	2010-11-09 16:36:32 UTC (rev 6579)
@@ -239,15 +239,17 @@
 class CalendarObject(CommonObjectResource):
     implements(ICalendarObject)
 
-    def __init__(self, calendar, name, uid):
+    def __init__(self, calendar, name, uid, metadata=None):
 
         super(CalendarObject, self).__init__(calendar, name, uid)
         self._objectTable = CALENDAR_OBJECT_TABLE
         
-        self._access = accessMode_to_type[""]
-        self._schedule_object = False
-        self._schedule_etags = ""
-        self._private_comments = False
+        if metadata is None:
+            metadata = {}
+        self.accessMode = metadata.get("accessMode", "")
+        self.isScheduleObject = metadata.get("isScheduleObject", False)
+        self.scheduleEtags = metadata.get("scheduleEtags", "")
+        self.hasPrivateComment = metadata.get("hasPrivateComment", False)
 
 
     @inlineCallbacks

Modified: CalendarServer/trunk/txdav/carddav/datastore/file.py
===================================================================
--- CalendarServer/trunk/txdav/carddav/datastore/file.py	2010-11-08 17:26:37 UTC (rev 6578)
+++ CalendarServer/trunk/txdav/carddav/datastore/file.py	2010-11-09 16:36:32 UTC (rev 6579)
@@ -151,7 +151,7 @@
     """
     implements(IAddressBookObject)
 
-    def __init__(self, name, addressbook):
+    def __init__(self, name, addressbook, metadata=None):
 
         super(AddressBookObject, self).__init__(name, addressbook)
 

Modified: CalendarServer/trunk/txdav/carddav/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/carddav/datastore/sql.py	2010-11-08 17:26:37 UTC (rev 6578)
+++ CalendarServer/trunk/txdav/carddav/datastore/sql.py	2010-11-09 16:36:32 UTC (rev 6579)
@@ -166,7 +166,7 @@
 
     implements(IAddressBookObject)
 
-    def __init__(self, addressbook, name, uid):
+    def __init__(self, addressbook, name, uid, metadata=None):
 
         super(AddressBookObject, self).__init__(addressbook, name, uid)
         self._objectTable = ADDRESSBOOK_OBJECT_TABLE

Modified: CalendarServer/trunk/txdav/carddav/datastore/test/common.py
===================================================================
--- CalendarServer/trunk/txdav/carddav/datastore/test/common.py	2010-11-08 17:26:37 UTC (rev 6578)
+++ CalendarServer/trunk/txdav/carddav/datastore/test/common.py	2010-11-09 16:36:32 UTC (rev 6579)
@@ -678,9 +678,9 @@
         L{AddressBookObjectNameAlreadyExistsError} if a addressbook object with the
         given name already exists in that addressbook.
         """
-        self.failUnlessFailure(
-            maybeDeferred((yield self.addressbookUnderTest())
-                .createAddressBookObjectWithName,
+        yield self.failUnlessFailure(
+            maybeDeferred(
+                (yield self.addressbookUnderTest()).createAddressBookObjectWithName,
                 "1.vcf", VComponent.fromString(vcard4_text)),
             ObjectResourceNameAlreadyExistsError
         )

Modified: CalendarServer/trunk/txdav/common/datastore/file.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/file.py	2010-11-08 17:26:37 UTC (rev 6578)
+++ CalendarServer/trunk/txdav/common/datastore/file.py	2010-11-09 16:36:32 UTC (rev 6579)
@@ -700,19 +700,11 @@
                 return obj
 
 
-    def emptyObjectWithName(self, name):
-        """
-        Sometimes we need an "empty" object that we can store meta-data on prior to actually
-        creating it with "real" data.
-        """
-        return self._objectResourceClass(name, self)
-
     @writeOperation
-    def createObjectResourceWithName(self, name, component, objectResource=None):
+    def createObjectResourceWithName(self, name, component, metadata=None):
         """
-        As per L{emptyObjectWithName} we may get passed an already created object which we need
-        to use for the one being created in order to preserve any meta-data that might have been
-        set prior to the actual creation of the data.
+        Create a new resource with component data and optional metadata. We create the
+        python object using the metadata then create the actual store object with setComponent. 
         """
         if name.startswith("."):
             raise ObjectResourceNameNotAllowedError(name)
@@ -721,15 +713,16 @@
         if objectResourcePath.exists():
             raise ObjectResourceNameAlreadyExistsError(name)
 
-        if objectResource is None:
-            objectResource = self._objectResourceClass(name, self)
+        objectResource = self._objectResourceClass(name, self, metadata)
         objectResource.setComponent(component, inserting=True)
         self._cachedObjectResources[name] = objectResource
 
         # Note: setComponent triggers a notification, so we don't need to
         # call notify( ) here like we do for object removal.
 
+        return objectResource
 
+
     @writeOperation
     def removeObjectResourceWithName(self, name):
         if name.startswith("."):
@@ -831,7 +824,7 @@
 
     compareAttributes = '_name _parentCollection'.split()
 
-    def __init__(self, name, parent):
+    def __init__(self, name, parent, metadata=None):
         self._name = name
         self._parentCollection = parent
         self._transaction = parent._transaction

Modified: CalendarServer/trunk/txdav/common/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql.py	2010-11-08 17:26:37 UTC (rev 6578)
+++ CalendarServer/trunk/txdav/common/datastore/sql.py	2010-11-09 16:36:32 UTC (rev 6579)
@@ -924,8 +924,7 @@
         """
         We create the empty object first then have it initialize itself from the store
         """
-        objectResource = self._objectResourceClass(self, name, uid)
-        objectResource = (yield objectResource.initFromStore())
+        objectResource = (yield self._objectResourceClass.objectWithName(self, name, uid))
         if objectResource:
             self._objects[objectResource.name()] = objectResource
             self._objects[objectResource.uid()] = objectResource
@@ -976,45 +975,24 @@
             self._objects[name] = None
             returnValue(None)
 
-    def emptyObjectWithName(self, name):
-        """
-        Sometimes we need an "empty" object that we can store meta-data on prior to actually
-        creating it with "real" data.
-        """
-        return self._objectResourceClass(self, name, None)
-
     @inlineCallbacks
-    def createObjectResourceWithName(self, name, component, objectResource=None):
+    def createObjectResourceWithName(self, name, component, metadata=None):
         """
-        As per L{emptyObjectWithName} we may get passed an already created object which we need
-        to use for the one being created in order to preserve any meta-data that might have been
-        set prior to the actual creation of the data.
+        Create a new resource with component data and optional metadata. We create the
+        python object using the metadata then create the actual store object with setComponent. 
         """
-        if name.startswith("."):
-            raise ObjectResourceNameNotAllowedError(name)
-
         if name in self._objects:
             if self._objects[name]:
                 raise ObjectResourceNameAlreadyExistsError()
-        else:
-            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,
-                [name, self._resourceID]
-            )
-            if rows:
-                raise ObjectResourceNameAlreadyExistsError()
 
-        if objectResource is None:
-            objectResource = self._objectResourceClass(self, name, None)
-        yield objectResource.setComponent(component, inserting=True)
+        objectResource = (yield self._objectResourceClass.create(self, name, component, metadata))
         self._objects[objectResource.name()] = objectResource
         self._objects[objectResource.uid()] = objectResource
 
-        # Note: setComponent triggers a notification, so we don't need to
+        # Note: create triggers a notification when the component is set, so we don't need to
         # call notify( ) here like we do for object removal.
 
+        returnValue(objectResource)
 
     @inlineCallbacks
     def removeObjectResourceWithName(self, name):
@@ -1314,7 +1292,7 @@
 
     _objectTable = None
 
-    def __init__(self, parent, name, uid):
+    def __init__(self, parent, name, uid, metadata=None):
         self._parentCollection = parent
         self._resourceID = None
         self._name = name
@@ -1326,7 +1304,32 @@
         self._objectText = None
 
 
+    @classmethod
+    def objectWithName(cls, parent, name, uid):
+        objectResource = cls(parent, name, uid)
+        return objectResource.initFromStore()
+
+    @classmethod
     @inlineCallbacks
+    def create(cls, parent, name, component, metadata):
+
+        child = (yield cls.objectWithName(parent, name, None))
+        if child:
+            raise ObjectResourceNameAlreadyExistsError(name)
+
+        if name.startswith("."):
+            raise ObjectResourceNameNotAllowedError(name)
+        
+        objectResource = cls(parent, name, None, metadata)
+        yield objectResource.setComponent(component, inserting=True)
+        yield objectResource._loadPropertyStore()
+
+        # Note: setComponent triggers a notification, so we don't need to
+        # call notify( ) here like we do for object removal.
+        
+        returnValue(objectResource)
+
+    @inlineCallbacks
     def initFromStore(self):
         """
         Initialise this object from the store. We read in and cache all the extra metadata
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20101109/6a800973/attachment-0001.html>


More information about the calendarserver-changes mailing list