[CalendarServer-changes] [10304] CalendarServer/trunk
source_changes at macosforge.org
source_changes at macosforge.org
Tue Jan 15 07:17:47 PST 2013
Revision: 10304
http://trac.calendarserver.org//changeset/10304
Author: cdaboo at apple.com
Date: 2013-01-15 07:17:47 -0800 (Tue, 15 Jan 2013)
Log Message:
-----------
Managed attachments changed to support dropbox compatibility. Note this turns off regular dropbox in both -test and -apple plists.
Modified Paths:
--------------
CalendarServer/trunk/calendarserver/tap/util.py
CalendarServer/trunk/calendarserver/tools/managetimezones.py
CalendarServer/trunk/conf/caldavd-apple.plist
CalendarServer/trunk/conf/caldavd-test.plist
CalendarServer/trunk/twistedcaldav/directory/principal.py
CalendarServer/trunk/twistedcaldav/method/propfind.py
CalendarServer/trunk/twistedcaldav/resource.py
CalendarServer/trunk/twistedcaldav/storebridge.py
CalendarServer/trunk/txdav/caldav/datastore/sql.py
Modified: CalendarServer/trunk/calendarserver/tap/util.py
===================================================================
--- CalendarServer/trunk/calendarserver/tap/util.py 2013-01-15 15:00:32 UTC (rev 10303)
+++ CalendarServer/trunk/calendarserver/tap/util.py 2013-01-15 15:17:47 UTC (rev 10304)
@@ -238,7 +238,7 @@
uri = "https://%s:%s" % (config.ServerHostName, config.SSLPort,)
else:
uri = "http://%s:%s" % (config.ServerHostName, config.HTTPPort,)
- attachments_uri = uri + "/calendars/__uids__/%(home)s/attachments/%(name)s"
+ attachments_uri = uri + "/calendars/__uids__/%(home)s/dropbox/%(dropbox_id)s/%(name)s"
return CommonSQLDataStore(
txnFactory, notifierFactory,
FilePath(config.AttachmentsRoot), attachments_uri,
Modified: CalendarServer/trunk/calendarserver/tools/managetimezones.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/managetimezones.py 2013-01-15 15:00:32 UTC (rev 10303)
+++ CalendarServer/trunk/calendarserver/tools/managetimezones.py 2013-01-15 15:17:47 UTC (rev 10304)
@@ -76,7 +76,7 @@
t.extractall(zonedir)
print "Converting data at: %s" % (zonedir,)
startYear = 1800
- endYear = 2018
+ endYear = PyCalendarDateTime.getToday().getYear() + 10
PyCalendar.sProdID = "-//calendarserver.org//Zonal//EN"
zonefiles = "northamerica", "southamerica", "europe", "africa", "asia", "australasia", "antarctica", "etcetera", "backward"
parser = tzconvert()
Modified: CalendarServer/trunk/conf/caldavd-apple.plist
===================================================================
--- CalendarServer/trunk/conf/caldavd-apple.plist 2013-01-15 15:00:32 UTC (rev 10303)
+++ CalendarServer/trunk/conf/caldavd-apple.plist 2013-01-15 15:17:47 UTC (rev 10304)
@@ -485,6 +485,10 @@
<!-- Calendar Drop Box -->
<key>EnableDropBox</key>
+ <false/>
+
+ <!-- Calendar Managed Attachments -->
+ <key>EnableManagedAttachments</key>
<true/>
<!-- Private Events -->
Modified: CalendarServer/trunk/conf/caldavd-test.plist
===================================================================
--- CalendarServer/trunk/conf/caldavd-test.plist 2013-01-15 15:00:32 UTC (rev 10303)
+++ CalendarServer/trunk/conf/caldavd-test.plist 2013-01-15 15:17:47 UTC (rev 10304)
@@ -899,7 +899,7 @@
<!-- Calendar Drop Box -->
<key>EnableDropBox</key>
- <true/>
+ <false/>
<!-- Calendar Managed Attachments -->
<key>EnableManagedAttachments</key>
Modified: CalendarServer/trunk/twistedcaldav/directory/principal.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/principal.py 2013-01-15 15:00:32 UTC (rev 10303)
+++ CalendarServer/trunk/twistedcaldav/directory/principal.py 2013-01-15 15:17:47 UTC (rev 10304)
@@ -65,7 +65,7 @@
from twistedcaldav.directory.idirectory import IDirectoryService
from twistedcaldav.directory.wiki import getWikiACL
from twistedcaldav.extensions import DirectoryElement
-from twistedcaldav.extensions import ReadOnlyResourceMixIn, DAVPrincipalResource,\
+from twistedcaldav.extensions import ReadOnlyResourceMixIn, DAVPrincipalResource, \
DAVResourceWithChildrenMixin
from twistedcaldav.resource import CalendarPrincipalCollectionResource, CalendarPrincipalResource
from twistedcaldav.scheduling.cuaddress import normalizeCUAddr
@@ -97,6 +97,7 @@
returnValue(self.defaultAccessControlList())
+
# Converter methods for recordsMatchingFields()
#
# A DAV property can be associated with one of these converter methods,
@@ -109,6 +110,8 @@
return "recordType", DirectoryRecord.fromCUType(cuType)
+
+
def cuAddressConverter(origCUAddr):
""" Converts calendar user addresses to OD-compatible form """
@@ -149,29 +152,36 @@
self.directory = IDirectoryService(directory)
+
def __repr__(self):
return "<%s: %s %s>" % (self.__class__.__name__, self.directory, self._url)
+
def locateChild(self, req, segments):
child = self.getChild(segments[0])
if child is not None:
return (child, segments[1:])
- return (NotFoundResource(principalCollections=self.principalCollections()),())
+ return (NotFoundResource(principalCollections=self.principalCollections()), ())
+
def deadProperties(self):
if not hasattr(self, "_dead_properties"):
self._dead_properties = NonePropertyStore(self)
return self._dead_properties
+
def etag(self):
return succeed(None)
+
def principalForShortName(self, recordType, name):
return self.principalForRecord(self.directory.recordWithShortName(recordType, name))
+
def principalForUser(self, user):
return self.principalForShortName(DirectoryService.recordType_users, user)
+
def principalForAuthID(self, user):
# Basic/Digest creds -> just lookup user name
if isinstance(user, UsernamePassword) or isinstance(user, DigestedCredentials):
@@ -183,15 +193,18 @@
return principal
elif user.username:
return self.principalForUser(user.username)
-
+
return None
+
def principalForUID(self, uid):
raise NotImplementedError("Subclass must implement principalForUID()")
+
def principalForCalendarUserAddress(self, address):
raise NotImplementedError("Subclass must implement principalForCalendarUserAddress()")
+
def principalForRecord(self, record):
if record is None or not record.enabled:
return None
@@ -220,6 +233,7 @@
customxml.EmailAddressSet),
}
+
def propertyToField(self, property, match):
"""
If property is a DAV property that maps to a directory field, return
@@ -233,6 +247,7 @@
field, match = converter(match)
return (field, match)
+
def principalSearchPropertySet(self):
props = []
for _ignore_field, _ignore_converter, description, xmlClass in self._fieldMap.itervalues():
@@ -243,7 +258,7 @@
),
davxml.Description(
davxml.PCDATAElement(description),
- **{"xml:lang":"en"}
+ **{"xml:lang": "en"}
),
)
)
@@ -251,6 +266,7 @@
return davxml.PrincipalSearchPropertySet(*props)
+
class DirectoryPrincipalProvisioningResource (DirectoryProvisioningResource):
"""
Collection resource which provisions directory principals as its children.
@@ -269,9 +285,11 @@
self.putChild(uidsResourceName, DirectoryPrincipalUIDProvisioningResource(self))
+
def principalForUID(self, uid):
return self.getChild(uidsResourceName).getChild(uid)
+
def _principalForURI(self, uri):
scheme, netloc, path, _ignore_params, _ignore_query, _ignore_fragment = urlparse(uri)
@@ -324,6 +342,7 @@
return None
+
def principalForCalendarUserAddress(self, address):
# First see if the address is a principal URI
principal = self._principalForURI(address)
@@ -339,9 +358,11 @@
log.debug("No principal for calendar user address: %r" % (address,))
return None
+
def principalForRecord(self, record):
return self.getChild(uidsResourceName).principalForRecord(record)
+
##
# Static
##
@@ -350,15 +371,18 @@
log.err("Attempt to create clone %r of resource %r" % (path, self))
raise HTTPError(responsecode.NOT_FOUND)
+
def getChild(self, name):
if name == "":
return self
else:
return self.putChildren.get(name, None)
+
def listChildren(self):
return self.directory.recordTypes()
+
##
# ACL
##
@@ -367,6 +391,7 @@
return (self,)
+
class DirectoryPrincipalTypeProvisioningResource (DirectoryProvisioningResource):
"""
Collection resource which provisions directory principals of a
@@ -386,12 +411,15 @@
self.recordType = recordType
self.parent = parent
+
def principalForUID(self, uid):
return self.parent.principalForUID(uid)
+
def principalForCalendarUserAddress(self, address):
return self.parent.principalForCalendarUserAddress(address)
+
def principalForRecord(self, record):
return self.parent.principalForRecord(record)
@@ -399,19 +427,23 @@
# Static
##
+
def createSimilarFile(self, path):
log.err("Attempt to create clone %r of resource %r" % (path, self))
raise HTTPError(responsecode.NOT_FOUND)
+
def getChild(self, name):
if name == "":
return self
else:
return self.principalForShortName(self.recordType, name)
+
def listChildren(self):
if config.EnablePrincipalListings:
+
def _recordShortnameExpand():
for record in self.directory.listRecords(self.recordType):
if record.enabled:
@@ -427,10 +459,12 @@
# ACL
##
+
def principalCollections(self):
return self.parent.principalCollections()
+
class DirectoryPrincipalUIDProvisioningResource (DirectoryProvisioningResource):
"""
Collection resource which provisions directory principals indexed
@@ -449,12 +483,15 @@
self.parent = parent
+
def principalForUID(self, uid):
return self.parent.principalForUID(uid)
+
def principalForCalendarUserAddress(self, address):
return self.parent.principalForCalendarUserAddress(address)
+
def principalForRecord(self, record):
if record is None or not record.enabled:
return None
@@ -471,10 +508,12 @@
# Static
##
+
def createSimilarFile(self, path):
log.err("Attempt to create clone %r of resource %r" % (path, self))
raise HTTPError(responsecode.NOT_FOUND)
+
def getChild(self, name):
if name == "":
return self
@@ -497,6 +536,7 @@
else:
return primaryPrincipal.getChild(subType)
+
def listChildren(self):
# Not a listable collection
raise HTTPError(responsecode.FORBIDDEN)
@@ -505,6 +545,7 @@
# ACL
##
+
def principalCollections(self):
return self.parent.principalCollections()
@@ -520,6 +561,7 @@
"directory-principal-resource.html").open()
)
+
def __init__(self, resource):
super(DirectoryPrincipalDetailElement, self).__init__()
self.resource = resource
@@ -612,6 +654,7 @@
return DirectoryPrincipalDetailElement(self.resource)
+
class DirectoryCalendarPrincipalDetailElement(DirectoryPrincipalDetailElement):
@renderer
@@ -671,6 +714,7 @@
return DirectoryCalendarPrincipalDetailElement(self.resource)
+
class DirectoryPrincipalResource (
PropfindCacheMixin, PermissionsMixIn, DAVPrincipalResource):
"""
@@ -678,16 +722,17 @@
"""
def liveProperties(self):
-
+
return super(DirectoryPrincipalResource, self).liveProperties() + (
- (calendarserver_namespace, "first-name" ),
- (calendarserver_namespace, "last-name" ),
+ (calendarserver_namespace, "first-name"),
+ (calendarserver_namespace, "last-name"),
(calendarserver_namespace, "email-address-set"),
davxml.ResourceID.qname(),
)
cacheNotifierFactory = DisabledCacheNotifier
+
def __init__(self, parent, record):
"""
@param parent: the parent of this resource.
@@ -704,32 +749,36 @@
assert record is not None, "Principal must have a directory record"
-
self.record = record
self.parent = parent
url = joinURL(parent.principalCollectionURL(), self.principalUID()) + slash
- self._url = url
+ self._url = url
self._alternate_urls = tuple([
joinURL(parent.parent.principalCollectionURL(), record.recordType, shortName) + slash for shortName in record.shortNames
])
+
def __str__(self):
return "(%s)%s" % (self.record.recordType, self.record.shortNames[0])
+
def __eq__(self, other):
"""
Principals are the same if their principalURLs are the same.
"""
return (self.principalURL() == other.principalURL()) if isinstance(other, DirectoryPrincipalResource) else False
+
def __ne__(self, other):
return not self.__eq__(other)
+
def __hash__(self):
return hash(self.principalUID())
+
@inlineCallbacks
def readProperty(self, property, request):
if type(property) is tuple:
@@ -764,11 +813,13 @@
result = (yield super(DirectoryPrincipalResource, self).readProperty(property, request))
returnValue(result)
+
def deadProperties(self):
if not hasattr(self, "_dead_properties"):
self._dead_properties = NonePropertyStore(self)
return self._dead_properties
+
def etag(self):
return succeed(None)
@@ -776,6 +827,7 @@
# HTTP
##
+
def htmlElement(self):
"""
Customize HTML rendering for directory principals.
@@ -786,9 +838,11 @@
# DAV
##
+
def isCollection(self):
return True
+
def displayName(self):
if self.record.fullName:
return self.record.fullName
@@ -799,6 +853,7 @@
# ACL
##
+
def _calendar_user_proxy_index(self):
"""
Return the SQL database for calendar user proxies.
@@ -815,19 +870,22 @@
# FIXME: Add API to IDirectoryRecord for getting a record URI?
return self._alternate_urls
+
def principalURL(self):
return self._url
+
def url(self):
return self.principalURL()
+
@inlineCallbacks
def isProxyFor(self, principal):
"""
Determine whether this principal is a read-only or read-write proxy for the
- specified principal.
+ specified principal.
"""
-
+
read_uids = (yield self.proxyFor(False))
if principal in read_uids:
returnValue(True)
@@ -835,9 +893,10 @@
write_uids = (yield self.proxyFor(True))
if principal in write_uids:
returnValue(True)
-
+
returnValue(False)
+
@inlineCallbacks
def proxyFor(self, read_write, resolve_memberships=True):
@@ -883,6 +942,7 @@
returnValue(proxyFors)
+
def _getRelatives(self, method, record=None, relatives=None, records=None, proxy=None, infinity=False):
if record is None:
record = self.record
@@ -912,12 +972,15 @@
return relatives
+
def groupMembers(self):
return succeed(self._getRelatives("members"))
+
def expandedGroupMembers(self):
return succeed(self._getRelatives("members", infinity=True))
+
@inlineCallbacks
def groupMemberships(self, infinity=False):
@@ -948,9 +1011,11 @@
returnValue(groups)
+
def expandedGroupMemberships(self):
return self.groupMemberships(infinity=True)
+
def groupsChanged(self):
"""
A callback indicating the directory group membership for this principal
@@ -959,27 +1024,35 @@
"""
return self.cacheNotifier.changed()
+
def principalCollections(self):
return self.parent.principalCollections()
+
def principalUID(self):
return self.record.uid
+
def serverURI(self):
return self.record.serverURI()
+
def server(self):
return self.record.server()
+
def partitionURI(self):
return self.record.partitionURI()
+
def locallyHosted(self):
return self.record.locallyHosted()
-
+
+
def thisServer(self):
return self.record.thisServer()
-
+
+
##
# Extra resource info
##
@@ -991,9 +1064,11 @@
augmentRecord.autoSchedule = autoSchedule
(yield self.record.service.augmentService.addAugmentRecords([augmentRecord]))
+
def getAutoSchedule(self):
return self.record.autoSchedule
+
def canAutoSchedule(self, organizer=None):
"""
Determine the auto-schedule state based on record state, type and config settings.
@@ -1011,6 +1086,7 @@
return True
return False
+
@inlineCallbacks
def setAutoScheduleMode(self, autoScheduleMode):
self.record.autoScheduleMode = autoScheduleMode if autoScheduleMode in allowedAutoScheduleModes else "default"
@@ -1018,6 +1094,7 @@
augmentRecord.autoScheduleMode = autoScheduleMode
(yield self.record.service.augmentService.addAugmentRecords([augmentRecord]))
+
def getAutoScheduleMode(self, organizer=None):
"""
Return the auto schedule mode value for the principal. If the optional
@@ -1053,6 +1130,7 @@
augmentRecord.autoAcceptGroup = autoAcceptGroup
(yield self.record.service.augmentService.addAugmentRecords([augmentRecord]))
+
def getAutoAcceptGroup(self):
"""
Returns the GUID of the auto accept group assigned to this principal, or empty
@@ -1060,6 +1138,7 @@
"""
return self.record.autoAcceptGroup
+
def autoAcceptFromOrganizer(self, organizer):
"""
Is the organizer a member of this principal's autoAcceptGroup?
@@ -1077,6 +1156,7 @@
return True
return False
+
def getCUType(self):
return self.record.getCUType()
@@ -1084,26 +1164,31 @@
# Static
##
+
def createSimilarFile(self, path):
log.err("Attempt to create clone %r of resource %r" % (path, self))
raise HTTPError(responsecode.NOT_FOUND)
+
def locateChild(self, req, segments):
child = self.getChild(segments[0])
if child is not None:
return (child, segments[1:])
return (None, ())
+
def getChild(self, name):
if name == "":
return self
return None
+
def listChildren(self):
return ()
+
class DirectoryCalendarPrincipalResource(DirectoryPrincipalResource,
CalendarPrincipalResource):
"""
@@ -1113,12 +1198,15 @@
def liveProperties(self):
return DirectoryPrincipalResource.liveProperties(self) + CalendarPrincipalResource.liveProperties(self)
+
def calendarsEnabled(self):
return config.EnableCalDAV and self.record.enabledForCalendaring
-
+
+
def addressBooksEnabled(self):
return config.EnableCardDAV and self.record.enabledForAddressBooks
-
+
+
@inlineCallbacks
def readProperty(self, property, request):
# Ouch, multiple inheritance.
@@ -1132,6 +1220,7 @@
# CalDAV
##
+
def calendarUserAddresses(self):
# No CUAs if not enabledForCalendaring.
@@ -1190,6 +1279,7 @@
else:
return False
+
@inlineCallbacks
def scheduleInbox(self, request):
home = yield self.calendarHome(request)
@@ -1202,16 +1292,18 @@
returnValue(inbox)
+
@inlineCallbacks
def notificationCollection(self, request):
notification = None
if config.Sharing.Enabled:
home = yield self.calendarHome(request)
- if home is not None:
+ if home is not None:
notification = yield home.getChild("notification")
returnValue(notification)
+
def calendarHomeURLs(self):
if self.record.enabledForCalendaring:
homeURL = self._homeChildURL(None)
@@ -1219,24 +1311,29 @@
homeURL = ""
return (homeURL,) if homeURL else ()
+
def scheduleInboxURL(self):
return self._homeChildURL("inbox/")
+
def scheduleOutboxURL(self):
return self._homeChildURL("outbox/")
+
def dropboxURL(self):
- if config.EnableDropBox:
+ if config.EnableDropBox or config.EnableManagedAttachments:
return self._homeChildURL("dropbox/")
else:
return None
+
def notificationURL(self):
if config.Sharing.Enabled:
return self._homeChildURL("notification/")
else:
return None
+
def addressBookHomeURLs(self):
if self.record.enabledForAddressBooks:
homeURL = self._addressBookHomeChildURL(None)
@@ -1244,6 +1341,7 @@
homeURL = ""
return (homeURL,) if homeURL else ()
+
def _homeChildURL(self, name):
if not hasattr(self, "calendarHomeURL"):
if not hasattr(self.record.service, "calendarHomesCollection"):
@@ -1253,7 +1351,7 @@
uidsResourceName,
self.record.uid
) + "/"
-
+
# Prefix with other server if needed
if not self.thisServer():
self.calendarHomeURL = joinURL(self.serverURI(), self.calendarHomeURL)
@@ -1283,7 +1381,7 @@
uidsResourceName,
self.record.uid
) + "/"
-
+
# Prefix with other server if needed
if not self.thisServer():
self.addressBookHomeURL = joinURL(self.serverURI(), self.addressBookHomeURL)
@@ -1294,6 +1392,7 @@
else:
return joinURL(url, name) if name else url
+
def addressBookHome(self, request):
# FIXME: self.record.service.addressBookHomesCollection smells like a hack
service = self.record.service
@@ -1306,6 +1405,7 @@
# Static
##
+
def getChild(self, name):
if name == "":
return self
@@ -1320,6 +1420,7 @@
else:
return None
+
def listChildren(self):
if config.EnableProxyPrincipals:
return ("calendar-proxy-read", "calendar-proxy-write")
@@ -1340,6 +1441,7 @@
)
+
def formatPrincipals(principals):
"""
Format a list of principals into some twisted.web.template DOM objects.
@@ -1354,6 +1456,7 @@
return None
return (record.recordType, record.shortNames[0])
+
def describe(principal):
if hasattr(principal, "record"):
return " - %s" % (principal.record.fullName,)
@@ -1368,6 +1471,7 @@
)
+
def formatList(iterable):
"""
Format a list of stuff as an interable.
@@ -1400,9 +1504,9 @@
return tags.a(href=url)(url)
+
def formatLinks(urls):
"""
Format a list of URL strings as a list of twisted.web.template DOM links.
"""
return formatList(formatLink(link) for link in urls)
-
Modified: CalendarServer/trunk/twistedcaldav/method/propfind.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/propfind.py 2013-01-15 15:00:32 UTC (rev 10303)
+++ CalendarServer/trunk/twistedcaldav/method/propfind.py 2013-01-15 15:17:47 UTC (rev 10304)
@@ -8,10 +8,10 @@
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
-#
+#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
-#
+#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -33,9 +33,9 @@
from twext.web2 import responsecode
from twext.web2.http import StatusResponse
from txdav.xml import element as davxml
-from twext.web2.dav.http import MultiStatusResponse, statusForFailure,\
+from twext.web2.dav.http import MultiStatusResponse, statusForFailure, \
ErrorResponse
-from twext.web2.dav.util import normalizeURL, davXMLFromStream
+from twext.web2.dav.util import normalizeURL, davXMLFromStream, parentForURL
from twext.python.log import Logger
@@ -52,6 +52,11 @@
Respond to a PROPFIND request. (RFC 2518, section 8.1)
"""
if not self.exists():
+ # Return 403 if parent does not allow Bind
+ parentURL = parentForURL(request.uri)
+ parent = (yield request.locateResource(parentURL))
+ yield parent.authorize(request, (davxml.Bind(),))
+
log.err("Resource not found: %s" % (self,))
raise HTTPError(responsecode.NOT_FOUND)
@@ -155,7 +160,7 @@
except:
log.err("Unable to get properties for resource %r" % (resource,))
raise
-
+
properties_by_status = {
responsecode.OK: [propertyName(p) for p in resource_properties]
}
@@ -164,12 +169,12 @@
responsecode.OK : [],
responsecode.NOT_FOUND : [],
}
-
+
if search_properties is "all":
properties_to_enumerate = (yield resource.listAllprop(request))
else:
properties_to_enumerate = search_properties
-
+
for property in properties_to_enumerate:
has = (yield resource.hasProperty(property, request))
if has:
@@ -191,17 +196,18 @@
properties_by_status[responsecode.NOT_FOUND].append(propertyName(property))
propstats = []
-
+
for status in properties_by_status:
properties = properties_by_status[status]
- if not properties: continue
-
- xml_status = davxml.Status.fromResponseCode(status)
+ if not properties:
+ continue
+
+ xml_status = davxml.Status.fromResponseCode(status)
xml_container = davxml.PropertyContainer(*properties)
- xml_propstat = davxml.PropertyStatus(xml_container, xml_status)
-
+ xml_propstat = davxml.PropertyStatus(xml_container, xml_status)
+
propstats.append(xml_propstat)
-
+
xml_response = davxml.PropertyStatusResponse(davxml.HRef(uri), *propstats)
# This needed for propfind cache tracking of children changes
@@ -210,9 +216,8 @@
request.childCacheURIs.append(resource.url())
else:
xml_response = davxml.StatusResponse(davxml.HRef(uri), davxml.Status.fromResponseCode(responsecode.FORBIDDEN))
-
+
xml_responses.append(xml_response)
-
if not hasattr(request, "extendedLogItems"):
request.extendedLogItems = {}
@@ -224,6 +229,7 @@
returnValue(MultiStatusResponse(xml_responses))
+
##
# Utilities
##
Modified: CalendarServer/trunk/twistedcaldav/resource.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/resource.py 2013-01-15 15:00:32 UTC (rev 10303)
+++ CalendarServer/trunk/twistedcaldav/resource.py 2013-01-15 15:17:47 UTC (rev 10304)
@@ -1874,7 +1874,7 @@
if self.directoryAddressBookEnabled():
baseProperties += (carddavxml.DirectoryGateway.qname(),)
- if config.EnableDropBox:
+ if config.EnableDropBox or config.EnableManagedAttachments:
baseProperties += (customxml.DropBoxHomeURL.qname(),)
if config.Sharing.Enabled:
@@ -1937,7 +1937,7 @@
returnValue(caldavxml.CalendarUserType(self.record.getCUType()))
elif namespace == calendarserver_namespace:
- if name == "dropbox-home-URL" and config.EnableDropBox:
+ if name == "dropbox-home-URL" and (config.EnableDropBox or config.EnableManagedAttachments):
url = self.dropboxURL()
if url is None:
returnValue(None)
@@ -2606,13 +2606,13 @@
from twistedcaldav.scheduling.caldav.resource import ScheduleOutboxResource
self._provisionedChildren["outbox"] = ScheduleOutboxResource
- if config.EnableDropBox:
+ if config.EnableDropBox and not config.EnableManagedAttachments:
from twistedcaldav.storebridge import DropboxCollection
self._provisionedChildren["dropbox"] = DropboxCollection
if config.EnableManagedAttachments:
from twistedcaldav.storebridge import AttachmentsCollection
- self._provisionedChildren["attachments"] = AttachmentsCollection
+ self._provisionedChildren["dropbox"] = AttachmentsCollection
if config.FreeBusyURL.Enabled:
from twistedcaldav.freebusyurl import FreeBusyURLResource
@@ -2774,7 +2774,7 @@
changed.append("notification/")
# Dropbox is never synchronized
- if config.EnableDropBox:
+ if config.EnableDropBox or config.EnableManagedAttachments:
notallowed.append("dropbox/")
# Add in notification changes
Modified: CalendarServer/trunk/twistedcaldav/storebridge.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/storebridge.py 2013-01-15 15:00:32 UTC (rev 10303)
+++ CalendarServer/trunk/twistedcaldav/storebridge.py 2013-01-15 15:17:47 UTC (rev 10304)
@@ -1384,6 +1384,10 @@
class NoDropboxHere(_GetChildHelper):
+ def getChild(self, name):
+ raise HTTPError(FORBIDDEN)
+
+
def isCollection(self):
return False
@@ -1627,7 +1631,8 @@
class AttachmentsCollection(_GetChildHelper):
"""
A collection of all managed attachments, presented as a
- resource under the user's calendar home.
+ resource under the user's calendar home. Attachments are stored
+ in L{AttachmentsChildCollection} child collections of this one.
"""
# FIXME: no direct tests for this class at all.
@@ -1648,24 +1653,29 @@
@inlineCallbacks
def getChild(self, name):
- attachmentObject = yield self._newStoreHome.attachmentObjectWithID(name)
- result = CalendarAttachment(
- None,
- attachmentObject,
- name,
- True,
- principalCollections=self.principalCollections()
+ calendarObject = yield self._newStoreHome.calendarObjectWithDropboxID(name)
+
+ # Hide the dropbox if it has no children
+ if calendarObject:
+ l = (yield calendarObject.managedAttachmentList())
+ if len(l) == 0:
+ calendarObject = None
+
+ if calendarObject is None:
+ returnValue(NoDropboxHere())
+ objectDropbox = AttachmentsChildCollection(
+ calendarObject, self, principalCollections=self.principalCollections()
)
- self.propagateTransaction(result)
- returnValue(result)
+ self.propagateTransaction(objectDropbox)
+ returnValue(objectDropbox)
def resourceType(self,):
- return davxml.ResourceType.collection # @UndefinedVariable
+ return davxml.ResourceType.dropboxhome # @UndefinedVariable
def listChildren(self):
- return self._newStoreHome.getAllAttachmentNames()
+ return self._newStoreHome.getAllDropboxIDs()
def supportedPrivileges(self, request):
@@ -1727,6 +1737,195 @@
+class AttachmentsChildCollection(_GetChildHelper):
+ """
+ A collection of all containers for attachments, presented as a
+ resource under the user's calendar home, where a dropbox is a
+ L{CalendarObjectDropbox}.
+ """
+ # FIXME: no direct tests for this class at all.
+
+ def __init__(self, calendarObject, parent, *a, **kw):
+ kw.update(principalCollections=parent.principalCollections())
+ super(AttachmentsChildCollection, self).__init__(*a, **kw)
+ self._newStoreCalendarObject = calendarObject
+ parent.propagateTransaction(self)
+
+
+ def isCollection(self):
+ """
+ It is a collection.
+ """
+ return True
+
+
+ @inlineCallbacks
+ def getChild(self, name):
+ attachmentObject = yield self._newStoreCalendarObject.managedAttachmentRetrieval(name)
+ result = CalendarAttachment(
+ None,
+ attachmentObject,
+ name,
+ True,
+ principalCollections=self.principalCollections()
+ )
+ self.propagateTransaction(result)
+ returnValue(result)
+
+
+ def resourceType(self,):
+ return davxml.ResourceType.dropbox # @UndefinedVariable
+
+
+ @inlineCallbacks
+ def listChildren(self):
+ l = (yield self._newStoreCalendarObject.managedAttachmentList())
+ returnValue(l)
+
+
+ @inlineCallbacks
+ def http_ACL(self, request):
+ # For managed attachment compatibility this is always forbidden as dropbox clients must never be
+ # allowed to store attachments or make any changes.
+ return FORBIDDEN
+
+
+ def http_MKCOL(self, request):
+ # For managed attachment compatibility this is always forbidden as dropbox clients must never be
+ # allowed to store attachments or make any changes.
+ return FORBIDDEN
+
+
+ @requiresPermissions(fromParent=[davxml.Unbind()])
+ def http_DELETE(self, request):
+ # For managed attachment compatibility this always succeeds as dropbox clients will do
+ # this but we don't want them to see an error. Managed attachments will always be cleaned
+ # up on removal of the actual calendar object resource.
+ return NO_CONTENT
+
+
+ @inlineCallbacks
+ def accessControlList(self, request, *a, **kw):
+ """
+ All principals identified as ATTENDEEs on the event for this dropbox
+ may read all its children. Also include proxies of ATTENDEEs. Ignore
+ unknown attendees. Do not allow attendees to write as we don't support
+ that with managed attachments.
+ """
+ originalACL = yield super(
+ AttachmentsChildCollection, self).accessControlList(request, *a, **kw)
+ originalACEs = list(originalACL.children)
+
+ if config.EnableProxyPrincipals:
+ owner = (yield self.ownerPrincipal(request))
+
+ originalACEs += (
+ # DAV:write-acl access for this principal's calendar-proxy-write users.
+ davxml.ACE(
+ davxml.Principal(davxml.HRef(joinURL(owner.principalURL(), "calendar-proxy-write/"))),
+ davxml.Grant(
+ davxml.Privilege(davxml.WriteACL()),
+ ),
+ davxml.Protected(),
+ TwistedACLInheritable(),
+ ),
+ )
+
+ cuas = (yield self._newStoreCalendarObject.component()).getAttendees()
+ newACEs = []
+ for calendarUserAddress in cuas:
+ principal = self.principalForCalendarUserAddress(
+ calendarUserAddress
+ )
+ if principal is None:
+ continue
+
+ principalURL = principal.principalURL()
+ privileges = [
+ davxml.Privilege(davxml.Read()),
+ davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()),
+ ]
+ newACEs.append(davxml.ACE(
+ davxml.Principal(davxml.HRef(principalURL)),
+ davxml.Grant(*privileges),
+ davxml.Protected(),
+ TwistedACLInheritable(),
+ ))
+ newACEs.append(davxml.ACE(
+ davxml.Principal(davxml.HRef(joinURL(principalURL, "calendar-proxy-write/"))),
+ davxml.Grant(*privileges),
+ davxml.Protected(),
+ TwistedACLInheritable(),
+ ))
+ newACEs.append(davxml.ACE(
+ davxml.Principal(davxml.HRef(joinURL(principalURL, "calendar-proxy-read/"))),
+ davxml.Grant(*privileges),
+ davxml.Protected(),
+ TwistedACLInheritable(),
+ ))
+
+ # Now also need invitees
+ newACEs.extend((yield self.sharedDropboxACEs()))
+
+ returnValue(davxml.ACL(*tuple(originalACEs + newACEs)))
+
+
+ @inlineCallbacks
+ def sharedDropboxACEs(self):
+
+ aces = ()
+ calendars = yield self._newStoreCalendarObject._parentCollection.asShared()
+ for calendar in calendars:
+
+ userprivs = [
+ ]
+ if calendar.shareMode() in (_BIND_MODE_READ, _BIND_MODE_WRITE,):
+ userprivs.append(davxml.Privilege(davxml.Read()))
+ userprivs.append(davxml.Privilege(davxml.ReadACL()))
+ userprivs.append(davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()))
+ if calendar.shareMode() in (_BIND_MODE_READ,):
+ userprivs.append(davxml.Privilege(davxml.WriteProperties()))
+ if calendar.shareMode() in (_BIND_MODE_WRITE,):
+ userprivs.append(davxml.Privilege(davxml.Write()))
+ proxyprivs = list(userprivs)
+ proxyprivs.remove(davxml.Privilege(davxml.ReadACL()))
+
+ principal = self.principalForUID(calendar._home.uid())
+ aces += (
+ # Inheritable specific access for the resource's associated principal.
+ davxml.ACE(
+ davxml.Principal(davxml.HRef(principal.principalURL())),
+ davxml.Grant(*userprivs),
+ davxml.Protected(),
+ TwistedACLInheritable(),
+ ),
+ )
+
+ if config.EnableProxyPrincipals:
+ aces += (
+ # DAV:read/DAV:read-current-user-privilege-set access for this principal's calendar-proxy-read users.
+ davxml.ACE(
+ davxml.Principal(davxml.HRef(joinURL(principal.principalURL(), "calendar-proxy-read/"))),
+ davxml.Grant(
+ davxml.Privilege(davxml.Read()),
+ davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()),
+ ),
+ davxml.Protected(),
+ TwistedACLInheritable(),
+ ),
+ # DAV:read/DAV:read-current-user-privilege-set/DAV:write access for this principal's calendar-proxy-write users.
+ davxml.ACE(
+ davxml.Principal(davxml.HRef(joinURL(principal.principalURL(), "calendar-proxy-write/"))),
+ davxml.Grant(*proxyprivs),
+ davxml.Protected(),
+ TwistedACLInheritable(),
+ ),
+ )
+
+ returnValue(aces)
+
+
+
class CalendarAttachment(_NewStoreFileMetaDataHelper, _GetChildHelper):
def __init__(self, calendarObject, attachment, attachmentName, managed, **kw):
@@ -2581,10 +2780,9 @@
result.headers.setHeader("content-location", request.path)
else:
result = post_result
- if action == "attachment-add":
+ if action in ("attachment-add", "attachment-update",):
result.headers.setHeader("location", location)
- if action in ("attachment-add", "attachment-update",):
- result.headers.addRawHeader("Cal-Managed-ID", attachment.dropboxID())
+ result.headers.addRawHeader("Cal-Managed-ID", attachment.managedID())
returnValue(result)
Modified: CalendarServer/trunk/txdav/caldav/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/sql.py 2013-01-15 15:00:32 UTC (rev 10303)
+++ CalendarServer/trunk/txdav/caldav/datastore/sql.py 2013-01-15 15:17:47 UTC (rev 10304)
@@ -69,10 +69,11 @@
PostgresLegacyInboxIndexEmulator
from txdav.common.datastore.sql_tables import CALENDAR_TABLE, \
CALENDAR_BIND_TABLE, CALENDAR_OBJECT_REVISIONS_TABLE, CALENDAR_OBJECT_TABLE, \
- _ATTACHMENTS_MODE_NONE, _ATTACHMENTS_MODE_READ, _ATTACHMENTS_MODE_WRITE, \
+ _ATTACHMENTS_MODE_NONE, _ATTACHMENTS_MODE_WRITE, \
CALENDAR_HOME_TABLE, CALENDAR_HOME_METADATA_TABLE, \
CALENDAR_AND_CALENDAR_BIND, CALENDAR_OBJECT_REVISIONS_AND_BIND_TABLE, \
- CALENDAR_OBJECT_AND_BIND_TABLE, schema, _BIND_MODE_OWN
+ CALENDAR_OBJECT_AND_BIND_TABLE, schema, _BIND_MODE_OWN, \
+ _ATTACHMENTS_MODE_READ
from txdav.common.icommondatastore import IndexedSearchException, \
InternalDataStoreError, HomeChildNameAlreadyExistsError, \
HomeChildNameNotAllowedError
@@ -408,6 +409,19 @@
@inlineCallbacks
+ def getAllManagedIDs(self):
+ at = schema.ATTACHMENT
+ attco = schema.ATTACHMENT_CALENDAR_OBJECT
+ rows = (yield Select(
+ [attco.MANAGED_ID, ],
+ From=attco.join(at, attco.ATTACHMENT_ID == at.ATTACHMENT_ID),
+ Where=at.CALENDAR_HOME_RESOURCE_ID == self._resourceID,
+ OrderBy=attco.MANAGED_ID
+ ).on(self._txn))
+ returnValue([row[0] for row in rows])
+
+
+ @inlineCallbacks
def attachmentObjectWithID(self, managedID):
attach = (yield ManagedAttachment.load(self._txn, managedID))
returnValue(attach)
@@ -890,6 +904,7 @@
self.scheduleTag = metadata.get("scheduleTag", "")
self.scheduleEtags = metadata.get("scheduleEtags", "")
self.hasPrivateComment = metadata.get("hasPrivateComment", False)
+ self._dropboxID = None
_allColumns = [
_objectSchema.RESOURCE_ID,
@@ -1077,18 +1092,18 @@
# Determine attachment mode (ignore inbox's) - NB we have to do this
# after setting up other properties as UID at least is needed
self._attachment = _ATTACHMENTS_MODE_NONE
- self._dropboxID = None
- if self._parentCollection.name() != "inbox":
- if component.hasPropertyInAnyComponent("X-APPLE-DROPBOX"):
- self._attachment = _ATTACHMENTS_MODE_WRITE
- self._dropboxID = (yield self.dropboxID())
- else:
- # Only include a dropbox id if dropbox attachments exist
- attachments = component.getAllPropertiesInAnyComponent("ATTACH")
- has_dropbox = any([attachment.value().find("/dropbox/") != -1 for attachment in attachments])
- if has_dropbox:
- self._attachment = _ATTACHMENTS_MODE_READ
+ if self._dropboxID is None:
+ if self._parentCollection.name() != "inbox":
+ if component.hasPropertyInAnyComponent("X-APPLE-DROPBOX"):
+ self._attachment = _ATTACHMENTS_MODE_WRITE
self._dropboxID = (yield self.dropboxID())
+ else:
+ # Only include a dropbox id if dropbox attachments exist
+ attachments = component.getAllPropertiesInAnyComponent("ATTACH")
+ has_dropbox = any([attachment.value().find("/dropbox/") != -1 for attachment in attachments])
+ if has_dropbox:
+ self._attachment = _ATTACHMENTS_MODE_READ
+ self._dropboxID = (yield self.dropboxID())
values = {
co.CALENDAR_RESOURCE_ID : self._calendar._resourceID,
@@ -1427,7 +1442,7 @@
def creatingResourceCheckAttachments(cls, txn, parent, 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.
+ to verify they are owned by the organizer/owner of the resource and re-write the managed-ids.
@param component: calendar component about to be stored
@type component: L{Component}
@@ -1445,7 +1460,7 @@
if len(attached) == 0:
returnValue(None)
- changes = yield cls._addingManagedIDs(txn, parent, attached, component.resourceUID())
+ changes = yield cls._addingManagedIDs(txn, parent, str(uuid.uuid4()), attached, component.resourceUID())
returnValue(changes)
@@ -1453,7 +1468,7 @@
def updatingResourceCheckAttachments(self, component):
"""
A component is being changed. 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.
+ to verify they are owned by the organizer/owner of the resource and re-write the managed-ids.
@param component: calendar component about to be stored
@type component: L{Component}
@@ -1492,7 +1507,7 @@
for managed_id in added:
changed[managed_id] = newattached[managed_id]
- changes = yield self._addingManagedIDs(self._txn, self._parentCollection, changed, component.resourceUID())
+ changes = yield self._addingManagedIDs(self._txn, self._parentCollection, self._dropboxID, changed, component.resourceUID())
# Make sure existing data is not changed
same = oldattached_keys & newattached_keys
@@ -1512,7 +1527,7 @@
@classmethod
@inlineCallbacks
- def _addingManagedIDs(cls, txn, parent, attached, newuid):
+ def _addingManagedIDs(cls, txn, parent, dropbox_id, attached, newuid):
# Now check each managed-id
changes = []
for managed_id, attachments in attached.items():
@@ -1534,14 +1549,14 @@
# 1. UID check
if uid == newuid:
- yield cls._syncAttachmentProperty(txn, managed_id, attachments)
+ yield cls._syncAttachmentProperty(txn, managed_id, dropbox_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)
+ yield cls._syncAttachmentProperty(txn, managed_id, dropbox_id, attachments, new_id)
changes.append((managed_id, new_id,))
else:
@@ -1552,7 +1567,7 @@
@classmethod
@inlineCallbacks
- def _syncAttachmentProperty(cls, txn, managed_id, attachments, new_id=None):
+ def _syncAttachmentProperty(cls, txn, managed_id, dropbox_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.
@@ -1564,14 +1579,12 @@
@param new_id: Value of new Managed-ID to use
@type new_id: C{str}
"""
- original_attachment = (yield ManagedAttachment.load(txn, managed_id))
+ new_attachment = (yield ManagedAttachment.load(txn, managed_id))
+ if new_id:
+ new_attachment._managedID = new_id
+ new_attachment._objectDropboxID = dropbox_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)))
+ yield new_attachment.updateProperty(attachment)
@classmethod
@@ -1624,6 +1637,10 @@
raise AttachmentStoreFailed
yield t.loseConnection()
+ if self._dropboxID is None:
+ self._dropboxID = str(uuid.uuid4())
+ attachment._objectDropboxID = self._dropboxID
+
# Now try and adjust the actual calendar data
#calendar = (yield self.component())
@@ -1786,6 +1803,44 @@
@inlineCallbacks
+ def managedAttachmentList(self):
+ """
+ Get a list of managed attachments where the names returned are for the last path segment
+ of the attachment URI.
+ """
+ at = schema.ATTACHMENT
+ attco = schema.ATTACHMENT_CALENDAR_OBJECT
+ rows = (yield Select(
+ [attco.MANAGED_ID, at.PATH, ],
+ From=attco.join(at, attco.ATTACHMENT_ID == at.ATTACHMENT_ID),
+ Where=attco.CALENDAR_OBJECT_RESOURCE_ID == Parameter("resourceID")
+ ).on(self._txn, resourceID=self._resourceID))
+ returnValue([ManagedAttachment.lastSegmentOfUriPath(row[0], row[1]) for row in rows])
+
+
+ @inlineCallbacks
+ def managedAttachmentRetrieval(self, name):
+ """
+ Return a managed attachment specified by the last path segment of the attachment URI.
+ """
+
+ # Scan all the associated attachments for the one that matches
+ at = schema.ATTACHMENT
+ attco = schema.ATTACHMENT_CALENDAR_OBJECT
+ rows = (yield Select(
+ [attco.MANAGED_ID, at.PATH, ],
+ From=attco.join(at, attco.ATTACHMENT_ID == at.ATTACHMENT_ID),
+ Where=attco.CALENDAR_OBJECT_RESOURCE_ID == Parameter("resourceID")
+ ).on(self._txn, resourceID=self._resourceID))
+
+ for att_managed_id, att_name in rows:
+ if ManagedAttachment.lastSegmentOfUriPath(att_managed_id, att_name) == name:
+ attachment = (yield self.attachmentWithManagedID(att_managed_id))
+ returnValue(attachment)
+ returnValue(None)
+
+
+ @inlineCallbacks
def createAttachmentWithName(self, name):
# We need to know the resource_ID of the home collection of the owner
@@ -2359,7 +2414,7 @@
created = sqltime(row_iter.next())
modified = sqltime(row_iter.next())
- attachment = cls(txn, a_id, managedID, None, ownerHomeID, True)
+ attachment = cls(txn, a_id, ".", None, ownerHomeID, True)
attachment._managedID = managedID
attachment._created = created
attachment._modified = modified
@@ -2399,6 +2454,8 @@
attco.MANAGED_ID : managedID,
attco.CALENDAR_OBJECT_RESOURCE_ID : referencedBy,
}).on(txn)
+ attachment._managedID = managedID
+ attachment._objectResourceID = referencedBy
returnValue(attachment)
@@ -2434,6 +2491,8 @@
attco.CALENDAR_OBJECT_RESOURCE_ID == referencedBy
),
).on(txn)
+ attachment._managedID = managedID
+ attachment._objectResourceID = referencedBy
# Now check whether old attachmentID is still referenced - if not delete it
rows = (yield Select(
@@ -2584,21 +2643,35 @@
@inlineCallbacks
- def location(self, new_id=None):
+ def location(self):
"""
- 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.
+ Return the URI location of the attachment.
"""
if not hasattr(self, "_ownerName"):
home = (yield self._txn.calendarHomeWithResourceID(self._ownerHomeID))
self._ownerName = home.name()
+ if not hasattr(self, "_objectDropboxID"):
+ if not hasattr(self, "_objectResource"):
+ self._objectResource = (yield self.objectResource())
+ self._objectDropboxID = self._objectResource._dropboxID
+
+ fname = self.lastSegmentOfUriPath(self._managedID, self._name)
location = self._txn._store.attachmentsURIPattern % {
"home": self._ownerName,
- "name": self._managedID if new_id is None else new_id,
+ "dropbox_id": self._objectDropboxID,
+ "name": fname,
}
returnValue(location)
+ @classmethod
+ def lastSegmentOfUriPath(cls, managed_id, name):
+ splits = name.rsplit(".", 1)
+ fname = splits[0]
+ suffix = splits[1] if len(splits) == 2 else "unknown"
+ return "%s-%s.%s" % (fname, managed_id[:8], suffix)
+
+
@inlineCallbacks
def changed(self, contentType, dispositionName, md5, size):
"""
@@ -2676,18 +2749,26 @@
"""
Return an iCalendar ATTACH property for this attachment.
"""
+ attach = Property("ATTACH", "", valuetype=PyCalendarValue.VALUETYPE_URI)
+ location = (yield self.updateProperty(attach))
+ returnValue((attach, location,))
+
+ @inlineCallbacks
+ def updateProperty(self, attach):
+ """
+ Update an iCalendar ATTACH property for this attachment.
+ """
+
location = (yield self.location())
- attach = Property("ATTACH", location, params={
- "MANAGED-ID": self.managedID(),
- "MTAG": self.md5(),
- "FMTTYPE": "%s/%s" % (self.contentType().mediaType, self.contentType().mediaSubtype),
- "FILENAME": self.name(),
- "SIZE": str(self.size()),
- }, valuetype=PyCalendarValue.VALUETYPE_URI)
+ attach.setParameter("MANAGED-ID", self.managedID())
+ attach.setParameter("MTAG", self.md5())
+ attach.setParameter("FMTTYPE", "%s/%s" % (self.contentType().mediaType, self.contentType().mediaSubtype))
+ attach.setParameter("FILENAME", self.name())
+ attach.setParameter("SIZE", str(self.size()))
+ attach.setValue(location)
- returnValue((attach, location,))
+ returnValue(location)
-
Calendar._objectResourceClass = CalendarObject
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20130115/e6ec37d6/attachment-0001.html>
More information about the calendarserver-changes
mailing list