[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