[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