[CalendarServer-changes] [10131] CalendarServer/branches/users/cdaboo/managed-attachments

source_changes at macosforge.org source_changes at macosforge.org
Thu Dec 6 13:28:23 PST 2012


Revision: 10131
          http://trac.calendarserver.org//changeset/10131
Author:   cdaboo at apple.com
Date:     2012-12-06 13:28:23 -0800 (Thu, 06 Dec 2012)
Log Message:
-----------
Support managed attachments and implicit scheduling.

Modified Paths:
--------------
    CalendarServer/branches/users/cdaboo/managed-attachments/twistedcaldav/method/put_common.py
    CalendarServer/branches/users/cdaboo/managed-attachments/twistedcaldav/scheduling/implicit.py
    CalendarServer/branches/users/cdaboo/managed-attachments/twistedcaldav/storebridge.py
    CalendarServer/branches/users/cdaboo/managed-attachments/txdav/caldav/datastore/sql.py

Modified: CalendarServer/branches/users/cdaboo/managed-attachments/twistedcaldav/method/put_common.py
===================================================================
--- CalendarServer/branches/users/cdaboo/managed-attachments/twistedcaldav/method/put_common.py	2012-12-06 21:20:50 UTC (rev 10130)
+++ CalendarServer/branches/users/cdaboo/managed-attachments/twistedcaldav/method/put_common.py	2012-12-06 21:28:23 UTC (rev 10131)
@@ -135,6 +135,7 @@
         internal_request=False,
         processing_organizer=None,
         returnData=False,
+        attachmentProcessingDone=False,
     ):
         """
         Function that does common PUT/COPY/MOVE behavior.
@@ -156,6 +157,7 @@
         @param internal_request:   True if this request originates internally and needs to bypass scheduling authorization checks.
         @param processing_organizer: True if implicit processing for an organizer, False if for an attendee, None if not implicit processing.
         @param returnData:         True if the caller wants the actual data written to the store returned
+        @param attachmentProcessingDone    True if the caller has already processed managed attachment changes
         """
 
         # Check that all arguments are valid
@@ -196,6 +198,7 @@
         self.internal_request = internal_request
         self.processing_organizer = processing_organizer
         self.returnData = returnData
+        self.attachmentProcessingDone = attachmentProcessingDone
 
         self.access = None
         self.hasPrivateComments = False
@@ -1186,6 +1189,10 @@
             # Handle sharing dropbox normalization
             dropboxChanged = (yield self.dropboxPathNormalization())
 
+            # Pre-process managed attachments
+            if not self.internal_request and not self.attachmentProcessingDone:
+                managed_copied, managed_removed = (yield self.destination.preProcessManagedAttachments(self.calendar))
+
             # Default/duplicate alarms
             alarmChanged = self.processAlarms()
 
@@ -1230,6 +1237,10 @@
             # Do the actual put or copy
             response = (yield self.doStore(data_changed))
 
+            # Post process managed attachments
+            if not self.internal_request and not self.attachmentProcessingDone:
+                yield self.destination.postProcessManagedAttachments(managed_copied, managed_removed)
+
             # Must not set ETag in response if data changed
             if did_implicit_action or dropboxChanged or alarmChanged:
                 def _removeEtag(request, response):

Modified: CalendarServer/branches/users/cdaboo/managed-attachments/twistedcaldav/scheduling/implicit.py
===================================================================
--- CalendarServer/branches/users/cdaboo/managed-attachments/twistedcaldav/scheduling/implicit.py	2012-12-06 21:20:50 UTC (rev 10130)
+++ CalendarServer/branches/users/cdaboo/managed-attachments/twistedcaldav/scheduling/implicit.py	2012-12-06 21:28:23 UTC (rev 10131)
@@ -211,6 +211,32 @@
         returnValue((self.action != "none", False,))
 
 
+    @inlineCallbacks
+    def testAttendeeEvent(self, request, resource, calendar):
+        """
+        Test the existing resource to see if it is an Attendee scheduling object resource.
+
+        @param request: the request object
+        @type request: L{Request}
+        @param resource: the existing resource to test
+        @type resource: L{Resource}
+        """
+
+        self.request = request
+        self.resource = resource
+        self.calendar = calendar
+        self.internal_request = False
+        self.action = "modify"
+
+        is_scheduling_object = (yield self.checkSchedulingObjectResource(resource))
+        if not is_scheduling_object:
+            returnValue(False)
+
+        yield self.checkImplicitState()
+
+        returnValue(self.state in ("attendee", "attendee-missing",))
+
+
     def checkValidOrganizer(self):
         """
         Make sure the ORGANIZER is allowed to do certain scheduling operations.
@@ -1051,7 +1077,7 @@
 
                 if not doITipReply:
                     log.debug("Implicit - attendee '%s' is updating UID: '%s' but change is not significant" % (self.attendee, self.uid))
-                    returnValue(None)
+                    returnValue(self.return_calendar)
                 log.debug("Attendee '%s' is allowed to update UID: '%s' with local organizer '%s'" % (self.attendee, self.uid, self.organizer))
 
             elif isinstance(self.organizerAddress, LocalCalendarUser):

Modified: CalendarServer/branches/users/cdaboo/managed-attachments/twistedcaldav/storebridge.py
===================================================================
--- CalendarServer/branches/users/cdaboo/managed-attachments/twistedcaldav/storebridge.py	2012-12-06 21:20:50 UTC (rev 10130)
+++ CalendarServer/branches/users/cdaboo/managed-attachments/twistedcaldav/storebridge.py	2012-12-06 21:28:23 UTC (rev 10131)
@@ -2082,7 +2082,7 @@
         # May need to add a location header
         addLocation(request, destination_uri)
 
-        storer = self.storeResource(request, parent, destination, destination_uri, destinationparent)
+        storer = self.storeResource(request, parent, destination, destination_uri, destinationparent, True, None)
         result = (yield storer.move())
         returnValue(result)
 
@@ -2097,7 +2097,7 @@
             return FORBIDDEN
 
 
-    def storeResource(self, request, parent, destination, destination_uri, destaination_parent):
+    def storeResource(self, request, parent, destination, destination_uri, destination_parent, hasSource, component):
         """
         Create the appropriate StoreXXX class for storing of data.
         """
@@ -2108,23 +2108,11 @@
     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
-            ))
+        component = self._componentFromStream((yield allDataFromStream(stream)))
+        result = (yield self.storeComponent(component))
+        returnValue(result)
 
-            # Re-initialize to get stuff setup again now we have no object
-            self._initializeWithObject(self._newStoreObject, self._newStoreParent)
 
-            returnValue(CREATED)
-
-
     @inlineCallbacks
     def storeComponent(self, component):
 
@@ -2195,7 +2183,28 @@
         returnValue(NO_CONTENT)
 
 
+    @inlineCallbacks
+    def preProcessManagedAttachments(self, calendar):
+        # If store object exists pass through, otherwise use underlying store ManagedAttachments object to determine changes
+        if self._newStoreObject:
+            copied, removed = (yield self._newStoreObject.updatingResourceCheckAttachments(calendar))
+        else:
+            copied = (yield self._newStoreParent.creatingResourceCheckAttachments(calendar))
+            removed = None
 
+        returnValue((copied, removed,))
+
+
+    @inlineCallbacks
+    def postProcessManagedAttachments(self, copied, removed):
+        # Pass through directly to store object
+        if copied:
+            yield self._newStoreObject.copyResourceAttachments(copied)
+        if removed:
+            yield self._newStoreObject.removeResourceAttachments(removed)
+
+
+
 class _MetadataProperty(object):
     """
     A python property which can be set either on a _newStoreObject or on some
@@ -2301,18 +2310,20 @@
                 raise HTTPError(PRECONDITION_FAILED)
 
 
-    def storeResource(self, request, parent, destination, destination_uri, destaination_parent):
+    def storeResource(self, request, parent, destination, destination_uri, destination_parent, hasSource, component, attachmentProcessingDone=False):
         return StoreCalendarObjectResource(
             request=request,
-            source=self,
-            source_uri=request.uri,
-            sourceparent=parent,
-            sourcecal=True,
-            deletesource=True,
+            source=self if hasSource else None,
+            source_uri=request.uri if hasSource else None,
+            sourceparent=parent if hasSource else None,
+            sourcecal=hasSource,
+            deletesource=hasSource,
             destination=destination,
             destination_uri=destination_uri,
-            destinationparent=destaination_parent,
+            destinationparent=destination_parent,
             destinationcal=True,
+            calendar=component,
+            attachmentProcessingDone=attachmentProcessingDone,
         )
 
 
@@ -2452,12 +2463,31 @@
                 filename = content_disposition.params["filename"]
             return content_type, filename
 
+        valid_preconditions = {
+            "attachment-add": "valid-attachment-add",
+            "attachment-update": "valid-attachment-update",
+            "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":
+
+            # Add an attachment property
             rids = _getRIDs()
             content_type, filename = _getContentInfo()
             try:
-                attachment, location = (yield self._newStoreObject.addAttachment(rids, content_type, filename, request.stream))
+                attachment, location = (yield self._newStoreObject.addAttachment(rids, content_type, filename, request.stream, calendar))
             except AttachmentStoreFailed:
                 raise HTTPError(ErrorResponse(
                     FORBIDDEN,
@@ -2471,23 +2501,13 @@
                     "Could not store the supplied attachment because user quota would be exceeded",
                 ))
 
-            # Look for Prefer header
-            if "return-representation" in request.headers.getHeader("prefer", {}):
-                result = (yield self.render(request))
-                result.code = OK
-                result.headers.setHeader("content-location", request.path)
-                result.headers.setHeader("location", location)
-            else:
-                result = Response(CREATED)
-                result.headers.setHeader("location", location)
-            result.headers.addRawHeader("Cal-Managed-ID", attachment.dropboxID())
-            returnValue(result)
+            post_result = Response(CREATED)
 
         elif action == "attachment-update":
             mid = _getMID()
             content_type, filename = _getContentInfo()
             try:
-                attachment, location = (yield self._newStoreObject.updateAttachment(mid, content_type, filename, request.stream))
+                attachment, location = (yield self._newStoreObject.updateAttachment(mid, content_type, filename, request.stream, calendar))
             except AttachmentStoreValidManagedID:
                 raise HTTPError(ErrorResponse(
                     FORBIDDEN,
@@ -2507,22 +2527,13 @@
                     "Could not store the supplied attachment because user quota would be exceeded",
                 ))
 
-            # Look for Prefer header
-            if "return-representation" in request.headers.getHeader("prefer", {}):
-                result = (yield self.render(request))
-                result.code = OK
-                result.headers.setHeader("content-location", request.path)
-            else:
-                result = Response(NO_CONTENT)
-                result.headers.setHeader("location", location)
-            result.headers.addRawHeader("Cal-Managed-ID", attachment.dropboxID())
-            returnValue(result)
+            post_result = Response(NO_CONTENT)
 
         elif action == "attachment-remove":
             rids = _getRIDs()
             mid = _getMID()
             try:
-                yield self._newStoreObject.removeAttachment(rids, mid)
+                yield self._newStoreObject.removeAttachment(rids, mid, calendar)
             except AttachmentStoreValidManagedID:
                 raise HTTPError(ErrorResponse(
                     FORBIDDEN,
@@ -2536,14 +2547,7 @@
                     "Could not remove the specified attachment",
                 ))
 
-            # Look for Prefer header
-            if "return-representation" in request.headers.getHeader("prefer", {}):
-                result = (yield self.render(request))
-                result.code = OK
-                result.headers.setHeader("content-location", request.path)
-            else:
-                result = Response(NO_CONTENT)
-            returnValue(result)
+            post_result = Response(NO_CONTENT)
 
         else:
             raise HTTPError(ErrorResponse(
@@ -2552,8 +2556,27 @@
                 "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
+        if "return-representation" in request.headers.getHeader("prefer", {}) and result.code / 100 == 2:
+            result = (yield self.render(request))
+            result.code = OK
+            result.headers.setHeader("content-location", request.path)
+        else:
+            result = post_result
+        if action == "attachment-add":
+            result.headers.setHeader("location", location)
+        if action in ("attachment-add", "attachment-update",):
+            result.headers.addRawHeader("Cal-Managed-ID", attachment.dropboxID())
+        returnValue(result)
 
+
+
 class AddressBookCollectionResource(_CommonHomeChildCollectionMixin, CalDAVResource):
     """
     Wrapper around a L{txdav.carddav.iaddressbook.IAddressBook}.
@@ -2725,18 +2748,19 @@
     vCard = _CommonObjectResource.component
 
 
-    def storeResource(self, request, parent, destination, destination_uri, destination_parent):
+    def storeResource(self, request, parent, destination, destination_uri, destination_parent, hasSource, component):
         return StoreAddressObjectResource(
             request=request,
-            source=self,
-            source_uri=request.uri,
-            sourceparent=parent,
-            sourceadbk=True,
-            deletesource=True,
+            source=self if hasSource else None,
+            source_uri=request.uri if hasSource else None,
+            sourceparent=parent if hasSource else None,
+            sourceadbk=hasSource,
+            deletesource=hasSource,
             destination=destination,
             destination_uri=destination_uri,
             destinationparent=destination_parent,
             destinationadbk=True,
+            vcard=component,
         )
 
 

Modified: CalendarServer/branches/users/cdaboo/managed-attachments/txdav/caldav/datastore/sql.py
===================================================================
--- CalendarServer/branches/users/cdaboo/managed-attachments/txdav/caldav/datastore/sql.py	2012-12-06 21:20:50 UTC (rev 10130)
+++ CalendarServer/branches/users/cdaboo/managed-attachments/txdav/caldav/datastore/sql.py	2012-12-06 21:28:23 UTC (rev 10131)
@@ -734,6 +734,16 @@
         return super(Calendar, self).unshare(ECALENDARTYPE)
 
 
+    def creatingResourceCheckAttachments(self, component):
+        """
+        When component data is created or changed we need to look for changes related to managed attachments.
+
+        @param component: the new calendar data
+        @type component: L{Component}
+        """
+        return CalendarObject.creatingResourceCheckAttachments(self._txn, self, component)
+
+
 icalfbtype_to_indexfbtype = {
     "UNKNOWN"         : 0,
     "FREE"            : 1,
@@ -782,8 +792,6 @@
         self.scheduleEtags = metadata.get("scheduleEtags", "")
         self.hasPrivateComment = metadata.get("hasPrivateComment", False)
 
-        self._cachedComponent = None
-
     _allColumns = [
         _objectSchema.RESOURCE_ID,
         _objectSchema.RESOURCE_NAME,
@@ -833,18 +841,12 @@
 
 
     @inlineCallbacks
-    def setComponent(self, component, inserting=False, attachmentsAlreadyProcessed=False):
+    def setComponent(self, component, inserting=False):
 
         validateCalendarComponent(self, self._calendar, component, inserting, self._txn._migrating)
 
-        if not attachmentsAlreadyProcessed:
-            yield self._preProcessAttachmentsOnResourceChange(component, inserting)
-
         yield self.updateDatabase(component, inserting=inserting)
 
-        if not attachmentsAlreadyProcessed:
-            yield self._postProcessAttachmentsOnResourceChange()
-
         if inserting:
             yield self._calendar._insertRevision(self._name)
         else:
@@ -960,7 +962,6 @@
         if not reCreate:
             componentText = str(component)
             self._objectText = componentText
-            self._cachedComponent = None
             organizer = component.getOrganizer()
             if not organizer:
                 organizer = ""
@@ -1034,9 +1035,11 @@
                         Where=tr.CALENDAR_OBJECT_RESOURCE_ID == self._resourceID
                     ).on(txn)
         else:
+            # Keep MODIFIED the same when doing an index-only update
             values = {
                 co.RECURRANCE_MIN : pyCalendarTodatetime(normalizeForIndex(recurrenceLowerLimit)) if recurrenceLowerLimit else None,
                 co.RECURRANCE_MAX : pyCalendarTodatetime(normalizeForIndex(recurrenceLimit)) if recurrenceLimit else None,
+                co.MODIFIED : self._modified,
             }
 
             yield Update(
@@ -1140,34 +1143,31 @@
         only allowed in good data.
         """
 
-        if self._cachedComponent is None:
-            text = yield self._text()
+        text = yield self._text()
 
-            try:
-                component = VComponent.fromString(text)
-            except InvalidICalendarDataError, e:
-                # This is a really bad situation, so do raise
-                raise InternalDataStoreError(
-                    "Data corruption detected (%s) in id: %s"
-                    % (e, self._resourceID)
-                )
+        try:
+            component = VComponent.fromString(text)
+        except InvalidICalendarDataError, e:
+            # This is a really bad situation, so do raise
+            raise InternalDataStoreError(
+                "Data corruption detected (%s) in id: %s"
+                % (e, self._resourceID)
+            )
 
-            # Fix any bogus data we can
-            fixed, unfixed = component.validCalendarData(doFix=True, doRaise=False)
+        # Fix any bogus data we can
+        fixed, unfixed = component.validCalendarData(doFix=True, doRaise=False)
 
-            if unfixed:
-                self.log_error("Calendar data id=%s had unfixable problems:\n  %s" %
-                               (self._resourceID, "\n  ".join(unfixed),))
+        if unfixed:
+            self.log_error("Calendar data id=%s had unfixable problems:\n  %s" %
+                           (self._resourceID, "\n  ".join(unfixed),))
 
-            if fixed:
-                self.log_error("Calendar data id=%s had fixable problems:\n  %s" %
-                               (self._resourceID, "\n  ".join(fixed),))
+        if fixed:
+            self.log_error("Calendar data id=%s had fixable problems:\n  %s" %
+                           (self._resourceID, "\n  ".join(fixed),))
 
-            self._cachedComponent = component
+        returnValue(component)
 
-        returnValue(self._cachedComponent)
 
-
     @inlineCallbacks
     def remove(self):
         # Need to also remove attachments
@@ -1320,20 +1320,10 @@
             self._copyAttachments, self._removeAttachments = (yield self.updatingResourceCheckAttachments(component))
 
 
+    @classmethod
     @inlineCallbacks
-    def _postProcessAttachmentsOnResourceChange(self):
+    def creatingResourceCheckAttachments(cls, txn, parent, component):
         """
-        When component data has been created or changed we need to update managed attachment references to the resource.
-        """
-        if self._copyAttachments:
-            yield self.copyResourceAttachments(self._copyAttachments)
-        if self._removeAttachments:
-            yield self.removeResourceAttachments(self._removeAttachments)
-
-
-    @inlineCallbacks
-    def creatingResourceCheckAttachments(self, component):
-        """
         A new component is going to be stored. Check any ATTACH properties that may be present
         to verify they owned by the organizer/owner of the resource and re-write the managed-ids.
 
@@ -1353,7 +1343,7 @@
         if len(attached) == 0:
             returnValue(None)
 
-        changes = yield self._addingManagedIDs(attached)
+        changes = yield cls._addingManagedIDs(txn, parent, attached, component.resourceUID())
         returnValue(changes)
 
 
@@ -1400,7 +1390,7 @@
         for managed_id in added:
             changed[managed_id] = newattached[managed_id]
 
-        changes = yield self._addingManagedIDs(changed)
+        changes = yield self._addingManagedIDs(self._txn, self._parentCollection, changed, component.resourceUID())
 
         # Make sure existing data is not changed
         same = oldattached_keys & newattached_keys
@@ -1418,42 +1408,81 @@
         returnValue((changes, removed,))
 
 
+    @classmethod
     @inlineCallbacks
-    def _addingManagedIDs(self, attached):
+    def _addingManagedIDs(cls, txn, parent, attached, newuid):
         # Now check each managed-id
         changes = []
         for managed_id, attachments in attached.items():
 
             # Must be in the same home as this resource
-            hids = (yield ManagedAttachment.homeForManagedID(self._txn, managed_id))
-            if len(hids) == 0:
+            details = (yield ManagedAttachment.usedManagedID(txn, managed_id))
+            if len(details) == 0:
                 raise AttachmentStoreValidManagedID
-            if len(hids) != 1:
+            if len(details) != 1:
                 # This is a bad store error - there should be only one home associated with a managed-id
                 raise InternalDataStoreError
-            if hids[0] != self._parentCollection.ownerHome()._resourceID:
-                raise AttachmentStoreValidManagedID
+            home_id, _ignore_resource_id, uid = details[0]
 
-            # Need to rewrite the managed-id, value in the properties
-            new_id = str(uuid.uuid4())
-            changes.append((managed_id, new_id,))
-            location = self._txn._store.attachmentsURIPattern % {
-                "home": self._parentCollection.ownerHome().name(),
-                "name": new_id,
-            }
-            original_attachment = (yield ManagedAttachment.load(self._txn, managed_id))
-            for attachment in attachments:
-                attachment.setParameter("MANAGED-ID", new_id)
-                attachment.setParameter("MTAG", original_attachment.md5())
-                attachment.setParameter("FMTTYPE", "%s/%s" % (original_attachment.contentType().mediaType, original_attachment.contentType().mediaSubtype))
-                attachment.setParameter("FILENAME", original_attachment.name())
-                attachment.setParameter("SIZE", str(original_attachment.size()))
-                attachment.setValue(location)
+            # Policy:
+            #
+            # 1. If Managed-ID is re-used in a resource with the same UID - it is fine - just rewrite the details
+            # 2. If Managed-ID is re-used in a different resource but owned by the same user - change managed-id to new one
+            # 3. Otherwise, strip off the managed-id property and treat as unmanaged.
 
+            # 1. UID check
+            if uid == newuid:
+                yield cls._syncAttachmentProperty(txn, managed_id, attachments)
+
+            # 2. Same home
+            elif home_id == parent.ownerHome()._resourceID:
+
+                # Need to rewrite the managed-id, value in the properties
+                new_id = str(uuid.uuid4())
+                yield cls._syncAttachmentProperty(txn, managed_id, attachments, new_id)
+                changes.append((managed_id, new_id,))
+
+            else:
+                cls._stripAttachmentProperty(attachments)
+
         returnValue(changes)
 
 
+    @classmethod
     @inlineCallbacks
+    def _syncAttachmentProperty(cls, txn, managed_id, attachments, new_id=None):
+        """
+        Make sure the supplied set of attach properties are all sync'd with the current value of the
+        matching managed-id attachment.
+
+        @param managed_id: Managed-Id to sync with
+        @type managed_id: C{str}
+        @param attachments: list of attachment properties
+        @type attachments: C{list} of L{twistedcaldav.ical.Property}
+        @param new_id: Value of new Managed-ID to use
+        @type new_id: C{str}
+        """
+        original_attachment = (yield ManagedAttachment.load(txn, managed_id))
+        for attachment in attachments:
+            attachment.setParameter("MANAGED-ID", managed_id if new_id is None else new_id)
+            attachment.setParameter("MTAG", original_attachment.md5())
+            attachment.setParameter("FMTTYPE", "%s/%s" % (original_attachment.contentType().mediaType, original_attachment.contentType().mediaSubtype))
+            attachment.setParameter("FILENAME", original_attachment.name())
+            attachment.setParameter("SIZE", str(original_attachment.size()))
+            attachment.setValue((yield original_attachment.location(new_id)))
+
+
+    @classmethod
+    def _stripAttachmentProperty(cls, attachments):
+        """
+        Strip off managed-id related properties from an attachment.
+        """
+        for attachment in attachments:
+            attachment.removeParameter("MANAGED-ID")
+            attachment.removeParameter("MTAG")
+
+
+    @inlineCallbacks
     def copyResourceAttachments(self, attached):
         """
         Copy an attachment reference for some other resource and link it to this resource.
@@ -1478,7 +1507,7 @@
 
 
     @inlineCallbacks
-    def addAttachment(self, rids, content_type, filename, stream):
+    def addAttachment(self, rids, content_type, filename, stream, calendar):
 
         # First write the data stream
 
@@ -1494,12 +1523,9 @@
         yield t.loseConnection()
 
         # Now try and adjust the actual calendar data
-        calendar = (yield self.component())
+        #calendar = (yield self.component())
 
-        location = self._txn._store.attachmentsURIPattern % {
-            "home": self._parentCollection.ownerHome().name(),
-            "name": attachment.managedID(),
-        }
+        location = (yield attachment.location())
         attach = Property("ATTACH", location, params={
             "MANAGED-ID": attachment.managedID(),
             "MTAG": attachment.md5(),
@@ -1513,14 +1539,14 @@
             # TODO - per-recurrence attachments
             pass
 
-        # Store the data
-        yield self.setComponent(calendar, attachmentsAlreadyProcessed=True)
+        # 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)
 
         returnValue((attachment, location,))
 
 
     @inlineCallbacks
-    def updateAttachment(self, managed_id, content_type, filename, stream):
+    def updateAttachment(self, managed_id, content_type, filename, stream, calendar):
 
         # First check the supplied managed-id is associated with this resource
         cobjs = (yield ManagedAttachment.referencesTo(self._txn, managed_id))
@@ -1549,7 +1575,7 @@
         yield t.loseConnection()
 
         # Now try and adjust the actual calendar data
-        calendar = (yield self.component())
+        #calendar = (yield self.component())
 
         location = self._txn._store.attachmentsURIPattern % {
             "home": self._parentCollection.ownerHome().name(),
@@ -1564,14 +1590,14 @@
         }, valuetype=PyCalendarValue.VALUETYPE_URI)
         calendar.replaceAllPropertiesWithParameterMatch(attach, "MANAGED-ID", managed_id)
 
-        # Store the data
-        yield self.setComponent(calendar, attachmentsAlreadyProcessed=True)
+        # 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)
 
         returnValue((attachment, location,))
 
 
     @inlineCallbacks
-    def removeAttachment(self, rids, managed_id):
+    def removeAttachment(self, rids, managed_id, calendar):
 
         # First check the supplied managed-id is associated with this resource
         cobjs = (yield ManagedAttachment.referencesTo(self._txn, managed_id))
@@ -1580,7 +1606,7 @@
 
         # Now try and adjust the actual calendar data
         all_removed = False
-        calendar = (yield self.component())
+        #calendar = (yield self.component())
         if rids is None:
             calendar.removeAllPropertiesWithParameterMatch("ATTACH", "MANAGED-ID", managed_id)
             all_removed = True
@@ -1588,8 +1614,8 @@
             # TODO: per-recurrence removal
             pass
 
-        # Store the data
-        yield self.setComponent(calendar, attachmentsAlreadyProcessed=True)
+        # 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)
 
         # Remove it - this will take care of actually removing it from the store if there are
         # no more references to the attachment
@@ -1631,7 +1657,8 @@
     @inlineCallbacks
     def removeManagedAttachmentWithID(self, managed_id):
         attachment = (yield self.attachmentWithManagedID(managed_id))
-        yield attachment.removeFromResource(self._resourceID)
+        if attachment._objectResourceID == self._resourceID:
+            yield attachment.removeFromResource(self._resourceID)
 
 
     @inlineCallbacks
@@ -2257,19 +2284,25 @@
 
     @classmethod
     @inlineCallbacks
-    def homeForManagedID(cls, txn, managedID):
+    def usedManagedID(cls, txn, managedID):
         """
-        Find all the calendar object resourceIds referenced by this supplied managed-id.
+        Return the "owner" home and referencing resource is, and UID for a managed-id.
         """
         att = schema.ATTACHMENT
         attco = schema.ATTACHMENT_CALENDAR_OBJECT
+        co = schema.CALENDAR_OBJECT
         rows = (yield Select(
-            [att.CALENDAR_HOME_RESOURCE_ID, ],
-            From=att.join(attco, att.ATTACHMENT_ID == attco.ATTACHMENT_ID, "left outer"),
+            [
+                att.CALENDAR_HOME_RESOURCE_ID,
+                attco.CALENDAR_OBJECT_RESOURCE_ID,
+                co.ICALENDAR_UID,
+            ],
+            From=att.join(
+                attco, att.ATTACHMENT_ID == attco.ATTACHMENT_ID, "left outer"
+            ).join(co, co.RESOURCE_ID == attco.CALENDAR_OBJECT_RESOURCE_ID),
             Where=(attco.MANAGED_ID == managedID),
         ).on(txn))
-        hids = set([row[0] for row in rows]) if rows is not None else set()
-        returnValue(tuple(hids))
+        returnValue(rows)
 
 
     @classmethod
@@ -2338,6 +2371,22 @@
 
 
     @inlineCallbacks
+    def location(self, new_id=None):
+        """
+        Return the URI location of the attachment. Use a different managed-id if one is passed in. That is used
+        when creating a reference to an existing attachment via a new Managed-ID.
+        """
+        if not hasattr(self, "_ownerName"):
+            home = (yield self._txn.calendarHomeWithResourceID(self._ownerHomeID))
+            self._ownerName = home.name()
+        location = self._txn._store.attachmentsURIPattern % {
+            "home": self._ownerName,
+            "name": self._managedID if new_id is None else new_id,
+        }
+        returnValue(location)
+
+
+    @inlineCallbacks
     def changed(self, contentType, dispositionName, md5, size):
         """
         Always update name to current disposition name.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20121206/ccddbe09/attachment-0001.html>


More information about the calendarserver-changes mailing list