[CalendarServer-changes] [3348] CalendarServer/branches/users/cdaboo/implicit-if-match-3306

source_changes at macosforge.org source_changes at macosforge.org
Mon Nov 10 13:36:49 PST 2008


Revision: 3348
          http://trac.macosforge.org/projects/calendarserver/changeset/3348
Author:   cdaboo at apple.com
Date:     2008-11-10 13:36:48 -0800 (Mon, 10 Nov 2008)
Log Message:
-----------
Provide option to allow compatibility with clients that use If-Match on PUT.

Modified Paths:
--------------
    CalendarServer/branches/users/cdaboo/implicit-if-match-3306/conf/caldavd-test.plist
    CalendarServer/branches/users/cdaboo/implicit-if-match-3306/conf/caldavd.plist
    CalendarServer/branches/users/cdaboo/implicit-if-match-3306/lib-patches/Twisted/twisted.web2.dav.static.patch
    CalendarServer/branches/users/cdaboo/implicit-if-match-3306/twistedcaldav/config.py
    CalendarServer/branches/users/cdaboo/implicit-if-match-3306/twistedcaldav/customxml.py
    CalendarServer/branches/users/cdaboo/implicit-if-match-3306/twistedcaldav/method/put_common.py
    CalendarServer/branches/users/cdaboo/implicit-if-match-3306/twistedcaldav/scheduling/implicit.py
    CalendarServer/branches/users/cdaboo/implicit-if-match-3306/twistedcaldav/scheduling/processing.py
    CalendarServer/branches/users/cdaboo/implicit-if-match-3306/twistedcaldav/static.py
    CalendarServer/branches/users/cdaboo/implicit-if-match-3306/twistedcaldav/test/test_collectioncontents.py

Modified: CalendarServer/branches/users/cdaboo/implicit-if-match-3306/conf/caldavd-test.plist
===================================================================
--- CalendarServer/branches/users/cdaboo/implicit-if-match-3306/conf/caldavd-test.plist	2008-11-07 21:50:23 UTC (rev 3347)
+++ CalendarServer/branches/users/cdaboo/implicit-if-match-3306/conf/caldavd-test.plist	2008-11-10 21:36:48 UTC (rev 3348)
@@ -484,6 +484,8 @@
         </array>
         <key>OldDraftCompatibility</key>
         <true/>
+        <key>ScheduleTagCompatibility</key>
+        <true/>
         <key>DefaultCalendarProvisioned</key>
         <true/>
         <key>EnablePrivateComments</key>

Modified: CalendarServer/branches/users/cdaboo/implicit-if-match-3306/conf/caldavd.plist
===================================================================
--- CalendarServer/branches/users/cdaboo/implicit-if-match-3306/conf/caldavd.plist	2008-11-07 21:50:23 UTC (rev 3347)
+++ CalendarServer/branches/users/cdaboo/implicit-if-match-3306/conf/caldavd.plist	2008-11-10 21:36:48 UTC (rev 3348)
@@ -358,6 +358,8 @@
         </array>
         <key>OldDraftCompatibility</key>
         <true/>
+        <key>ScheduleTagCompatibility</key>
+        <true/>
         <key>DefaultCalendarProvisioned</key>
         <true/>
         <key>EnablePrivateComments</key>

Modified: CalendarServer/branches/users/cdaboo/implicit-if-match-3306/lib-patches/Twisted/twisted.web2.dav.static.patch
===================================================================
--- CalendarServer/branches/users/cdaboo/implicit-if-match-3306/lib-patches/Twisted/twisted.web2.dav.static.patch	2008-11-07 21:50:23 UTC (rev 3347)
+++ CalendarServer/branches/users/cdaboo/implicit-if-match-3306/lib-patches/Twisted/twisted.web2.dav.static.patch	2008-11-10 21:36:48 UTC (rev 3348)
@@ -60,11 +60,12 @@
  
      def __repr__(self):
          return "<%s: %s>" % (self.__class__.__name__, self.fp.path)
-@@ -75,6 +81,12 @@
+@@ -75,6 +81,13 @@
      # WebDAV
      ##
  
 +    def etag(self):
++        if not self.fp.exists(): return None
 +        if self.hasDeadProperty(TwistedGETContentMD5):
 +            return http_headers.ETag(str(self.readDeadProperty(TwistedGETContentMD5)))
 +        else:
@@ -73,7 +74,7 @@
      def davComplianceClasses(self):
          return ("1", "access-control") # Add "2" when we have locking
  
-@@ -87,7 +99,6 @@
+@@ -87,7 +100,6 @@
          """
          See L{IDAVResource.isCollection}.
          """
@@ -81,7 +82,7 @@
          return self.fp.isdir()
  
      ##
-@@ -98,6 +109,50 @@
+@@ -98,6 +110,50 @@
          return succeed(davPrivilegeSet)
  
      ##
@@ -132,7 +133,7 @@
      # Workarounds for issues with File
      ##
  
-@@ -112,8 +167,12 @@
+@@ -112,8 +168,12 @@
          See L{IResource}C{.locateChild}.
          """
          # If getChild() finds a child resource, return it
@@ -147,7 +148,7 @@
          
          # If we're not backed by a directory, we have no children.
          # But check for existance first; we might be a collection resource
-@@ -132,7 +191,9 @@
+@@ -132,7 +192,9 @@
          return (self.createSimilarFile(self.fp.child(path).path), segments[1:])
  
      def createSimilarFile(self, path):

Modified: CalendarServer/branches/users/cdaboo/implicit-if-match-3306/twistedcaldav/config.py
===================================================================
--- CalendarServer/branches/users/cdaboo/implicit-if-match-3306/twistedcaldav/config.py	2008-11-07 21:50:23 UTC (rev 3347)
+++ CalendarServer/branches/users/cdaboo/implicit-if-match-3306/twistedcaldav/config.py	2008-11-10 21:36:48 UTC (rev 3348)
@@ -204,6 +204,7 @@
             "HTTPDomain"                 : "",    # Domain for http calendar user addresses on this server
             "AddressPatterns"            : [],    # Reg-ex patterns to match local calendar user addresses
             "OldDraftCompatibility"      : True,  # Whether to maintain compatibility with non-implicit mode
+            "ScheduleTagCompatibility"   : True,  # Whether to support older clients that do not use Schedule-Tag feature
             "DefaultCalendarProvisioned" : True,  # Whether the provisioned default calendar is marked as the scheduling default
             "EnablePrivateComments"      : True,  # Private comments from attendees to organizer
         },

Modified: CalendarServer/branches/users/cdaboo/implicit-if-match-3306/twistedcaldav/customxml.py
===================================================================
--- CalendarServer/branches/users/cdaboo/implicit-if-match-3306/twistedcaldav/customxml.py	2008-11-07 21:50:23 UTC (rev 3347)
+++ CalendarServer/branches/users/cdaboo/implicit-if-match-3306/twistedcaldav/customxml.py	2008-11-10 21:36:48 UTC (rev 3348)
@@ -68,6 +68,16 @@
     name = "scheduling-object-resource"
     hidden = True
 
+class TwistedScheduleMatchETags(davxml.WebDAVElement):
+    """
+    List of ETags that can be used for a "weak" If-Match comparison.    
+    """
+    namespace = twisted_private_namespace
+    name = "scheduling-match-etags"
+    hidden = True
+
+    allowed_children = { (dav_namespace, "getetag"): (0, None) }
+
 class TwistedCalendarHasPrivateCommentsProperty (davxml.WebDAVEmptyElement):
     """
     Indicates that a calendar resource has private comments.

Modified: CalendarServer/branches/users/cdaboo/implicit-if-match-3306/twistedcaldav/method/put_common.py
===================================================================
--- CalendarServer/branches/users/cdaboo/implicit-if-match-3306/twistedcaldav/method/put_common.py	2008-11-07 21:50:23 UTC (rev 3347)
+++ CalendarServer/branches/users/cdaboo/implicit-if-match-3306/twistedcaldav/method/put_common.py	2008-11-10 21:36:48 UTC (rev 3348)
@@ -49,7 +49,8 @@
 from twistedcaldav.caldavxml import NumberOfRecurrencesWithinLimits
 from twistedcaldav.caldavxml import caldav_namespace
 from twistedcaldav.customxml import calendarserver_namespace ,\
-    TwistedCalendarHasPrivateCommentsProperty, TwistedSchedulingObjectResource
+    TwistedCalendarHasPrivateCommentsProperty, TwistedSchedulingObjectResource,\
+    TwistedScheduleMatchETags
 from twistedcaldav.customxml import TwistedCalendarAccessProperty
 from twistedcaldav.fileops import copyToWithXAttrs, copyXAttrs
 from twistedcaldav.fileops import putWithXAttrs
@@ -390,7 +391,19 @@
                     log.debug("If-Schedule-Tag-Match: header value '%s' does not match resource value '%s'" % (header, scheduletag,))
                     raise HTTPError(responsecode.PRECONDITION_FAILED)
                 self.schedule_tag_match = True
+            
+            elif config.Scheduling["CalDAV"]["ScheduleTagCompatibility"]:
+                # Compatibility with old clients. Policy:
+                #
+                # 1. If If-Match header is not present, never do smart merge.
+                # 2. If If-Match is present and the specified ETag is considered a "weak" match to the
+                #    current Schedule-Tag, then do smart merge, else reject with a 412.
+                #
+                # Actually by the time we get here the pre-condition will already have been tested and found to be OK,
+                # so we can just always do smart merge now if If-Match is present.
 
+                self.schedule_tag_match = self.request.headers.getHeader("If-Match") is not None
+
     def validResourceName(self):
         """
         Make sure that the resource name for the new resource is valid.
@@ -641,6 +654,7 @@
             self.scheduletag = None
 
         data_changed = False
+        did_implicit_action = False
 
         # Do scheduling
         if not self.isiTIP:
@@ -689,10 +703,11 @@
                     self.calendar = new_calendar
                     self.calendardata = str(self.calendar)
                     data_changed = True
+                did_implicit_action = True
         else:
             is_scheduling_resource = False
             
-        returnValue((is_scheduling_resource, data_changed,))
+        returnValue((is_scheduling_resource, data_changed, did_implicit_action,))
 
     @inlineCallbacks
     def doStore(self, implicit):
@@ -870,7 +885,7 @@
             new_has_private_comments = self.preservePrivateComments()
     
             # Do scheduling
-            is_scheduling_resource, data_changed = (yield self.doImplicitScheduling())
+            is_scheduling_resource, data_changed, did_implicit_action = (yield self.doImplicitScheduling())
 
             # Initialize the rollback system
             self.setupRollback()
@@ -892,7 +907,15 @@
             # Do the actual put or copy
             response = (yield self.doStore(data_changed))
             
+            # Must not set ETag in response if data changed
+            if did_implicit_action:
+                def _removeEtag(request, response):
+                    response.headers.removeHeader('etag')
+                    return response
+                _removeEtag.handleErrors = True
 
+                self.request.addResponseFilter(_removeEtag, atEnd=True)
+
             # Check for scheduling object resource and write property
             if is_scheduling_resource:
                 self.destination.writeDeadProperty(TwistedSchedulingObjectResource())
@@ -922,9 +945,26 @@
                 # Add a response header
                 response.headers.setHeader("Schedule-Tag", self.scheduletag)                
 
+                # Handle weak etag compatibility
+                if config.Scheduling["CalDAV"]["ScheduleTagCompatibility"]:
+                    if change_scheduletag:
+                        # Schedule-Tag change => weak ETag behavior must not happen
+                        etags = ()
+                    else:
+                        # Schedule-Tag did not change => add current ETag to list of those that can
+                        # be used in a weak pre-condition test
+                        if self.destination.hasDeadProperty(TwistedScheduleMatchETags):
+                            etags = self.destination.readDeadProperty(TwistedScheduleMatchETags).children
+                        else:
+                            etags = ()
+                    etags += (davxml.GETETag.fromString(self.destination.etag().tag),)
+                    self.destination.writeDeadProperty(TwistedScheduleMatchETags(*etags))
+                else:
+                    self.destination.removeDeadProperty(TwistedScheduleMatchETags)                
             else:
                 self.destination.removeDeadProperty(TwistedSchedulingObjectResource)                
                 self.destination.removeDeadProperty(ScheduleTag)                
+                self.destination.removeDeadProperty(TwistedScheduleMatchETags)                
 
             # Check for existence of private comments and write property
             if config.Scheduling["CalDAV"].get("EnablePrivateComments", True):

Modified: CalendarServer/branches/users/cdaboo/implicit-if-match-3306/twistedcaldav/scheduling/implicit.py
===================================================================
--- CalendarServer/branches/users/cdaboo/implicit-if-match-3306/twistedcaldav/scheduling/implicit.py	2008-11-07 21:50:23 UTC (rev 3347)
+++ CalendarServer/branches/users/cdaboo/implicit-if-match-3306/twistedcaldav/scheduling/implicit.py	2008-11-10 21:36:48 UTC (rev 3348)
@@ -77,7 +77,7 @@
 
         # If action is remove we actually need to get state from the existing scheduling object resource
         if self.action == "remove":
-            # Also make sure that we return the new calendar being be written rather than the old one
+            # Also make sure that we return the new calendar being written rather than the old one
             # when the implicit action is executed
             self.return_calendar = calendar
             self.calendar = resource.iCalendar()

Modified: CalendarServer/branches/users/cdaboo/implicit-if-match-3306/twistedcaldav/scheduling/processing.py
===================================================================
--- CalendarServer/branches/users/cdaboo/implicit-if-match-3306/twistedcaldav/scheduling/processing.py	2008-11-07 21:50:23 UTC (rev 3347)
+++ CalendarServer/branches/users/cdaboo/implicit-if-match-3306/twistedcaldav/scheduling/processing.py	2008-11-10 21:36:48 UTC (rev 3348)
@@ -331,6 +331,11 @@
                         customxml.Update(*update_details),
                     ),
                 )
+                
+                # Refresh from another Attendee should not have Inbox item
+                if hasattr(self.request, "doing_attendee_refresh"):
+                    autoprocessed = True
+
                 result = (True, autoprocessed, changes,)
                 
             else:

Modified: CalendarServer/branches/users/cdaboo/implicit-if-match-3306/twistedcaldav/static.py
===================================================================
--- CalendarServer/branches/users/cdaboo/implicit-if-match-3306/twistedcaldav/static.py	2008-11-07 21:50:23 UTC (rev 3347)
+++ CalendarServer/branches/users/cdaboo/implicit-if-match-3306/twistedcaldav/static.py	2008-11-10 21:36:48 UTC (rev 3348)
@@ -40,9 +40,10 @@
 import errno
 from urlparse import urlsplit
 
-from twisted.internet.defer import fail, succeed, inlineCallbacks, returnValue
+from twisted.internet.defer import fail, succeed, inlineCallbacks, returnValue,\
+    maybeDeferred
 from twisted.python.failure import Failure
-from twisted.web2 import responsecode
+from twisted.web2 import responsecode, http, http_headers
 from twisted.web2.http import HTTPError, StatusResponse
 from twisted.web2.dav import davxml
 from twisted.web2.dav.fileop import mkcollection, rmdir
@@ -56,7 +57,8 @@
 from twistedcaldav import customxml
 from twistedcaldav.caldavxml import caldav_namespace
 from twistedcaldav.config import config
-from twistedcaldav.customxml import TwistedCalendarAccessProperty
+from twistedcaldav.customxml import TwistedCalendarAccessProperty,\
+    TwistedScheduleMatchETags
 from twistedcaldav.extensions import DAVFile, DirectoryPrincipalPropertySearchMixIn
 from twistedcaldav.extensions import CachingXattrPropertyStore
 from twistedcaldav.freebusyurl import FreeBusyURLResource
@@ -92,6 +94,51 @@
         else:
             return super(CalDAVFile, self).__repr__()
 
+    def checkPreconditions(self, request):
+        """
+        We override the base class to handle the special implicit scheduling weak ETag behavior
+        for compatibility with old clients using If-Match.
+        """
+        
+        if config.Scheduling["CalDAV"]["ScheduleTagCompatibility"]:
+            
+            if self.exists() and self.hasDeadProperty(TwistedScheduleMatchETags):
+                etags = self.readDeadProperty(TwistedScheduleMatchETags).children
+                if len(etags) > 1:
+                    # This is almost verbatim from twisted.web2.static.checkPreconditions
+                    if request.method not in ("GET", "HEAD"):
+                        
+                        # Loop over each tag and succeed if any one matches, else re-raise last exception
+                        exists = self.exists()
+                        last_modified = self.lastModified()
+                        last_exception = None
+                        for etag in etags:
+                            try:
+                                http.checkPreconditions(
+                                    request,
+                                    entityExists = exists,
+                                    etag = http_headers.ETag(etag),
+                                    lastModified = last_modified,
+                                )
+                            except HTTPError, e:
+                                last_exception = e
+                            else:
+                                break
+                        else:
+                            if last_exception:
+                                raise last_exception
+            
+                    # Check per-method preconditions
+                    method = getattr(self, "preconditions_" + request.method, None)
+                    if method:
+                        response = maybeDeferred(method, request)
+                        response.addCallback(lambda _: request)
+                        return response
+                    else:
+                        return None
+
+        return super(CalDAVFile, self).checkPreconditions(request)
+
     def deadProperties(self):
         if not hasattr(self, "_dead_properties"):
             self._dead_properties = CachingXattrPropertyStore(self)

Modified: CalendarServer/branches/users/cdaboo/implicit-if-match-3306/twistedcaldav/test/test_collectioncontents.py
===================================================================
--- CalendarServer/branches/users/cdaboo/implicit-if-match-3306/twistedcaldav/test/test_collectioncontents.py	2008-11-07 21:50:23 UTC (rev 3347)
+++ CalendarServer/branches/users/cdaboo/implicit-if-match-3306/twistedcaldav/test/test_collectioncontents.py	2008-11-10 21:36:48 UTC (rev 3348)
@@ -50,7 +50,7 @@
 
         # Need to not do implicit behavior during these tests
         def _fakeDoImplicitScheduling(self):
-            return False, False
+            return False, False, False
         
         StoreCalendarObjectResource.doImplicitScheduling = _fakeDoImplicitScheduling
 
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20081110/fe3d635f/attachment-0001.html>


More information about the calendarserver-changes mailing list