[CalendarServer-changes] [11093] CalendarServer/branches/users/cdaboo/store-scheduling

source_changes at macosforge.org source_changes at macosforge.org
Tue Apr 23 12:49:27 PDT 2013


Revision: 11093
          http://trac.calendarserver.org//changeset/11093
Author:   cdaboo at apple.com
Date:     2013-04-23 12:49:27 -0700 (Tue, 23 Apr 2013)
Log Message:
-----------
Checkpoint: move managed attachment processing fully into the store.

Modified Paths:
--------------
    CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/storebridge.py
    CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/test/test_implicit.py
    CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/sql.py
    CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/icalendarstore.py

Modified: CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/storebridge.py
===================================================================
--- CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/storebridge.py	2013-04-23 16:37:12 UTC (rev 11092)
+++ CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/storebridge.py	2013-04-23 19:49:27 UTC (rev 11093)
@@ -52,7 +52,6 @@
 from twistedcaldav.resource import CalDAVResource, GlobalAddressBookResource, \
     DefaultAlarmPropertyMixin
 from twistedcaldav.scheduling_store.caldav.resource import ScheduleInboxResource
-from twistedcaldav.scheduling.implicit import ImplicitScheduler
 from twistedcaldav.vcard import Component as VCard, InvalidVCardDataError
 
 from txdav.base.propertystore.base import PropertyName
@@ -62,7 +61,7 @@
     TooManyAttendeesError, InvalidCalendarAccessError, ValidOrganizerError, \
     UIDExistsError, InvalidUIDError, InvalidPerUserDataMerge, \
     AttendeeAllowedError, ResourceDeletedError, InvalidComponentForStoreError, \
-    InvalidResourceMove, UIDExistsElsewhereError
+    InvalidResourceMove, UIDExistsElsewhereError, InvalidAttachmentOperation
 from txdav.common.datastore.sql_tables import _BIND_MODE_READ, _BIND_MODE_WRITE, \
     _BIND_MODE_DIRECT
 from txdav.common.icommondatastore import NoSuchObjectResourceError, \
@@ -2578,6 +2577,16 @@
         InvalidComponentTypeError: (caldav_namespace, "supported-component"),
     }
 
+    StoreAttachmentValidErrors = set((
+        AttachmentStoreFailed,
+        InvalidAttachmentOperation,
+    ))
+
+    StoreAttachmentExceptionsErrors = {
+        AttachmentStoreValidManagedID: (caldav_namespace, "valid-managed-id-parameter",),
+        AttachmentRemoveFailed: (caldav_namespace, "valid-attachment-remove",),
+    }
+
     @inlineCallbacks
     def http_PUT(self, request):
 
@@ -2783,105 +2792,77 @@
             "attachment-remove": "valid-attachment-remove",
         }
 
-        # Only allow organizers to manipulate managed attachments for now
-        calendar = (yield self.iCalendarForUser(request))
-        scheduler = ImplicitScheduler()
-        is_attendee = (yield scheduler.testAttendeeEvent(request, self, calendar,))
-        if is_attendee and action in valid_preconditions:
-            raise HTTPError(ErrorResponse(
-                FORBIDDEN,
-                (caldav_namespace, valid_preconditions[action],),
-                "Attendees are not allowed to manipulate managed attachments",
-            ))
-
         # Dispatch to store object
-        if action == "attachment-add":
+        try:
+            if action == "attachment-add":
+                rids = _getRIDs()
+                content_type, filename = _getContentInfo()
+                attachment, location = (yield self._newStoreObject.addAttachment(rids, content_type, filename, request.stream))
+                post_result = Response(CREATED)
 
-            # Add an attachment property
-            rids = _getRIDs()
-            content_type, filename = _getContentInfo()
-            try:
-                attachment, location = (yield self._newStoreObject.addAttachment(rids, content_type, filename, request.stream, calendar))
-            except AttachmentStoreFailed:
+            elif action == "attachment-update":
+                mid = _getMID()
+                content_type, filename = _getContentInfo()
+                attachment, location = (yield self._newStoreObject.updateAttachment(mid, content_type, filename, request.stream))
+                post_result = Response(NO_CONTENT)
+
+            elif action == "attachment-remove":
+                rids = _getRIDs()
+                mid = _getMID()
+                yield self._newStoreObject.removeAttachment(rids, mid)
+                post_result = Response(NO_CONTENT)
+
+            else:
                 raise HTTPError(ErrorResponse(
                     FORBIDDEN,
-                    (caldav_namespace, "valid-attachment-add",),
-                    "Could not store the supplied attachment",
+                    (caldav_namespace, "valid-action-parameter",),
+                    "The action parameter in the request-URI is not valid",
                 ))
-            except QuotaExceeded:
-                raise HTTPError(ErrorResponse(
-                    INSUFFICIENT_STORAGE_SPACE,
-                    (dav_namespace, "quota-not-exceeded"),
-                    "Could not store the supplied attachment because user quota would be exceeded",
-                ))
 
-            post_result = Response(CREATED)
+        except QuotaExceeded:
+            raise HTTPError(ErrorResponse(
+                INSUFFICIENT_STORAGE_SPACE,
+                (dav_namespace, "quota-not-exceeded"),
+                "Could not store the supplied attachment because user quota would be exceeded",
+            ))
 
-        elif action == "attachment-update":
-            mid = _getMID()
-            content_type, filename = _getContentInfo()
-            try:
-                attachment, location = (yield self._newStoreObject.updateAttachment(mid, content_type, filename, request.stream, calendar))
-            except AttachmentStoreValidManagedID:
+        # Map store exception to HTTP errors
+        except Exception as err:
+
+            if type(err) in self.StoreAttachmentValidErrors:
                 raise HTTPError(ErrorResponse(
-                    FORBIDDEN,
-                    (caldav_namespace, "valid-managed-id-parameter",),
-                    "The managed-id parameter does not refer to an attachment in this calendar object resource",
+                    responsecode.FORBIDDEN,
+                    (caldav_namespace, valid_preconditions[action],),
+                    str(err),
                 ))
-            except AttachmentStoreFailed:
+
+            elif type(err) in self.StoreAttachmentExceptionsErrors:
                 raise HTTPError(ErrorResponse(
-                    FORBIDDEN,
-                    (caldav_namespace, "valid-attachment-update",),
-                    "Could not store the supplied attachment",
+                    responsecode.FORBIDDEN,
+                    self.StoreAttachmentExceptionsErrors[type(err)],
+                    str(err),
                 ))
-            except QuotaExceeded:
-                raise HTTPError(ErrorResponse(
-                    INSUFFICIENT_STORAGE_SPACE,
-                    (dav_namespace, "quota-not-exceeded"),
-                    "Could not store the supplied attachment because user quota would be exceeded",
-                ))
 
-            post_result = Response(NO_CONTENT)
+            elif type(err) in self.StoreExceptionsStatusErrors:
+                raise HTTPError(StatusResponse(responsecode.FORBIDDEN, str(err)))
 
-        elif action == "attachment-remove":
-            rids = _getRIDs()
-            mid = _getMID()
-            try:
-                yield self._newStoreObject.removeAttachment(rids, mid, calendar)
-            except AttachmentStoreValidManagedID:
+            elif type(err) in self.StoreExceptionsErrors:
                 raise HTTPError(ErrorResponse(
-                    FORBIDDEN,
-                    (caldav_namespace, "valid-managed-id-parameter",),
-                    "The managed-id parameter does not refer to an attachment in this calendar object resource",
+                    responsecode.FORBIDDEN,
+                    self.StoreExceptionsErrors[type(err)],
+                    str(err),
                 ))
-            except AttachmentRemoveFailed:
-                raise HTTPError(ErrorResponse(
-                    FORBIDDEN,
-                    (caldav_namespace, "valid-attachment-remove",),
-                    "Could not remove the specified attachment",
-                ))
 
-            post_result = Response(NO_CONTENT)
+            else:
+                raise
 
-        else:
-            raise HTTPError(ErrorResponse(
-                FORBIDDEN,
-                (caldav_namespace, "valid-action-parameter",),
-                "The action parameter in the request-URI is not valid",
-            ))
-
-        # TODO: The storing piece here should go away once we do implicit in the store
-        # Store new resource
-        parent = (yield request.locateResource(parentForURL(request.path)))
-        storer = self.storeResource(request, None, self, request.uri, parent, False, calendar, attachmentProcessingDone=True)
-        result = (yield storer.run())
-
         # Look for Prefer header
         prefer = request.headers.getHeader("prefer", {})
         returnRepresentation = any([key == "return" and value == "representation" for key, value, _ignore_args in prefer])
-        if returnRepresentation and result.code / 100 == 2:
+        if returnRepresentation:
             result = (yield self.render(request))
             result.code = OK
+            result.headers.removeHeader("content-location")
             result.headers.setHeader("content-location", request.path)
         else:
             result = post_result

Modified: CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/test/test_implicit.py
===================================================================
--- CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/test/test_implicit.py	2013-04-23 16:37:12 UTC (rev 11092)
+++ CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/test/test_implicit.py	2013-04-23 19:49:27 UTC (rev 11093)
@@ -873,7 +873,7 @@
 
 class ImplicitRequests(CommonCommonTests, TestCase):
     """
-    Test twistedcaldav.scheduling.implicit with a Request object.
+    Test txdav.caldav.datastore.scheduling.implicit.
     """
 
     @inlineCallbacks

Modified: CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/sql.py
===================================================================
--- CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/sql.py	2013-04-23 16:37:12 UTC (rev 11092)
+++ CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/sql.py	2013-04-23 19:49:27 UTC (rev 11093)
@@ -73,7 +73,7 @@
     AttendeeAllowedError, InvalidPerUserDataMerge, ComponentUpdateState, \
     ValidOrganizerError, ShareeAllowedError, ComponentRemoveState, \
     InvalidComponentForStoreError, InvalidResourceMove, InvalidDefaultCalendar, \
-    UIDExistsElsewhereError
+    UIDExistsElsewhereError, InvalidAttachmentOperation
 from txdav.caldav.icalendarstore import QuotaExceeded
 from txdav.common.datastore.sql import CommonHome, CommonHomeChild, \
     CommonObjectResource, ECALENDARTYPE
@@ -1831,6 +1831,8 @@
         new_component = None
         did_implicit_action = False
 
+        is_internal = internal_state not in (ComponentUpdateState.NORMAL, ComponentUpdateState.ATTACHMENT_UPDATE,)
+
         # Do scheduling
         if not self.calendar().isInbox():
             scheduler = ImplicitScheduler()
@@ -1840,10 +1842,10 @@
                 self.calendar(),
                 None if inserting else self,
                 component,
-                internal_request=(internal_state != ComponentUpdateState.NORMAL),
+                internal_request=is_internal,
             ))
 
-            if do_implicit_action and internal_state == ComponentUpdateState.NORMAL:
+            if do_implicit_action and not is_internal:
 
                 # Cannot do implicit in sharee's shared calendar
                 if not self.calendar().owned():
@@ -2817,8 +2819,37 @@
 
 
     @inlineCallbacks
-    def addAttachment(self, rids, content_type, filename, stream, calendar):
+    def _checkValidManagedAttachmentChange(self):
+        """
+        Make sure a managed attachment add, update or remover operation is valid.
+        """
 
+        # Only allow organizers to manipulate managed attachments for now
+        calendar = (yield self.componentForUser())
+        scheduler = ImplicitScheduler()
+        is_attendee = (yield scheduler.testAttendeeEvent(self.calendar(), self, calendar,))
+        if is_attendee:
+            raise InvalidAttachmentOperation("Attendees are not allowed to manipulate managed attachments")
+
+
+    @inlineCallbacks
+    def addAttachment(self, rids, content_type, filename, stream):
+        """
+        Add a new managed attachment to this calendar object.
+
+        @param rids: list of recurrence-ids for components to add to, or C{None} to add to all.
+        @type rids: C{str} or C{None}
+        @param content_type: the MIME media type/subtype of the attachment
+        @type content_type: L{MimeType}
+        @param filename: the name for the attachment
+        @type filename: C{str}
+        @param stream: the stream to read attachment data from
+        @type stream: L{IStream}
+        """
+
+        # Check validity of request
+        yield self._checkValidManagedAttachmentChange()
+
         # First write the data stream
 
         # We need to know the resource_ID of the home collection of the owner
@@ -2836,8 +2867,11 @@
             self._dropboxID = str(uuid.uuid4())
         attachment._objectDropboxID = self._dropboxID
 
-        # Now try and adjust the actual calendar data
-        #calendar = (yield self.component())
+        # Now try and adjust the actual calendar data.
+        # NB We need a copy of the original calendar data as implicit scheduling will need to compare that to
+        # the original in order to detect changes that would case scheduling.
+        calendar = (yield self.componentForUser())
+        calendar = calendar.duplicate()
 
         attach, location = (yield attachment.attachProperty())
         if rids is None:
@@ -2846,15 +2880,30 @@
             # TODO - per-recurrence attachments
             pass
 
-        # TODO: Here is where we want to store data implicitly - for now we have to let app layer deal with it
-        #yield self.setComponent(calendar)
+        # Here is where we want to store data implicitly
+        yield self._setComponentInternal(calendar, internal_state=ComponentUpdateState.ATTACHMENT_UPDATE)
 
         returnValue((attachment, location,))
 
 
     @inlineCallbacks
-    def updateAttachment(self, managed_id, content_type, filename, stream, calendar):
+    def updateAttachment(self, managed_id, content_type, filename, stream):
+        """
+        Update a managed attachment in this calendar object.
 
+        @param managed_id: the attachment's managed-id
+        @type managed_id: C{str}
+        @param content_type: the new MIME media type/subtype of the attachment
+        @type content_type: L{MIMEType}
+        @param filename: the new name for the attachment
+        @type filename: C{str}
+        @param stream: the stream to read new attachment data from
+        @type stream: L{IStream}
+        """
+
+        # Check validity of request
+        yield self._checkValidManagedAttachmentChange()
+
         # First check the supplied managed-id is associated with this resource
         cobjs = (yield ManagedAttachment.referencesTo(self._txn, managed_id))
         if self._resourceID not in cobjs:
@@ -2881,29 +2930,47 @@
             raise AttachmentStoreFailed
         yield t.loseConnection()
 
-        # Now try and adjust the actual calendar data
-        #calendar = (yield self.component())
+        # Now try and adjust the actual calendar data.
+        # NB We need a copy of the original calendar data as implicit scheduling will need to compare that to
+        # the original in order to detect changes that would case scheduling.
+        calendar = (yield self.componentForUser())
+        calendar = calendar.duplicate()
 
         attach, location = (yield attachment.attachProperty())
         calendar.replaceAllPropertiesWithParameterMatch(attach, "MANAGED-ID", managed_id)
 
-        # TODO: Here is where we want to store data implicitly - for now we have to let app layer deal with it
-        #yield self.setComponent(calendar)
+        # Here is where we want to store data implicitly
+        yield self._setComponentInternal(calendar, internal_state=ComponentUpdateState.ATTACHMENT_UPDATE)
 
         returnValue((attachment, location,))
 
 
     @inlineCallbacks
-    def removeAttachment(self, rids, managed_id, calendar):
+    def removeAttachment(self, rids, managed_id):
+        """
+        Remove a managed attachment from this calendar object.
 
+        @param rids: list of recurrence-ids for components to add to, or C{None} to add to all.
+        @type rids: C{str} or C{None}
+        @param managed_id: the attachment's managed-id
+        @type managed_id: C{str}
+        """
+
+        # Check validity of request
+        yield self._checkValidManagedAttachmentChange()
+
         # First check the supplied managed-id is associated with this resource
         cobjs = (yield ManagedAttachment.referencesTo(self._txn, managed_id))
         if self._resourceID not in cobjs:
             raise AttachmentStoreValidManagedID
 
-        # Now try and adjust the actual calendar data
+        # Now try and adjust the actual calendar data.
+        # NB We need a copy of the original calendar data as implicit scheduling will need to compare that to
+        # the original in order to detect changes that would case scheduling.
         all_removed = False
-        #calendar = (yield self.component())
+        calendar = (yield self.componentForUser())
+        calendar = calendar.duplicate()
+
         if rids is None:
             calendar.removeAllPropertiesWithParameterMatch("ATTACH", "MANAGED-ID", managed_id)
             all_removed = True
@@ -2911,8 +2978,8 @@
             # TODO: per-recurrence removal
             pass
 
-        # TODO: Here is where we want to store data implicitly - for now we have to let app layer deal with it
-        #yield self.setComponent(calendar)
+        # Here is where we want to store data implicitly
+        yield self._setComponentInternal(calendar, internal_state=ComponentUpdateState.ATTACHMENT_UPDATE)
 
         # Remove it - this will take care of actually removing it from the store if there are
         # no more references to the attachment

Modified: CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/icalendarstore.py
===================================================================
--- CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/icalendarstore.py	2013-04-23 16:37:12 UTC (rev 11092)
+++ CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/icalendarstore.py	2013-04-23 19:49:27 UTC (rev 11093)
@@ -74,6 +74,8 @@
                             iTIP message. Some validation and implicit scheduling is not done. Schedule-Tag
                             is changed.
 
+    ATTACHMENT_UPDATE     - change to a managed attachment that is re-writing calendar data.
+
     RAW                   - store the supplied data as-is without any processing or validation. This is used
                             for unit testing purposes only.
     """
@@ -82,12 +84,14 @@
     INBOX = NamedConstant()
     ORGANIZER_ITIP_UPDATE = NamedConstant()
     ATTENDEE_ITIP_UPDATE = NamedConstant()
+    ATTACHMENT_UPDATE = NamedConstant()
     RAW = NamedConstant()
 
     NORMAL.description = "normal"
     INBOX.description = "inbox"
     ORGANIZER_ITIP_UPDATE.description = "organizer-update"
     ATTENDEE_ITIP_UPDATE.description = "attendee-update"
+    ATTACHMENT_UPDATE.description = "attachment-update"
     RAW.description = "raw"
 
 
@@ -214,6 +218,13 @@
 
 
 
+class InvalidAttachmentOperation(Exception):
+    """
+    Unable to store an attachment because some aspect of the request is invalid.
+    """
+
+
+
 class AttachmentStoreFailed(Exception):
     """
     Unable to store an attachment.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20130423/194d3867/attachment-0001.html>


More information about the calendarserver-changes mailing list