[CalendarServer-changes] [5265] CalendarServer/branches/users/cdaboo/shared-calendars-5187/ twistedcaldav

source_changes at macosforge.org source_changes at macosforge.org
Sat Mar 6 12:03:21 PST 2010


Revision: 5265
          http://trac.macosforge.org/projects/calendarserver/changeset/5265
Author:   cdaboo at apple.com
Date:     2010-03-06 12:03:21 -0800 (Sat, 06 Mar 2010)
Log Message:
-----------
Proper uninvite and shared collection delete behavior.

Modified Paths:
--------------
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/customxml.py
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/method/delete_common.py
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/notifications.py
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/resource.py
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/sharing.py
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/static.py

Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/customxml.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/customxml.py	2010-03-06 20:01:57 UTC (rev 5264)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/customxml.py	2010-03-06 20:03:21 UTC (rev 5265)
@@ -761,7 +761,6 @@
         HostURL.qname()                  : (0, 1),
         Organizer.qname()                : (0, 1),
         InviteSummary.qname()            : (0, 1),
-        UID.qname()                      : (0, 1),
     }
 
 class ResourceUpdateAdded(davxml.WebDAVEmptyElement):
@@ -809,6 +808,8 @@
 
     allowed_children = {
         DTStamp.qname()                            : (0, None),
+        UID.qname()                                : (0, None),
+        Sequence.qname()                           : (0, None),
         InviteNotification.qname()                 : (0, None),
         ResourceUpdateNotification.qname()         : (0, None),
         SharedCalendarUpdateNotification.qname()   : (0, None),

Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/method/delete_common.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/method/delete_common.py	2010-03-06 20:01:57 UTC (rev 5264)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/method/delete_common.py	2010-03-06 20:03:21 UTC (rev 5265)
@@ -212,6 +212,12 @@
                 errors.add(childurl, responsecode.BAD_REQUEST)
 
         # Now do normal delete
+
+        # Handle sharing
+        wasShared = (yield delresource.isShared(self.request))
+        if wasShared:
+            yield delresource.downgradeFromShare(self.request)
+
         # Change CTag
         yield delresource.bumpSyncToken()
         more_responses = (yield self.deleteResource(delresource, deluri, parent))
@@ -346,6 +352,12 @@
                 errors.add(childurl, responsecode.BAD_REQUEST)
 
         # Now do normal delete
+
+        # Handle sharing
+        wasShared = (yield delresource.isShared(self.request))
+        if wasShared:
+            yield delresource.downgradeFromShare(self.request)
+
         yield delresource.updateCTag()
         more_responses = (yield self.deleteResource(delresource, deluri, parent))
         

Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/notifications.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/notifications.py	2010-03-06 20:01:57 UTC (rev 5264)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/notifications.py	2010-03-06 20:03:21 UTC (rev 5265)
@@ -59,7 +59,7 @@
         
         response = (yield super(NotificationResource, self).http_DELETE(request))
         if response == responsecode.NO_CONTENT:
-            self._parent.deleteNotifictionMessageByName(request, self.resourceName())
+            yield self._parent.removedNotifictionMessage(request, self.resourceName())
         returnValue(response)
 
 class NotificationCollectionResource(DAVResource):
@@ -76,17 +76,16 @@
     def resourceType(self):
         return davxml.ResourceType.notification
 
+    @inlineCallbacks
     def addNotification(self, request, uid, xmltype, xmldata):
         
         # Write data to file
         rname = uid + ".xml"
-        self._writeNotification(request, uid, rname, xmltype, xmldata)
+        yield self._writeNotification(request, uid, rname, xmltype, xmldata)
 
         # Update database
         self.notificationsDB().addOrUpdateRecord(NotificationRecord(uid, rname, xmltype))
 
-        return succeed(None)
-
     def _writeNotification(self, request, uid, rname, xmltype, xmldata):
         raise NotImplementedError
 
@@ -96,12 +95,29 @@
     def getNotifictionMessageByUID(self, request, uid):
         return succeed(self.notificationsDB().recordForUID(uid))
 
+    @inlineCallbacks
     def deleteNotifictionMessageByUID(self, request, uid):
-        return succeed(self.notificationsDB().removeRecordForUID(uid))
+        
+        # See if it exists and delete the resource
+        record = self.notificationsDB().recordForUID(uid)
+        if record:
+            yield self._deleteNotification(request, record.name)
+            self.notificationsDB().removeRecordForUID(record.uid)
 
     def deleteNotifictionMessageByName(self, request, rname):
-        return succeed(self.notificationsDB().removeRecordForName(rname))
 
+        # See if it exists and delete the resource
+        record = self.notificationsDB().recordForName(rname)
+        if record:
+            self._deleteNotification(request, record.name)
+            self.notificationsDB().removeRecordForUID(record.uid)
+        
+        return succeed(None)
+
+    def removedNotifictionMessage(self, request, rname):
+        self.notificationsDB().removeRecordForName(rname)
+        return succeed(None)
+        
 class NotificationRecord(object):
     
     def __init__(self, uid, name, xmltype):

Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/resource.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/resource.py	2010-03-06 20:01:57 UTC (rev 5264)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/resource.py	2010-03-06 20:03:21 UTC (rev 5265)
@@ -69,7 +69,7 @@
 from twistedcaldav.ical import Component as iComponent
 from twistedcaldav.ical import allowedComponents
 from twistedcaldav.icaldav import ICalDAVResource, ICalendarPrincipalResource
-from twistedcaldav.sharing import SharingMixin
+from twistedcaldav.sharing import SharedCollectionMixin
 from twistedcaldav.vcard import Component as vComponent
 
 
@@ -135,7 +135,7 @@
     return fun
 
 
-class CalDAVResource (CalDAVComplianceMixIn, SharingMixin, DAVResource, LoggingMixIn):
+class CalDAVResource (CalDAVComplianceMixIn, SharedCollectionMixin, DAVResource, LoggingMixIn):
     """
     CalDAV resource.
 

Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/sharing.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/sharing.py	2010-03-06 20:01:57 UTC (rev 5264)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/sharing.py	2010-03-06 20:03:21 UTC (rev 5265)
@@ -31,7 +31,7 @@
 import types
 
 __all__ = [
-    "SharingMixin",
+    "SharedCollectionMixin",
 ]
 
 from twistedcaldav import customxml
@@ -41,7 +41,7 @@
 Sharing behavior
 """
 
-class SharingMixin(object):
+class SharedCollectionMixin(object):
     
     def invitesDB(self):
         
@@ -79,20 +79,24 @@
 
         return succeed(True)
     
+    @inlineCallbacks
     def downgradeFromShare(self, request):
         
-        # Change resource type
+        # Change resource type (note this might be called after deleting a resource
+        # so we have to cope with that)
         rtype = self.resourceType()
         rtype = davxml.ResourceType(*([child for child in rtype.children if child != customxml.SharedOwner()]))
         self.writeDeadProperty(rtype)
         
         # Remove all invitees
+        records = self.invitesDB().allRecords()
+        yield self.uninviteUserToShare([record.userid for record in records], None, request)
 
         # Remove invites database
         self.invitesDB().remove()
         delattr(self, "_invitesDB")
     
-        return succeed(True)
+        returnValue(True)
 
     def removeUserFromInvite(self, userid, request):
         """ Remove a user from this shared calendar """
@@ -236,14 +240,28 @@
         
         returnValue(True)            
 
+    @inlineCallbacks
     def uninviteSingleUserFromShare(self, userid, aces, request):
         
+        newuserid = self.validUserIDForShare(userid)
+        if newuserid:
+            userid = newuserid
+
         # Cancel invites
+        record = self.invitesDB().recordForUserID(userid)
+        
+        # If current user state is accepted then we send an invite with the new state, otherwise
+        # we cancel any existing invites for the user
+        if record and record.state != "ACCEPTED":
+            yield self.removeInvite(record, request)
+        elif record:
+            record.state = "DELETED"
+            yield self.sendInvite(record, request)
 
         # Remove from database
         self.invitesDB().removeRecordForUserID(userid)
         
-        return succeed(True)            
+        returnValue(True)            
 
     def inviteSingleUserUpdateToShare(self, userid, acesOLD, aceNEW, summary, request, commonName="", shareName=""):
         
@@ -273,6 +291,8 @@
         xmltype = customxml.InviteNotification.name
         xmldata = customxml.Notification(
             customxml.DTStamp.fromString(dateTimeToString(datetime.datetime.now(tz=utc))),
+            customxml.UID.fromString(record.inviteuid),
+            customxml.Sequence.fromString(str(record.sequence)),
             customxml.InviteNotification(
                 davxml.HRef.fromString(record.userid),
                 inviteStatusMapToXML[record.state](),
@@ -285,14 +305,24 @@
                     davxml.HRef.fromString(owner),
                 ),
                 customxml.InviteSummary.fromString(record.summary),
-                customxml.UID.fromString(record.inviteuid),
-                customxml.Sequence.fromString(str(record.sequence)),
             ),
         ).toxml()
         
         # Add to collections
         yield notifications.addNotification(request, record.inviteuid, xmltype, xmldata)
 
+    @inlineCallbacks
+    def removeInvite(self, record, request):
+        
+        # Locate notifications collection for user
+        sharee = self.principalForCalendarUserAddress(record.userid)
+        if sharee is None:
+            raise ValueError("sharee is None but userid was valid before")
+        notifications = (yield request.locateResource(sharee.notificationURL()))
+        
+        # Add to collections
+        yield notifications.deleteNotifictionMessageByUID(request, record.inviteuid)
+
     def xmlPOSTNoAuth(self, encoding, request):
         def _handleErrorResponse(error):
             if isinstance(error.value, HTTPError) and hasattr(error.value, "response"):
@@ -479,6 +509,94 @@
         ("text", "xml") : xmlPOSTAuth,
     }
 
+class SharedHomeMixin(object):
+    """
+    A mix-in for calendar/addressbook homes that defines the operations for manipulating a sharee's
+    set of shared calendfars.
+    """
+    
+    def acceptShare(self, request, hostUrl, displayname=None):
+        """ Accept an invite to a shared calendar """
+        return succeed(True)
+
+    def wouldAcceptShare(self, hostUrl, request):
+        return succeed(True)
+
+    def removeShare(self, hostUrl, resourceName, request):
+        """ Remove a shared calendar named in resourceName """
+        return succeed(True)
+
+    def declineShare(self, hostUrl, request):
+        return succeed(True)
+
+    def xmlPOSTNoAuth(self, encoding, request):
+        def _handleResponse(result):
+            response = Response(code=responsecode.OK)
+            return response
+
+        def _handleErrorResponse(error):
+            if isinstance(error.value, HTTPError) and hasattr(error.value, "response"):
+                return error.value.response
+            return Response(code=responsecode.BAD_REQUEST)
+
+        def _handleInviteUpdate(inviteupdatedoc):
+            """ Handle a user accepting or declining a sharing invite """
+            hostUrl = None
+            accepted = None
+            summary = None
+            for item in inviteupdatedoc.children:
+                if isinstance(item, customxml.InviteStatusAccepted):
+                    accepted = True
+                elif isinstance(item, customxml.InviteStatusDeclined):
+                    accepted = False
+                elif isinstance(item, customxml.InviteSummary):
+                    for summaryitem in item.children:
+                        if isinstance(summaryitem, PCDATAElement):
+                            summary = summaryitem.data
+                elif isinstance(item, customxml.HostURL):
+                    for hosturlItem in item.children:
+                        if isinstance(hosturlItem, davxml.HRef):
+                            for hrefItem in hosturlItem.children:
+                                if isinstance(hrefItem, PCDATAElement):
+                                    hostUrl = hrefItem.data
+            
+            if accepted is not None:
+                if hostUrl:
+                    if accepted:
+                        return self.acceptShare(request, hostUrl, displayname=summary)
+                    else:
+                        return self.declineShare(hostUrl, request)
+                else:
+                    raise HTTPError(ErrorResponse(
+                        responsecode.FORBIDDEN,
+                        (customxml.calendarserver_namespace, "valid-request-content"),
+                        "missing hosturl",
+                    ))
+            else:
+                raise HTTPError(ErrorResponse(
+                    responsecode.FORBIDDEN,
+                    (customxml.calendarserver_namespace, "valid-request-content"),
+                    "missing invite status",
+                ))
+
+        def _getData(data):
+            try:
+                doc = davxml.WebDAVDocument.fromString(data)
+            except ValueError, e:
+                print "Error parsing doc (%s) Doc:\n %s" % (str(e), data,)
+                raise
+
+            root = doc.root_element
+            xmlDocHanders = {
+                customxml.InviteNotification: _handleInviteUpdate,          
+            }
+            if type(root) in xmlDocHanders:
+                return xmlDocHanders[type(root)](root).addCallbacks(_handleResponse, errback=_handleErrorResponse)
+            else:
+                return fail(True)
+
+        return allDataFromStream(request.stream).addCallback(_getData)
+
 inviteAccessMapToXML = {
     "read-only"  : customxml.ReadAccess,
     "read-write" : customxml.ReadWriteAccess,

Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/static.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/static.py	2010-03-06 20:01:57 UTC (rev 5264)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/static.py	2010-03-06 20:03:21 UTC (rev 5265)
@@ -55,7 +55,7 @@
 from twext.web2.http import HTTPError, StatusResponse
 from twext.web2.dav import davxml
 from twext.web2.dav.element.base import dav_namespace
-from twext.web2.dav.fileop import mkcollection, rmdir
+from twext.web2.dav.fileop import mkcollection, rmdir, delete
 from twext.web2.dav.http import ErrorResponse
 from twext.web2.dav.idav import IDAVResource
 from twext.web2.dav.noneprops import NonePropertyStore
@@ -1312,11 +1312,20 @@
 
     def _writeNotification(self, request, uid, rname, xmltype, xmldata):
         
+        # TODO: use the generic StoreObject api so that quota, sync-token etc all get changed properly
         child = self.createSimilarFile(self.fp.child(rname).path)
         child.fp.setContent(xmldata)
         child.writeDeadProperty(davxml.GETContentType.fromString(generateContentType(MimeType("text", "xml", params={"charset":"utf-8"}))))
         child.writeDeadProperty(customxml.NotificationType.fromString(xmltype))
+        
+        return succeed(True)
 
+    def _deleteNotification(self, request, rname):
+        
+        # TODO: use the generic DeleteResource api so that quota, sync-token etc all get changed properly
+        childfp = self.fp.child(rname)
+        return delete("", childfp)
+
 class NotificationFile(NotificationResource, CalDAVFile):
 
     def __init__(self, path, parent):
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20100306/206d562d/attachment-0001.html>


More information about the calendarserver-changes mailing list