[CalendarServer-changes] [5604] CalendarServer/trunk

source_changes at macosforge.org source_changes at macosforge.org
Fri May 14 13:19:37 PDT 2010


Revision: 5604
          http://trac.macosforge.org/projects/calendarserver/changeset/5604
Author:   cdaboo at apple.com
Date:     2010-05-14 13:19:35 -0700 (Fri, 14 May 2010)
Log Message:
-----------
Shared address books, direct sharing merged from branch.

Modified Paths:
--------------
    CalendarServer/trunk/twistedcaldav/customxml.py
    CalendarServer/trunk/twistedcaldav/directory/addressbook.py
    CalendarServer/trunk/twistedcaldav/method/delete_common.py
    CalendarServer/trunk/twistedcaldav/method/get.py
    CalendarServer/trunk/twistedcaldav/resource.py
    CalendarServer/trunk/twistedcaldav/sharing.py
    CalendarServer/trunk/twistedcaldav/static.py
    CalendarServer/trunk/twistedcaldav/stdconfig.py

Added Paths:
-----------
    CalendarServer/trunk/twistedcaldav/sharedcollection.py

Removed Paths:
-------------
    CalendarServer/trunk/twistedcaldav/sharedcalendar.py

Property Changed:
----------------
    CalendarServer/trunk/


Property changes on: CalendarServer/trunk
___________________________________________________________________
Modified: svn:mergeinfo
   - /CalendarServer/branches/config-separation:4379-4443
/CalendarServer/branches/egg-info-351:4589-4625
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627:3628-3644
/CalendarServer/branches/users/cdaboo/partition-4464:4465-4957
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187:5188-5440
/CalendarServer/branches/users/glyph/contacts-server-merge:4971-5080
/CalendarServer/branches/users/glyph/sendfdport:5388-5424
/CalendarServer/branches/users/glyph/use-system-twisted:5084-5149
/CalendarServer/branches/users/sagen/locations-resources:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2:5052-5061
/CalendarServer/branches/users/sagen/resource-delegates-4038:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066:4068-4075
/CalendarServer/branches/users/sagen/resources-2:5084-5093
   + /CalendarServer/branches/config-separation:4379-4443
/CalendarServer/branches/egg-info-351:4589-4625
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464:4465-4957
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187:5188-5440
/CalendarServer/branches/users/glyph/contacts-server-merge:4971-5080
/CalendarServer/branches/users/glyph/sendfdport:5388-5424
/CalendarServer/branches/users/glyph/use-system-twisted:5084-5149
/CalendarServer/branches/users/sagen/locations-resources:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2:5052-5061
/CalendarServer/branches/users/sagen/resource-delegates-4038:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066:4068-4075
/CalendarServer/branches/users/sagen/resources-2:5084-5093

Modified: CalendarServer/trunk/twistedcaldav/customxml.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/customxml.py	2010-05-14 20:18:02 UTC (rev 5603)
+++ CalendarServer/trunk/twistedcaldav/customxml.py	2010-05-14 20:19:35 UTC (rev 5604)
@@ -688,12 +688,12 @@
     namespace = calendarserver_namespace
     name = "shared-url"
 
-class SharedCalendar (davxml.WebDAVElement):
+class SharedAs (davxml.WebDAVElement):
     """
     The url for a shared calendar.
     """
     namespace = calendarserver_namespace
-    name = "shared-calendar"
+    name = "shared-as"
 
     allowed_children = {
         (dav_namespace, "href")    : (1, 1),

Modified: CalendarServer/trunk/twistedcaldav/directory/addressbook.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/addressbook.py	2010-05-14 20:18:02 UTC (rev 5603)
+++ CalendarServer/trunk/twistedcaldav/directory/addressbook.py	2010-05-14 20:19:35 UTC (rev 5604)
@@ -263,7 +263,7 @@
         self.parent = parent
 
         childlist = ()
-        if config.Sharing.Enabled and config.Sharing.Calendars.Enabled:
+        if config.Sharing.Enabled and config.Sharing.AddressBooks.Enabled and not config.Sharing.Calendars.Enabled:
             childlist += (
                 ("notification", NotificationCollectionResource),
             )

Modified: CalendarServer/trunk/twistedcaldav/method/delete_common.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/delete_common.py	2010-05-14 20:18:02 UTC (rev 5603)
+++ CalendarServer/trunk/twistedcaldav/method/delete_common.py	2010-05-14 20:19:35 UTC (rev 5604)
@@ -343,6 +343,13 @@
             log.err(msg)
             raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, msg))
 
+        # Check virtual share first
+        isVirtual = yield delresource.isVirtualShare(self.request)
+        if isVirtual:
+            log.debug("Removing shared address book %s" % (delresource,))
+            yield delresource.removeVirtualShare(self.request)
+            returnValue(responsecode.NO_CONTENT)
+
         log.debug("Deleting addressbook %s" % (delresource.fp.path,))
 
         errors = ResponseQueue(deluri, "DELETE", responsecode.NO_CONTENT)

Modified: CalendarServer/trunk/twistedcaldav/method/get.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/get.py	2010-05-14 20:18:02 UTC (rev 5603)
+++ CalendarServer/trunk/twistedcaldav/method/get.py	2010-05-14 20:19:35 UTC (rev 5604)
@@ -21,7 +21,9 @@
 __all__ = ["http_GET"]
 
 from twisted.internet.defer import inlineCallbacks, returnValue
+from twext.web2 import responsecode
 from twext.web2.dav import davxml
+from twext.web2.dav.http import ErrorResponse
 from twext.web2.dav.util import parentForURL
 from twext.web2.http import HTTPError
 from twext.web2.http import Response
@@ -29,48 +31,70 @@
 from twext.web2.stream import MemoryStream
 
 from twistedcaldav.caldavxml import ScheduleTag
-from twistedcaldav.customxml import TwistedCalendarAccessProperty
+from twistedcaldav.customxml import TwistedCalendarAccessProperty,\
+    calendarserver_namespace
 from twistedcaldav.datafilters.privateevents import PrivateEventFilter
 from twistedcaldav.resource import isPseudoCalendarCollectionResource
 
 @inlineCallbacks
 def http_GET(self, request):
 
-    # Look for calendar access restriction on existing resource.
     if self.exists():
-        parentURL = parentForURL(request.uri)
-        parent = (yield request.locateResource(parentURL))
-        if isPseudoCalendarCollectionResource(parent):
-    
-            # Check authorization first
-            yield self.authorize(request, (davxml.Read(),))
-
-            caldata = (yield self.iCalendarForUser(request))
-
-            try:
-                access = self.readDeadProperty(TwistedCalendarAccessProperty)
-            except HTTPError:
-                access = None
+        # Special sharing request on a calendar or address book
+        if self.isCalendarCollection() or self.isAddressBookCollection():
+            
+            # Check for action=share
+            if request.args:
+                action = request.args.get("action", ("",))
+                if len(action) != 1:
+                    raise HTTPError(ErrorResponse(responsecode.BAD_REQUEST, (calendarserver_namespace, "valid-action")))
+                action = action[0]
+                    
+                dispatch = {
+                    "share"   : self.directShare,
+                }.get(action, None)
                 
-            if access:
+                if dispatch is None:
+                    raise HTTPError(ErrorResponse(responsecode.BAD_REQUEST, (calendarserver_namespace, "supported-action")))
         
-                # Non DAV:owner's have limited access to the data
-                isowner = (yield self.isOwner(request, adminprincipals=True, readprincipals=True))
-                
-                # Now "filter" the resource calendar data
-                caldata = PrivateEventFilter(access, isowner).filter(caldata)
+                response = (yield dispatch(request))
+                returnValue(response)
+        
+        else:
+            # Look for calendar access restriction on existing resource.
+            parentURL = parentForURL(request.uri)
+            parent = (yield request.locateResource(parentURL))
+            if isPseudoCalendarCollectionResource(parent):
+        
+                # Check authorization first
+                yield self.authorize(request, (davxml.Read(),))
     
-            response = Response()
-            response.stream = MemoryStream(str(caldata))
-            response.headers.setHeader("content-type", MimeType.fromString("text/calendar; charset=utf-8"))
+                caldata = (yield self.iCalendarForUser(request))
     
-            # Add Schedule-Tag header if property is present
-            if self.hasDeadProperty(ScheduleTag):
-                scheduletag = self.readDeadProperty(ScheduleTag)
-                if scheduletag:
-                    response.headers.setHeader("Schedule-Tag", str(scheduletag))
+                try:
+                    access = self.readDeadProperty(TwistedCalendarAccessProperty)
+                except HTTPError:
+                    access = None
+                    
+                if access:
+            
+                    # Non DAV:owner's have limited access to the data
+                    isowner = (yield self.isOwner(request, adminprincipals=True, readprincipals=True))
+                    
+                    # Now "filter" the resource calendar data
+                    caldata = PrivateEventFilter(access, isowner).filter(caldata)
         
-            returnValue(response)
+                response = Response()
+                response.stream = MemoryStream(str(caldata))
+                response.headers.setHeader("content-type", MimeType.fromString("text/calendar; charset=utf-8"))
+        
+                # Add Schedule-Tag header if property is present
+                if self.hasDeadProperty(ScheduleTag):
+                    scheduletag = self.readDeadProperty(ScheduleTag)
+                    if scheduletag:
+                        response.headers.setHeader("Schedule-Tag", str(scheduletag))
+            
+                returnValue(response)
 
     # Do normal GET behavior
     response = (yield super(CalDAVFile, self).http_GET(request))

Modified: CalendarServer/trunk/twistedcaldav/resource.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/resource.py	2010-05-14 20:18:02 UTC (rev 5603)
+++ CalendarServer/trunk/twistedcaldav/resource.py	2010-05-14 20:19:35 UTC (rev 5604)
@@ -198,7 +198,10 @@
                 )
 
             elif config.Sharing.AddressBooks.Enabled and self.isAddressBookCollection():
-                baseProperties += (customxml.Invite.qname(),)
+                baseProperties += (
+                    customxml.Invite.qname(),
+                    customxml.AllowedSharingModes.qname(),
+                )
                 
         return super(CalDAVResource, self).liveProperties() + baseProperties
 
@@ -214,6 +217,7 @@
         return qname in (
             caldavxml.CalendarDescription.qname(),
             caldavxml.CalendarTimeZone.qname(),
+            carddavxml.AddressBookDescription.qname(),
         )
 
     def isGlobalProperty(self, qname):
@@ -398,13 +402,18 @@
             ))
 
         elif qname == customxml.Invite.qname():
-            if config.Sharing.Enabled and config.Sharing.Calendars.Enabled and self.isCalendarCollection():
+            if config.Sharing.Enabled and (
+                config.Sharing.Calendars.Enabled and self.isCalendarCollection() or 
+                config.Sharing.AddressBooks.Enabled and self.isAddressBookCollection()
+            ):
                 result = (yield self.inviteProperty(request))
                 returnValue(result)
 
         elif qname == customxml.AllowedSharingModes.qname():
             if config.Sharing.Enabled and config.Sharing.Calendars.Enabled and self.isCalendarCollection():
                 returnValue(customxml.AllowedSharingModes(customxml.CanBeShared()))
+            elif config.Sharing.Enabled and config.Sharing.AddressBooks.Enabled and self.isAddressBookCollection():
+                returnValue(customxml.AllowedSharingModes(customxml.CanBeShared()))
 
         result = (yield super(CalDAVResource, self).readProperty(property, request))
         returnValue(result)
@@ -481,13 +490,19 @@
         yield self._preProcessWriteProperty(property, request)
 
         if property.qname() == davxml.ResourceType.qname():
-            if self.isCalendarCollection():
+            if self.isCalendarCollection() or self.isAddressBookCollection():
                 sawShare = [child for child in property.children if child.qname() == (calendarserver_namespace, "shared-owner")]
-                if not (config.Sharing.Enabled and config.Sharing.Calendars.Enabled):
-                    raise HTTPError(StatusResponse(
-                        responsecode.FORBIDDEN,
-                        "Cannot create shared calendars on this server.",
-                    ))
+                if sawShare:
+                    if self.isCalendarCollection() and not (config.Sharing.Enabled and config.Sharing.Calendars.Enabled):
+                        raise HTTPError(StatusResponse(
+                            responsecode.FORBIDDEN,
+                            "Cannot create shared calendars on this server.",
+                        ))
+                    elif self.isAddressBookCollection() and not (config.Sharing.Enabled and config.Sharing.AddressBooks.Enabled):
+                        raise HTTPError(StatusResponse(
+                            responsecode.FORBIDDEN,
+                            "Cannot create shared address books on this server.",
+                        ))
 
                 # Check if adding or removing share
                 shared = (yield self.isShared(request))
@@ -500,7 +515,8 @@
                         "Protected property %s may not be set." % (property.sname(),)
                     ))
                 for child in property.children:
-                    if child.qname() == caldavxml.Calendar.qname():
+                    if self.isCalendarCollection and child.qname() == caldavxml.Calendar.qname() or \
+                       self.isAddressBookCollection and child.qname() == carddavxml.AddressBook.qname():
                         break
                 else:
                     raise HTTPError(StatusResponse(
@@ -532,10 +548,12 @@
     @inlineCallbacks
     def accessControlList(self, request, *args, **kwargs):
 
+        acls = None
         isvirt = (yield self.isVirtualShare(request))
         if isvirt:
             acls = self.shareeAccessControlList()
-        else:
+
+        if acls is None:
             acls = (yield super(CalDAVResource, self).accessControlList(request, *args, **kwargs))
 
         # Look for private events access classification

Deleted: CalendarServer/trunk/twistedcaldav/sharedcalendar.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/sharedcalendar.py	2010-05-14 20:18:02 UTC (rev 5603)
+++ CalendarServer/trunk/twistedcaldav/sharedcalendar.py	2010-05-14 20:19:35 UTC (rev 5604)
@@ -1,45 +0,0 @@
-##
-# Copyright (c) 2010 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.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-
-from twisted.internet.defer import inlineCallbacks, returnValue
-
-from twistedcaldav.linkresource import LinkResource
-
-__all__ = [
-    "SharedCalendarResource",
-]
-
-"""
-Sharing behavior
-"""
-
-class SharedCalendarResource(LinkResource):
-    """
-    This is similar to a WrapperResource except that we locate our shared calendar resource dynamically. 
-    """
-    
-    def __init__(self, parent, share):
-        self.share = share
-        super(SharedCalendarResource, self).__init__(parent, None)
-
-    @inlineCallbacks
-    def linkedResource(self, request):
-        
-        if not hasattr(self, "_linkedResource"):
-            self._linkedResource = (yield request.locateResource(self.share.hosturl))
-            ownerPrincipal = (yield self.parent.ownerPrincipal(request))
-            self._linkedResource.setVirtualShare(ownerPrincipal, self.share)
-        returnValue(self._linkedResource)

Copied: CalendarServer/trunk/twistedcaldav/sharedcollection.py (from rev 5601, CalendarServer/branches/users/cdaboo/more-sharing-5591/twistedcaldav/sharedcollection.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/sharedcollection.py	                        (rev 0)
+++ CalendarServer/trunk/twistedcaldav/sharedcollection.py	2010-05-14 20:19:35 UTC (rev 5604)
@@ -0,0 +1,45 @@
+##
+# Copyright (c) 2010 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.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from twisted.internet.defer import inlineCallbacks, returnValue
+
+from twistedcaldav.linkresource import LinkResource
+
+__all__ = [
+    "SharedCollectionResource",
+]
+
+"""
+Sharing behavior
+"""
+
+class SharedCollectionResource(LinkResource):
+    """
+    This is similar to a WrapperResource except that we locate our shared collection resource dynamically. 
+    """
+    
+    def __init__(self, parent, share):
+        self.share = share
+        super(SharedCollectionResource, self).__init__(parent, None)
+
+    @inlineCallbacks
+    def linkedResource(self, request):
+        
+        if not hasattr(self, "_linkedResource"):
+            self._linkedResource = (yield request.locateResource(self.share.hosturl))
+            ownerPrincipal = (yield self.parent.ownerPrincipal(request))
+            self._linkedResource.setVirtualShare(ownerPrincipal, self.share)
+        returnValue(self._linkedResource)

Modified: CalendarServer/trunk/twistedcaldav/sharing.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/sharing.py	2010-05-14 20:18:02 UTC (rev 5603)
+++ CalendarServer/trunk/twistedcaldav/sharing.py	2010-05-14 20:19:35 UTC (rev 5604)
@@ -29,7 +29,7 @@
     returnValue
 from twistedcaldav import customxml, caldavxml
 from twistedcaldav.config import config
-from twistedcaldav.customxml import SharedCalendar
+from twistedcaldav.customxml import calendarserver_namespace
 from twistedcaldav.sql import AbstractSQLDatabase, db_prefix
 from uuid import uuid4
 from vobject.icalendar import dateTimeToString, utc
@@ -41,6 +41,10 @@
 Sharing behavior
 """
 
+# Types of sharing mode
+SHARETYPE_INVITE = "I"  # Invite based sharing
+SHARETYPE_DIRECT = "D"  # Direct linking based sharing
+
 class SharedCollectionMixin(object):
     
     def invitesDB(self):
@@ -132,6 +136,73 @@
                 record.summary = summary
             self.invitesDB().addOrUpdateRecord(record)
 
+    @inlineCallbacks
+    def directShare(self, request):
+        """
+        Directly bind an accessible calendar/address book collection into the current
+        principal's calendar/addressbook home.
+
+        @param request: the request triggering this action
+        @type request: L{IRequest}
+        """
+        
+        # Need to have at least DAV:read to do this
+        yield self.authorize(request, (davxml.Read(),))
+        
+        # Find current principal
+        authz_principal = self.currentPrincipal(request).children[0]
+        if not isinstance(authz_principal, davxml.HRef):
+            raise HTTPError(ErrorResponse(
+                responsecode.FORBIDDEN,
+                (calendarserver_namespace, "valid-principal"),
+                "Current user principal not a DAV:href",
+            ))
+        principalURL = str(authz_principal)
+        if not principalURL:
+            raise HTTPError(ErrorResponse(
+                responsecode.FORBIDDEN,
+                (calendarserver_namespace, "valid-principal"),
+                "Current user principal not specified",
+            ))
+        principal = (yield request.locateResource(principalURL))
+        
+        # Check enabled for service
+        from twistedcaldav.directory.principal import DirectoryCalendarPrincipalResource
+        if not isinstance(principal, DirectoryCalendarPrincipalResource):
+            raise HTTPError(ErrorResponse(
+                responsecode.FORBIDDEN,
+                (calendarserver_namespace, "invalid-principal"),
+                "Current user principal is not a calendar/addressbook enabled principal",
+            ))
+        
+        # Get the home collection
+        if self.isCalendarCollection():
+            home = principal.calendarHome()
+        elif self.isAddressBookCollection():
+            home = principal.addressBookHome()
+        else:
+            raise HTTPError(ErrorResponse(
+                responsecode.FORBIDDEN,
+                (calendarserver_namespace, "invalid-principal"),
+                "No calendar/addressbook home for principal",
+            ))
+            
+        # TODO: Make sure principal is not sharing back to themselves
+        compareURL = (yield self.canonicalURL(request))
+        homeURL = home.url()
+        if compareURL.startswith(homeURL):
+            raise HTTPError(ErrorResponse(
+                responsecode.FORBIDDEN,
+                (calendarserver_namespace, "invalid-share"),
+                "Can't share your own calendar or addressbook",
+            ))
+
+        # Accept it
+        response = (yield home.acceptDirectShare(request, request.path, self.resourceID(), self.displayName()))
+        
+        # Return the URL of the shared calendar
+        returnValue(response)
+
     def isShared(self, request):
         """ Return True if this is an owner shared calendar collection """
         return succeed(self.isSpecialCollection(customxml.SharedOwner))
@@ -148,8 +219,11 @@
     def removeVirtualShare(self, request):
         """ Return True if this is a shared calendar collection """
         
-        # Remove from sharee's calendar home
-        shareeHome = self._shareePrincipal.calendarHome()
+        # Remove from sharee's calendar/address book home
+        if self.isCalendarCollection():
+            shareeHome = self._shareePrincipal.calendarHome()
+        elif self.isAddressBookCollection():
+            shareeHome = self._shareePrincipal.addressBookHome()
         return shareeHome.removeShare(request, self._share)
 
     @inlineCallbacks
@@ -182,8 +256,14 @@
 
         assert self._isVirtualShare, "Only call this for a virtual share"
 
+        # Direct shares use underlying privileges of shared collection
+        if self._share.sharetype == SHARETYPE_DIRECT:
+            return None
+
+        # Invite shares use access mode from the invite
+
         # Get the invite for this sharee
-        invite = self.invitesDB().recordForInviteUID(self._share.inviteuid)
+        invite = self.invitesDB().recordForInviteUID(self._share.shareuid)
         if invite is None:
             return davxml.ACL()
         
@@ -208,13 +288,17 @@
                 davxml.Protected(),
                 TwistedACLInheritable(),
             ),
-            # Inheritable CALDAV:read-free-busy access for authenticated users.
-            davxml.ACE(
-                davxml.Principal(davxml.Authenticated()),
-                davxml.Grant(davxml.Privilege(caldavxml.ReadFreeBusy())),
-                TwistedACLInheritable(),
-            ),
         )
+        
+        if self.isCalendarCollection():
+            aces += (
+                # Inheritable CALDAV:read-free-busy access for authenticated users.
+                davxml.ACE(
+                    davxml.Principal(davxml.Authenticated()),
+                    davxml.Grant(davxml.Privilege(caldavxml.ReadFreeBusy())),
+                    TwistedACLInheritable(),
+                ),
+            )
 
         # Give read access to config.ReadPrincipals
         aces += config.ReadACEs
@@ -305,13 +389,6 @@
                 record.state = "INVALID"
                 self.invitesDB().addOrUpdateRecord(record)
                 
-    def getInviteUsers(self, request):
-        return succeed(True)
-
-    def sendNotificationOnChange(self, icalendarComponent, request, state="added"):
-        """ Possibly send a push and or email notification on a change to a resource in a shared collection """
-        return succeed(True)
-
     def inviteUserToShare(self, userid, cn, ace, summary, request):
         """ Send out in invite first, and then add this user to the share list
             @param userid: 
@@ -388,10 +465,13 @@
         # Cancel invites
         record = self.invitesDB().recordForUserID(userid)
         
-        # Remove any shared calendar
+        # Remove any shared calendar or address book
         sharee = self.principalForCalendarUserAddress(record.userid)
         if sharee:
-            shareeHome = sharee.calendarHome()
+            if self.isCalendarCollection():
+                shareeHome = sharee.calendarHome()
+            elif self.isAddressBookCollection():
+                shareeHome = sharee.addressBookHome()
             yield shareeHome.removeShareByUID(request, record.inviteuid)
     
             # If current user state is accepted then we send an invite with the new state, otherwise
@@ -810,82 +890,102 @@
 class SharedHomeMixin(object):
     """
     A mix-in for calendar/addressbook homes that defines the operations for manipulating a sharee's
-    set of shared calendfars.
+    set of shared calendars.
     """
     
     def sharesDB(self):
         
         if not hasattr(self, "_sharesDB"):
-            self._sharesDB = SharedCalendarsDatabase(self)
+            self._sharesDB = SharedCollectionsDatabase(self)
         return self._sharesDB
 
     def provisionShares(self):
         
         if not hasattr(self, "_provisionedShares"):
-            from twistedcaldav.sharedcalendar import SharedCalendarResource
+            from twistedcaldav.sharedcollection import SharedCollectionResource
             for share in self.sharesDB().allRecords():
-                child = SharedCalendarResource(self, share)
+                child = SharedCollectionResource(self, share)
                 self.putChild(share.localname, child)
             self._provisionedShares = True
 
     @inlineCallbacks
-    def acceptShare(self, request, hostUrl, inviteUID, displayname=None):
+    def acceptInviteShare(self, request, hostUrl, inviteUID, displayname=None):
         
-        # Do this first to make sure we have a valid share
+        # Send the invite reply then add the link
         yield self._changeShare(request, "ACCEPTED", hostUrl, inviteUID, displayname)
 
+        response = (yield self._acceptShare(request, SHARETYPE_INVITE, hostUrl, inviteUID, displayname))
+        returnValue(response)
+
+    def acceptDirectShare(self, request, hostUrl, resourceUID, displayname=None):
+
+        # Just add the link
+        return self._acceptShare(request, SHARETYPE_DIRECT, hostUrl, resourceUID, displayname)
+
+    @inlineCallbacks
+    def _acceptShare(self, request, sharetype, hostUrl, shareUID, displayname=None):
+
         # Add or update in DB
-        oldShare = self.sharesDB().recordForInviteUID(inviteUID)
+        oldShare = self.sharesDB().recordForShareUID(shareUID)
         if not oldShare:
-            oldShare = share = SharedCalendarRecord(inviteUID, hostUrl, str(uuid4()), displayname)
+            oldShare = share = SharedCollectionRecord(shareUID, sharetype, hostUrl, str(uuid4()), displayname)
             self.sharesDB().addOrUpdateRecord(share)
         
         # Set per-user displayname to whatever was given
         if displayname:
-            sharedCalendar = (yield request.locateResource(hostUrl))
+            sharedCollection = (yield request.locateResource(hostUrl))
             ownerPrincipal = (yield self.ownerPrincipal(request))
-            sharedCalendar.setVirtualShare(ownerPrincipal, oldShare)
-            yield sharedCalendar.writeProperty(davxml.DisplayName.fromString(displayname), request)
+            sharedCollection.setVirtualShare(ownerPrincipal, oldShare)
+            yield sharedCollection.writeProperty(davxml.DisplayName.fromString(displayname), request)
  
-        # Return the URL of the shared calendar
+        # Return the URL of the shared collection
         returnValue(XMLResponse(
             code = responsecode.OK,
-            element = SharedCalendar(
+            element = customxml.SharedAs(
                 davxml.HRef.fromString(joinURL(self.url(), oldShare.localname))
             )
         ))
 
-    def wouldAcceptShare(self, hostUrl, request):
-        return succeed(True)
-
     def removeShare(self, request, share):
-        """ Remove a shared calendar named in resourceName and send a decline """
-        return self.declineShare(request, share.hosturl, share.inviteuid)
+        """ Remove a shared collection named in resourceName """
 
+        # Send a decline when an invite share is removed only
+        if share.sharetype == SHARETYPE_INVITE:
+            return self.declineShare(request, share.hosturl, share.shareuid)
+        else:
+            return self.removeDirectShare(request, share)
+
     @inlineCallbacks
-    def removeShareByUID(self, request, inviteuid):
-        """ Remove a shared calendar but do not send a decline back """
+    def removeShareByUID(self, request, shareUID):
+        """ Remove a shared collection but do not send a decline back """
 
-        record = self.sharesDB().recordForInviteUID(inviteuid)
-        if record:
-            shareURL = joinURL(self.url(), record.localname)
-    
+        share = self.sharesDB().recordForShareUID(shareUID)
+        if share:
+            yield self.removeDirectShare(request, share)
+
+        returnValue(True)
+
+    @inlineCallbacks
+    def removeDirectShare(self, request, share):
+        """ Remove a shared collection but do not send a decline back """
+
+        shareURL = joinURL(self.url(), share.localname)
+
+        if self.isCalendarCollection():
             # For backwards compatibility we need to sync this up with the calendar-free-busy-set on the inbox
             principal = (yield self.resourceOwnerPrincipal(request))
             inboxURL = principal.scheduleInboxURL()
             if inboxURL:
                 inbox = (yield request.locateResource(inboxURL))
                 inbox.processFreeBusyCalendar(shareURL, False)
-    
-            self.sharesDB().removeRecordForInviteUID(inviteuid)
 
-        returnValue(True)
+        self.sharesDB().removeRecordForShareUID(share.shareuid)
 
     @inlineCallbacks
     def declineShare(self, request, hostUrl, inviteUID):
 
         # Remove it if its in the DB
-        self.sharesDB().removeRecordForInviteUID(inviteUID)
+        yield self.removeShareByUID(request, inviteUID)
 
         yield self._changeShare(request, "DECLINED", hostUrl, inviteUID)
         
@@ -893,31 +993,31 @@
 
     @inlineCallbacks
     def _changeShare(self, request, state, hostUrl, replytoUID, displayname=None):
-        """ Accept an invite to a shared calendar """
+        """ Accept an invite to a shared collection """
         
         # Change state in sharer invite
         owner = (yield self.ownerPrincipal(request))
         owner = owner.principalURL()
-        sharedCalendar = (yield request.locateResource(hostUrl))
-        if sharedCalendar is None:
-            # Original shared calendar is gone - nothing we can do except ignore it
+        sharedCollection = (yield request.locateResource(hostUrl))
+        if sharedCollection is None:
+            # Original shared collection is gone - nothing we can do except ignore it
             raise HTTPError(ErrorResponse(
                 responsecode.FORBIDDEN,
                 (customxml.calendarserver_namespace, "valid-request"),
-                "invalid shared calendar",
+                "invalid shared collection",
             ))
             
         # Change the record
-        yield sharedCalendar.changeUserInviteState(request, replytoUID, owner, state, displayname)
+        yield sharedCollection.changeUserInviteState(request, replytoUID, owner, state, displayname)
 
-        yield self.sendReply(request, owner, sharedCalendar, state, hostUrl, replytoUID, displayname)
+        yield self.sendReply(request, owner, sharedCollection, state, hostUrl, replytoUID, displayname)
 
     @inlineCallbacks
-    def sendReply(self, request, sharee, sharedCalendar, state, hostUrl, replytoUID, displayname=None):
+    def sendReply(self, request, sharee, sharedCollection, state, hostUrl, replytoUID, displayname=None):
         
 
         # Locate notifications collection for sharer
-        sharer = (yield sharedCalendar.ownerPrincipal(request))
+        sharer = (yield sharedCollection.ownerPrincipal(request))
         notifications = (yield request.locateResource(sharer.notificationURL()))
         
         # Generate invite XML
@@ -976,7 +1076,7 @@
                     "missing required XML elements",
                 ))
             if accepted:
-                return self.acceptShare(request, hostUrl, replytoUID, displayname=summary)
+                return self.acceptInviteShare(request, hostUrl, replytoUID, displayname=summary)
             else:
                 return self.declineShare(request, hostUrl, replytoUID)
 
@@ -999,28 +1099,29 @@
 
         return allDataFromStream(request.stream).addCallback(_getData)
 
-class SharedCalendarRecord(object):
+class SharedCollectionRecord(object):
     
-    def __init__(self, inviteuid, hosturl, localname, summary):
-        self.inviteuid = inviteuid
+    def __init__(self, shareuid, sharetype, hosturl, localname, summary):
+        self.shareuid = shareuid
+        self.sharetype = sharetype
         self.hosturl = hosturl
         self.localname = localname
         self.summary = summary
 
-class SharedCalendarsDatabase(AbstractSQLDatabase, LoggingMixIn):
+class SharedCollectionsDatabase(AbstractSQLDatabase, LoggingMixIn):
     
     db_basename = db_prefix + "shares"
     schema_version = "1"
     db_type = "shares"
 
-    def __init__(self, resource):
+    def __init__(self, resource):   
         """
         @param resource: the L{twistedcaldav.static.CalDAVFile} resource for
             the shared collection. C{resource} must be a calendar/addressbook home collection.)
         """
         self.resource = resource
-        db_filename = os.path.join(self.resource.fp.path, SharedCalendarsDatabase.db_basename)
-        super(SharedCalendarsDatabase, self).__init__(db_filename, True, autocommit=True)
+        db_filename = os.path.join(self.resource.fp.path, SharedCollectionsDatabase.db_basename)
+        super(SharedCollectionsDatabase, self).__init__(db_filename, True, autocommit=True)
 
     def create(self):
         """
@@ -1038,25 +1139,25 @@
         row = self._db_execute("select * from SHARES where LOCALNAME = :1", localname)
         return self._makeRecord(row[0]) if row else None
     
-    def recordForInviteUID(self, inviteUID):
+    def recordForShareUID(self, shareUID):
 
-        row = self._db_execute("select * from SHARES where INVITEUID = :1", inviteUID)
+        row = self._db_execute("select * from SHARES where SHAREUID = :1", shareUID)
         return self._makeRecord(row[0]) if row else None
     
     def addOrUpdateRecord(self, record):
 
-        self._db_execute("""insert or replace into SHARES (INVITEUID, HOSTURL, LOCALNAME, SUMMARY)
-            values (:1, :2, :3, :4)
-            """, record.inviteuid, record.hosturl, record.localname, record.summary,
+        self._db_execute("""insert or replace into SHARES (SHAREUID, SHARETYPE, HOSTURL, LOCALNAME, SUMMARY)
+            values (:1, :2, :3, :4, :5)
+            """, record.shareuid, record.sharetype, record.hosturl, record.localname, record.summary,
         )
     
     def removeRecordForLocalName(self, localname):
 
         self._db_execute("delete from SHARES where LOCALNAME = :1", localname)
     
-    def removeRecordForInviteUID(self, inviteUID):
+    def removeRecordForShareUID(self, shareUID):
 
-        self._db_execute("delete from SHARES where INVITEUID = :1", inviteUID)
+        self._db_execute("delete from SHARES where SHAREUID = :1", shareUID)
     
     def remove(self):
         
@@ -1067,13 +1168,13 @@
         """
         @return: the schema version assigned to this index.
         """
-        return SharedCalendarsDatabase.schema_version
+        return SharedCollectionsDatabase.schema_version
 
     def _db_type(self):
         """
         @return: the collection type assigned to this index.
         """
-        return SharedCalendarsDatabase.db_type
+        return SharedCollectionsDatabase.db_type
 
     def _db_init_data_tables(self, q):
         """
@@ -1082,15 +1183,17 @@
         """
         #
         # SHARES table is the primary table
-        #   INVITEUID: UID for this invite
+        #   SHAREUID: UID for this share
+        #   SHARETYPE: type of share: "I" for invite, "D" for direct
         #   HOSTURL: URL for data source
         #   LOCALNAME: local path name
-        #   SUMMARY: Invite summary
+        #   SUMMARY: Share summary
         #
         q.execute(
             """
             create table SHARES (
-                INVITEUID      text unique,
+                SHAREUID       text unique,
+                SHARETYPE      text(1),
                 HOSTURL        text,
                 LOCALNAME      text,
                 SUMMARY        text
@@ -1100,7 +1203,7 @@
 
         q.execute(
             """
-            create index INVITEUID on SHARES (INVITEUID)
+            create index SHAREUID on SHARES (SHAREUID)
             """
         )
         q.execute(
@@ -1124,4 +1227,4 @@
 
     def _makeRecord(self, row):
         
-        return SharedCalendarRecord(*[str(item) if type(item) == types.UnicodeType else item for item in row])
+        return SharedCollectionRecord(*[str(item) if type(item) == types.UnicodeType else item for item in row])

Modified: CalendarServer/trunk/twistedcaldav/static.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/static.py	2010-05-14 20:18:02 UTC (rev 5603)
+++ CalendarServer/trunk/twistedcaldav/static.py	2010-05-14 20:19:35 UTC (rev 5604)
@@ -974,7 +974,7 @@
 
     def provision(self):
         result = super(CalendarHomeFile, self).provision()
-        if config.Sharing.Enabled:
+        if config.Sharing.Enabled and config.Sharing.Calendars.Enabled:
             self.provisionShares()
         return result
 
@@ -989,7 +989,7 @@
         else:
             FreeBusyURLFileClass = None
             
-        if config.Sharing.Enabled:
+        if config.Sharing.Enabled and config.Sharing.Calendars.Enabled:
             NotificationCollectionFileClass = NotificationCollectionFile
         else:
             NotificationCollectionFileClass = None
@@ -1514,7 +1514,7 @@
     def createSimilarFile(self, path):
         raise HTTPError(responsecode.NOT_FOUND)
 
-class AddressBookHomeFile (AutoProvisioningFileMixIn, DirectoryAddressBookHomeResource, CalDAVFile):
+class AddressBookHomeFile (AutoProvisioningFileMixIn, SharedHomeMixin, DirectoryAddressBookHomeResource, CalDAVFile):
     """
     Address book home collection resource.
     """
@@ -1540,6 +1540,8 @@
 
     def provision(self):
         result = super(AddressBookHomeFile, self).provision()
+        if config.Sharing.Enabled and config.Sharing.AddressBooks.Enabled:
+            self.provisionShares()
         self.provisionLinks()
         return result
 
@@ -1555,7 +1557,7 @@
 
     def provisionChild(self, name):
  
-        if config.Sharing.Enabled:
+        if config.Sharing.Enabled and config.Sharing.AddressBooks.Enabled and not config.Sharing.Calendars.Enabled:
             NotificationCollectionFileClass = NotificationCollectionFile
         else:
             NotificationCollectionFileClass = None

Modified: CalendarServer/trunk/twistedcaldav/stdconfig.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/stdconfig.py	2010-05-14 20:18:02 UTC (rev 5603)
+++ CalendarServer/trunk/twistedcaldav/stdconfig.py	2010-05-14 20:19:35 UTC (rev 5604)
@@ -337,7 +337,7 @@
             "AllowScheduling" : False, # Scheduling in shared calendars
         },
         "AddressBooks" : {
-            "Enabled"         : False, # Address Books on/off switch
+            "Enabled"         : True,  # Address Books on/off switch
         }        
     },
 
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20100514/cbf10faf/attachment-0001.html>


More information about the calendarserver-changes mailing list