[CalendarServer-changes] [7413] CalendarServer/trunk/twistedcaldav

source_changes at macosforge.org source_changes at macosforge.org
Wed May 4 13:16:35 PDT 2011


Revision: 7413
          http://trac.macosforge.org/projects/calendarserver/changeset/7413
Author:   cdaboo at apple.com
Date:     2011-05-04 13:16:35 -0700 (Wed, 04 May 2011)
Log Message:
-----------
Support default addressbook behavior.

Modified Paths:
--------------
    CalendarServer/trunk/twistedcaldav/carddavxml.py
    CalendarServer/trunk/twistedcaldav/directory/principal.py
    CalendarServer/trunk/twistedcaldav/method/copymove.py
    CalendarServer/trunk/twistedcaldav/resource.py
    CalendarServer/trunk/twistedcaldav/storebridge.py

Modified: CalendarServer/trunk/twistedcaldav/carddavxml.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/carddavxml.py	2011-05-04 20:10:51 UTC (rev 7412)
+++ CalendarServer/trunk/twistedcaldav/carddavxml.py	2011-05-04 20:16:35 UTC (rev 7413)
@@ -1,5 +1,5 @@
 ##
-# Copyright (c) 2005-2010 Apple Inc. All rights reserved.
+# Copyright (c) 2005-2011 Apple Inc. All rights reserved.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -500,6 +500,14 @@
     """
     name = "directory"
     
+class DefaultAddressBookURL (CardDAVElement):
+    """
+    A single href indicating which addressbook is the default.
+    """
+    name = "default-addressbook-URL"
+
+    allowed_children = { (davxml.dav_namespace, "href"): (0, 1) }
+
 ##
 # Extensions to davxml.ResourceType
 ##

Modified: CalendarServer/trunk/twistedcaldav/directory/principal.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/principal.py	2011-05-04 20:10:51 UTC (rev 7412)
+++ CalendarServer/trunk/twistedcaldav/directory/principal.py	2011-05-04 20:16:35 UTC (rev 7413)
@@ -1001,7 +1001,7 @@
         if hasattr(service, "addressBookHomesCollection"):
             return service.addressBookHomesCollection.homeForDirectoryRecord(self.record, request)
         else:
-            return None
+            return succeed(None)
 
     ##
     # Static

Modified: CalendarServer/trunk/twistedcaldav/method/copymove.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/copymove.py	2011-05-04 20:10:51 UTC (rev 7412)
+++ CalendarServer/trunk/twistedcaldav/method/copymove.py	2011-05-04 20:16:35 UTC (rev 7413)
@@ -39,7 +39,8 @@
 )
 
 from twistedcaldav.resource import isCalendarCollectionResource,\
-    isPseudoCalendarCollectionResource, CalDAVResource
+    isPseudoCalendarCollectionResource, CalDAVResource,\
+    isAddressBookCollectionResource
 
 log = Logger()
 
@@ -131,6 +132,8 @@
     if not result:
         is_calendar_collection = isPseudoCalendarCollectionResource(self)
         defaultCalendar = (yield self.isDefaultCalendar(request)) if is_calendar_collection else False
+        is_addressbook_collection = isAddressBookCollectionResource(self)
+        defaultAddressBook = (yield self.isDefaultAddressBook(request)) if is_addressbook_collection else False
 
         if not is_calendar_collection:
             result = yield maybeMOVEContact(self, request)
@@ -140,9 +143,13 @@
         # Do default WebDAV action
         result = (yield super(CalDAVResource, self).http_MOVE(request))
         
-        if is_calendar_collection:
-            # Do some clean up
-            yield self.movedCalendar(request, defaultCalendar, destination, destination_uri)
+        if result == responsecode.NO_CONTENT:
+            if is_calendar_collection:
+                # Do some clean up
+                yield self.movedCalendar(request, defaultCalendar, destination, destination_uri)
+            elif is_addressbook_collection:
+                # Do some clean up
+                yield self.movedAddressBook(request, defaultAddressBook, destination, destination_uri)
 
         returnValue(result)
         

Modified: CalendarServer/trunk/twistedcaldav/resource.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/resource.py	2011-05-04 20:10:51 UTC (rev 7412)
+++ CalendarServer/trunk/twistedcaldav/resource.py	2011-05-04 20:16:35 UTC (rev 7413)
@@ -1064,14 +1064,6 @@
 
     findSpecialCollections = findSpecialCollectionsFaster
 
-    def createdCalendar(self, request):
-        """
-        See L{ICalDAVResource.createCalendar}.
-        This implementation raises L{NotImplementedError}; a subclass must
-        override it.
-        """
-        unimplemented(self)
-
     @inlineCallbacks
     def deletedCalendar(self, request):
         """
@@ -1187,6 +1179,35 @@
 
 
     @inlineCallbacks
+    def movedAddressBook(self, request, defaultAddressBook, destination, destination_uri):
+        """
+        AddressBook has been moved. Need to do some extra clean-up.
+        """
+        
+        # Adjust the default addressbook setting if necessary
+        if defaultAddressBook:
+            principal = (yield self.resourceOwnerPrincipal(request))
+            home = (yield principal.addressBookHome(request))
+            (_ignore_scheme, _ignore_host, destination_path, _ignore_query, _ignore_fragment) = urlsplit(normalizeURL(destination_uri))
+            yield home.writeProperty(carddavxml.DefaultAddressBookURL(davxml.HRef(destination_path)), request)               
+
+    @inlineCallbacks
+    def isDefaultAddressBook(self, request):
+        
+        assert self.isAddressBookCollection()
+        
+        # Not allowed to delete the default address book
+        principal = (yield self.resourceOwnerPrincipal(request))
+        home = (yield principal.addressBookHome(request))
+        default = (yield home.readProperty(carddavxml.DefaultAddressBookURL.qname(), request))
+        if default and len(default.children) == 1:
+            defaultURL = normalizeURL(str(default.children[0]))
+            myURL = (yield self.canonicalURL(request))
+            returnValue(defaultURL == myURL)
+
+        returnValue(False)
+
+    @inlineCallbacks
     def vCard(self):
         """
         See L{ICalDAVResource.vCard}.
@@ -2551,6 +2572,64 @@
         returnValue((storeHome, created))
 
 
+    def liveProperties(self):
+        
+        return super(AddressBookHomeResource, self).liveProperties() + (
+            carddavxml.DefaultAddressBookURL.qname(),
+        )
+
+    @inlineCallbacks
+    def readProperty(self, property, request):
+        if type(property) is tuple:
+            qname = property
+        else:
+            qname = property.qname()
+
+        if qname == carddavxml.DefaultAddressBookURL.qname():
+            # Must have a valid default
+            try:
+                defaultAddressBookProperty = self.readDeadProperty(property)
+            except HTTPError:
+                defaultAddressBookProperty = None
+            if defaultAddressBookProperty and len(defaultAddressBookProperty.children) == 1:
+                defaultAddressBook = str(defaultAddressBookProperty.children[0])
+                adbk = (yield request.locateResource(str(defaultAddressBook)))
+                if adbk is not None and adbk.exists() and isAddressBookCollectionResource(adbk):
+                    returnValue(defaultAddressBookProperty) 
+            
+            # Default is not valid - we have to try to pick one
+            defaultAddressBookProperty = (yield self.pickNewDefaultAddressBook(request))
+            returnValue(defaultAddressBookProperty)
+            
+        result = (yield super(AddressBookHomeResource, self).readProperty(property, request))
+        returnValue(result)
+
+    @inlineCallbacks
+    def writeProperty(self, property, request):
+        assert isinstance(property, davxml.WebDAVElement)
+
+        if property.qname() == carddavxml.DefaultAddressBookURL.qname():
+            # Verify that the address book added in the PROPPATCH is valid.
+            property.children = [davxml.HRef(normalizeURL(str(href))) for href in property.children]
+            new_adbk = [str(href) for href in property.children]
+            adbk = None
+            if len(new_adbk) == 1:
+                adbkURI = str(new_adbk[0])
+                adbk = (yield request.locateResource(str(new_adbk[0])))
+            if adbk is None or not adbk.exists() or not isAddressBookCollectionResource(adbk):
+                # Validate that href's point to a valid addressbook.
+                raise HTTPError(ErrorResponse(
+                    responsecode.CONFLICT,
+                    (carddav_namespace, "valid-default-addressbook-URL"),
+                    "Invalid URI",
+                ))
+            else:
+                # Canonicalize the URL to __uids__ form
+                adbkURI = (yield adbk.canonicalURL(request))
+                property = carddavxml.DefaultAddressBookURL(davxml.HRef(adbkURI))
+
+        yield super(AddressBookHomeResource, self).writeProperty(property, request)
+
     def _setupProvisions(self):
 
         # Cache children which must be of a specific type
@@ -2590,6 +2669,35 @@
 
 
     @inlineCallbacks
+    def pickNewDefaultAddressBook(self, request):
+        """
+        First see if "addressbook" exists in the addressbook home and pick that. Otherwise
+        pick the first one we see.
+        """
+        defaultAddressBookURL = joinURL(self.url(), "addressbook")
+        defaultAddressBook = (yield self.makeRegularChild("addressbook"))
+        if defaultAddressBook is None or not defaultAddressBook.exists():
+            getter = iter((yield self._newStoreHome.addressbooks()))
+            # FIXME: the back-end should re-provision a default addressbook here.
+            # Really, the dead property shouldn't be necessary, and this should
+            # be entirely computed by a back-end method like 'defaultAddressBook()'
+            try:
+                anAddressBook = getter.next()
+            except StopIteration:
+                raise RuntimeError("No address books at all.")
+
+            defaultAddressBookURL = joinURL(self.url(), anAddressBook.name())
+
+        self.writeDeadProperty(
+            carddavxml.DefaultAddressBookURL(
+                davxml.HRef(defaultAddressBookURL)
+            )
+        )
+        returnValue(carddavxml.DefaultAddressBookURL(
+            davxml.HRef(defaultAddressBookURL))
+        )
+
+    @inlineCallbacks
     def getInternalSyncToken(self):
         # The newstore implementation supports this directly
         adbktoken = yield self._newStoreHome.syncToken()

Modified: CalendarServer/trunk/twistedcaldav/storebridge.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/storebridge.py	2011-05-04 20:10:51 UTC (rev 7412)
+++ CalendarServer/trunk/twistedcaldav/storebridge.py	2011-05-04 20:16:35 UTC (rev 7413)
@@ -36,6 +36,7 @@
 from twistedcaldav.cache import CacheStoreNotifier, ResponseCacheMixin,\
     DisabledCacheNotifier
 from twistedcaldav.caldavxml import caldav_namespace
+from twistedcaldav.carddavxml import carddav_namespace
 from twistedcaldav.config import config
 from twistedcaldav.ical import Component as VCalendar, Property as VProperty,\
     InvalidICalendarDataError, iCalendarProductID
@@ -1977,6 +1978,70 @@
         
         returnValue(storer.returndata if hasattr(storer, "returndata") else None)
 
+    @inlineCallbacks
+    def storeRemove(self, request, viaRequest, where):
+        """
+        Delete this collection resource, first deleting each contained
+        object resource.
+
+        This has to emulate the behavior in fileop.delete in that any errors
+        need to be reported back in a multistatus response.
+
+        @param request: The request used to locate child resources.  Note that
+            this is the request which I{triggered} the C{DELETE}, but which may
+            not actually be a C{DELETE} request itself.
+
+        @type request: L{twext.web2.iweb.IRequest}
+
+        @param viaRequest: Indicates if the delete was a direct result of an http_DELETE
+        which for calendars at least will require implicit cancels to be sent.
+
+        @type request: C{bool}
+
+        @param where: the URI at which the resource is being deleted.
+        @type where: C{str}
+
+        @return: an HTTP response suitable for sending to a client (or
+            including in a multi-status).
+
+        @rtype: something adaptable to L{twext.web2.iweb.IResponse}
+        """
+
+        # Not allowed to delete the default address book
+        default = (yield self.isDefaultAddressBook(request))
+        if default:
+            log.err("Cannot DELETE default address book: %s" % (self,))
+            raise HTTPError(ErrorResponse(
+                FORBIDDEN,
+                (carddav_namespace, "default-addressbook-delete-allowed",),
+                "Cannot delete default address book",
+            ))
+
+        response = (
+            yield super(AddressBookCollectionResource, self).storeRemove(
+                request, viaRequest, where
+            )
+        )
+
+        returnValue(response)
+
+    # FIXME: access control
+    @inlineCallbacks
+    def http_MOVE(self, request):
+        """
+        Moving an address book collection is allowed for the purposes of changing
+        that address book's name.
+        """
+        defaultAddressBook = (yield self.isDefaultAddressBook(request))
+        
+        result = (yield super(AddressBookCollectionResource, self).http_MOVE(request))
+        if result == NO_CONTENT:
+            destinationURI = urlsplit(request.headers.getHeader("destination"))[2]
+            destination = yield request.locateResource(destinationURI)
+            yield self.movedAddressBook(request, defaultAddressBook,
+                               destination, destinationURI)
+        returnValue(result)
+
 class GlobalAddressBookCollectionResource(GlobalAddressBookResource, AddressBookCollectionResource):
     """
     Wrapper around a L{txdav.carddav.iaddressbook.IAddressBook}.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20110504/2ddab964/attachment-0001.html>


More information about the calendarserver-changes mailing list