[Checkins] SVN: z3c.dav/trunk/src/z3c/dav/ We need to use the IDAVLockdiscovery adapters for the protocol to find out

Michael Kerrin michael.kerrin at openapp.ie
Thu Jun 7 16:26:31 EDT 2007


Log message for revision 76477:
  We need to use the IDAVLockdiscovery adapters for the protocol to find out
  what locks exist on a resource, trying to use the ActiveLock adapters in the
  past didn't work since multiple active locks can exist on the one resource.
  

Changed:
  U   z3c.dav/trunk/src/z3c/dav/ifvalidator.py
  U   z3c.dav/trunk/src/z3c/dav/interfaces.py
  U   z3c.dav/trunk/src/z3c/dav/locking.py
  U   z3c.dav/trunk/src/z3c/dav/locking.txt

-=-
Modified: z3c.dav/trunk/src/z3c/dav/ifvalidator.py
===================================================================
--- z3c.dav/trunk/src/z3c/dav/ifvalidator.py	2007-06-07 19:24:13 UTC (rev 76476)
+++ z3c.dav/trunk/src/z3c/dav/ifvalidator.py	2007-06-07 20:26:30 UTC (rev 76477)
@@ -89,12 +89,12 @@
 
       >>> import UserDict
       >>> import zope.interface.verify
-      >>> import zope.publisher.interfaces
       >>> import zope.publisher.interfaces.http
       >>> from zope.publisher.interfaces import IPublishTraverse
       >>> from zope.publisher.browser import TestRequest
       >>> from zope.traversing.interfaces import IPhysicallyLocatable
       >>> from zope.app.publication.zopepublication import ZopePublication
+      >>> from zope.security.proxy import removeSecurityProxy
 
     The validator is a utility that implements the IF header conditional
     request as specified in the WebDAV specification.
@@ -137,10 +137,11 @@
       ...        self.context = context
       ...    @property
       ...    def tokens(self):
-      ...        if getattr(self.context, '_tokens', None) is not None:
-      ...            return self.context._tokens
-      ...        if self.context:
-      ...            return [self.context.__name__]
+      ...        context = removeSecurityProxy(self.context) # ???
+      ...        if getattr(context, '_tokens', None) is not None:
+      ...            return context._tokens
+      ...        if context and context.__name__:
+      ...            return [context.__name__]
       ...        return []
       >>> zope.component.getGlobalSiteManager().registerAdapter(
       ...    Statetokens, (None, TestRequest, None))
@@ -239,7 +240,7 @@
       >>> validator.valid(resource, request, None)
       False
       >>> getStateResults(request)
-      {}
+      {'/test': {'locktoken': False}}
 
       >>> resource._tokens = ['locktoken']
       >>> validator.valid(resource, request, None)
@@ -296,7 +297,7 @@
     Setup up three content object. One is the context for validation, the
     second is a locked resource, and the last is the root of the site.
 
-      >>> root = Demo('root')
+      >>> root = Demo('')
       >>> locked = Demo('locked')
       >>> root.add(locked)
       >>> demo = Demo('demo')
@@ -349,6 +350,16 @@
       >>> getStateResults(request)
       {'/missing': {'locked': False}}
 
+    If we have specify multiple resources then we need to parse the
+    whole `IF` header so that the state results method knows about the
+    different resources.
+
+      >>> request._environ['IF'] = '</demo> (<demo>) </locked> (<notlocked>)'
+      >>> validator.valid(demo, request, None)
+      True
+      >>> getStateResults(request)
+      {'/locked': {'notlocked': False}, '/demo': {'demo': True}}
+
     Invalid data
     ============
 
@@ -395,6 +406,101 @@
       >>> getStateResults(request)
       {'/ddd': {'hi': False}}
 
+    matchesIfHeader method
+    ======================
+
+    Test the state of the context to see if matches the list of states
+    supplied in the `IF` header.
+
+      >>> request = TestRequest(environ = {'REQUEST_METHOD': 'FROG'})
+      >>> request.setPublication(ZopePublication(None))
+
+    When the resource is not locked and hence are no state tokens for the
+    object then this method returns True
+
+      >>> matchesIfHeader(root, request)
+      True
+
+    When the resource is locked, and there is no `IF` header then we haven't
+    matched anything.
+
+      >>> matchesIfHeader(demo, request)
+      False
+
+    XXX - Not sure if this is right.
+
+      >>> request._environ['IF'] = '</demo> (NOT <DAV:no-lock>)'
+      >>> validator.valid(demo, request, None)
+      True
+      >>> matchesIfHeader(demo, request)
+      False
+
+    The parsed state token in the `IF` header for the demo object matches
+    the current state token.
+
+      >>> demo._tokens = ['test'] # setup the state tokens
+      >>> request._environ['IF'] = '</demo> (<test>)'
+      >>> validator.valid(demo, request, None)
+      True
+      >>> matchesIfHeader(demo, request)
+      True
+
+    The parsed state token for the demo object does not match that in the
+    `IF` header. Note that we needed to specify the <DAV:no-lock> in order
+    for the request to be valid.
+
+      >>> request._environ['IF'] = '</demo> (<falsetest>) (NOT <DAV:no-lock>)'
+      >>> validator.valid(demo, request, None)
+      True
+      >>> matchesIfHeader(demo, request)
+      False
+
+    The state token for the root object matches that in the `IF` header. But
+    since the demo object is a child of the root token then it is also
+    assumed to match the parsed data.
+
+      >>> root._tokens = ['roottest']
+      >>> request._environ['IF'] = '</> (<roottest>)'
+      >>> validator.valid(demo, request, None)
+      True
+      >>> matchesIfHeader(root, request)
+      True
+      >>> matchesIfHeader(demo, request)
+      True
+
+    Since the state of the root token does not match the information in the
+    `IF` header, and the demo object fails to match the data.
+
+      >>> request._environ['IF'] = '</> (<falseroottest>) (NOT <DAV:no-lock>)'
+      >>> validator.valid(demo, request, None)
+      True
+      >>> matchesIfHeader(root, request)
+      False
+      >>> matchesIfHeader(demo, request)
+      False
+
+    Two resources are specified in the `IF` header, so the root object fails
+    to match the `IF` header whereas the demo object does match.
+
+      >>> request._environ['IF'] = '</> (<falseroottest>) </demo> (<test>)'
+      >>> validator.valid(demo, request, None)
+      True
+      >>> matchesIfHeader(root, request)
+      False
+      >>> matchesIfHeader(demo, request)
+      True
+
+    Even tough the demo objects state failed to match the `IF` header, its
+    parent did.
+
+      >>> request._environ['IF'] = '</> (<roottest>) </demo> (<demofalsetest>)'
+      >>> validator.valid(demo, request, None)
+      True
+      >>> matchesIfHeader(root, request)
+      True
+      >>> matchesIfHeader(demo, request)
+      False
+
     Cleanup
     =======
 
@@ -477,6 +583,8 @@
 
     def valid(self, context, request, view):
         stateresults = {}
+        conditionalresult = False
+
         try:
             for resource, conditions in self.get_next_list(request):
                 if resource:
@@ -531,20 +639,18 @@
                     # one or more Lists. They evaluate to true if and only if
                     # any of the contained lists evaluates to true. That is if
                     # listresult is True then the tag-lists are True.
-                    break
-            else:
-                return False
+                    conditionalresult = True
         except ValueError:
             # Error in parsing the IF header, so as with all conditional
             # requests we default to True - that is a valid request.
-            pass
+            conditionalresult = True
 
         # We may have states and entity tags that failed, but we don't want
         # to reparse the if header to figure this out.
         reqannot = zope.annotation.interfaces.IAnnotations(request)
         reqannot[STATE_ANNOTS] = stateresults
 
-        return True
+        return conditionalresult
 
     def invalidStatus(self, context, request, view):
         return 412
@@ -559,16 +665,35 @@
 
 
 def matchesIfHeader(context, request):
-    activelock = zope.component.queryMultiAdapter(
-        (context, request), z3c.dav.coreproperties.IActiveLock, default = None)
-    if activelock is not None:
-        reqannot = zope.annotation.interfaces.IAnnotations(request)
-        return reqannot.get(STATE_ANNOTS, {}).get(
-            zope.traversing.api.getPath(context), {}).get(
-                activelock.locktoken[0], False)
-    return True
+    # Test the state of the context to see if matches the list of states
+    # supplied in the `IF` header.
+    reqannot = zope.annotation.interfaces.IAnnotations(request)
+    stateresults = reqannot.get(STATE_ANNOTS, {})
 
+    islocked = False
+    while context:
+        # We need to specify a None view here. This means that IStateTokens
+        # adapters need to be reqistered for all views. But in some situations
+        # it might be nice to restrict a state token adapter to specific views,
+        # for example is the view's context is a null resource.
+        states = zope.component.queryMultiAdapter(
+            (context, request, None), IStateTokens, default = [])
+        states = states and states.tokens
+        if states:
+            parsedstates = stateresults.get(
+                zope.traversing.api.getPath(context), {})
+            for locktoken in states:
+                if parsedstates.get(locktoken, False):
+                    return True
+            if parsedstates:
+                # If the 
+                return False
+            islocked = True
+        context = getattr(context, "__parent__", None)
 
+    return not islocked
+
+
 class StateTokens(object):
     """
     Default state tokens implementation.
@@ -597,21 +722,27 @@
     is locked.
 
       >>> class Activelock(object):
-      ...    zope.interface.implements(z3c.dav.coreproperties.IActiveLock)
-      ...    zope.component.adapts(IResource,
-      ...        zope.interface.Interface)
       ...    def __init__(self, context, request):
       ...        self.context = context
       ...    locktoken = ['testlocktoken']
+      >>> class Lockdiscovery(object):
+      ...    zope.interface.implements(z3c.dav.coreproperties.IDAVLockdiscovery)
+      ...    zope.component.adapts(IResource, zope.interface.Interface)
+      ...    def __init__(self, context, request):
+      ...        self.context = context
+      ...        self.request = request
+      ...    @property
+      ...    def lockdiscovery(self):
+      ...        return [Activelock(self.context, self.request)]
 
-      >>> zope.component.getGlobalSiteManager().registerAdapter(Activelock)
+      >>> zope.component.getGlobalSiteManager().registerAdapter(Lockdiscovery)
 
       >>> states.tokens
       ['testlocktoken']
 
     Cleanup.
 
-      >>> zope.component.getGlobalSiteManager().unregisterAdapter(Activelock)
+      >>> zope.component.getGlobalSiteManager().unregisterAdapter(Lockdiscovery)
       True
 
     """
@@ -623,10 +754,13 @@
 
     @property
     def tokens(self):
-        activelock = zope.component.queryMultiAdapter(
-            (self.context, self.request), z3c.dav.coreproperties.IActiveLock)
-        if activelock is not None:
-            locktokens = activelock.locktoken
-            if locktokens:
-                return locktokens
+        lockdiscovery = zope.component.queryMultiAdapter(
+            (self.context, self.request),
+            z3c.dav.coreproperties.IDAVLockdiscovery)
+        lockdiscovery = lockdiscovery and lockdiscovery.lockdiscovery
+        if lockdiscovery is not None:
+            for activelock in lockdiscovery:
+                locktokens = activelock.locktoken
+                if locktokens:
+                    return locktokens
         return []

Modified: z3c.dav/trunk/src/z3c/dav/interfaces.py
===================================================================
--- z3c.dav/trunk/src/z3c/dav/interfaces.py	2007-06-07 19:24:13 UTC (rev 76476)
+++ z3c.dav/trunk/src/z3c/dav/interfaces.py	2007-06-07 20:26:30 UTC (rev 76477)
@@ -560,6 +560,8 @@
         recurse through all the subitems locking them has you go.
 
         Raise a AlreadyLockedError if some resource is already locked.
+
+        Return the locktoken associated with the token that we just created.
         """
 
     def refreshlock(timeout):
@@ -567,9 +569,10 @@
         Refresh to lock token associated with this resource.
         """
 
-    def unlock():
+    def unlock(locktoken):
         """
-        Unlock the current context.
+        Find the lock token on this resource with the same locktoken as the
+        argument and unlock it.
         """
 
     def islocked():

Modified: z3c.dav/trunk/src/z3c/dav/locking.py
===================================================================
--- z3c.dav/trunk/src/z3c/dav/locking.py	2007-06-07 19:24:13 UTC (rev 76476)
+++ z3c.dav/trunk/src/z3c/dav/locking.py	2007-06-07 20:26:30 UTC (rev 76477)
@@ -47,7 +47,7 @@
 import z3c.etree
 import z3c.dav.interfaces
 import z3c.dav.properties
-from z3c.dav.coreproperties import IActiveLock, IDAVSupportedlock
+from z3c.dav.coreproperties import IDAVLockdiscovery, IDAVSupportedlock
 import z3c.dav.utils
 import ifvalidator
 
@@ -252,14 +252,11 @@
         refreshlock = False
 
         if self.request.xmlDataSource is None:
-            errors = self.handleLockRefresh()
+            self.handleLockRefresh()
             refreshlock = True
         else: # Body => try to lock the resource
-            errors = self.handleLock()
+            locktoken = self.handleLock()
 
-        if errors:
-            raise z3c.dav.interfaces.WebDAVErrors(self.context, errors)
-
         etree = z3c.etree.getEngine()
 
         davprop, adapter = z3c.dav.properties.getProperty(
@@ -269,14 +266,10 @@
         propel = etree.Element(etree.QName("DAV:", "prop"))
         propel.append(davwidget.render())
 
-        activelock = component.getMultiAdapter((self.context, self.request),
-                                               IActiveLock)
-
         self.request.response.setStatus(200)
         self.request.response.setHeader("Content-Type", "application/xml")
         if not refreshlock:
-            self.request.response.setHeader("Lock-Token",
-                                            "<%s>" % activelock.locktoken[0])
+            self.request.response.setHeader("Lock-Token", "<%s>" % locktoken)
 
         return etree.tostring(propel)
 
@@ -293,7 +286,7 @@
         self.lockmanager.refreshlock(timeout)
 
     def handleLock(self):
-        errors = []
+        locktoken = None
 
         xmlsource = self.request.xmlDataSource
         if xmlsource.tag != "{DAV:}lockinfo":
@@ -339,15 +332,15 @@
             owner_str = None
 
         try:
-            self.lockmanager.lock(scope = lockscope_str,
-                                  type = locktype_str,
-                                  owner = owner_str,
-                                  duration = timeout,
-                                  depth = depth)
+            locktoken = self.lockmanager.lock(scope = lockscope_str,
+                                              type = locktype_str,
+                                              owner = owner_str,
+                                              duration = timeout,
+                                              depth = depth)
         except z3c.dav.interfaces.AlreadyLocked, error:
-            errors.append(error)
+            raise z3c.dav.interfaces.WebDAVErrors(self.context, [error])
 
-        return errors
+        return locktoken
 
 ################################################################################
 #
@@ -393,14 +386,14 @@
                 self.request, message = u"No lock-token header supplied")
 
         activelock = component.getMultiAdapter(
-            (self.context, self.request), IActiveLock)
+            (self.context, self.request), IDAVLockdiscovery).lockdiscovery
         if not self.lockmanager.islocked() or \
-               activelock.locktoken[0] != locktoken:
+               locktoken not in [ltoken.locktoken[0] for ltoken in activelock]:
             raise z3c.dav.interfaces.ConflictError(
                 self.context, message = "object is locked or the lock isn't" \
                                         " in the scope the passed.")
 
-        self.lockmanager.unlock()
+        self.lockmanager.unlock(locktoken)
 
         self.request.response.setStatus(204)
         return ""

Modified: z3c.dav/trunk/src/z3c/dav/locking.txt
===================================================================
--- z3c.dav/trunk/src/z3c/dav/locking.txt	2007-06-07 19:24:13 UTC (rev 76476)
+++ z3c.dav/trunk/src/z3c/dav/locking.txt	2007-06-07 20:26:30 UTC (rev 76477)
@@ -104,7 +104,8 @@
   ...            'duration': duration,
   ...            'depth': depth}
   ...        print "Locked the resource."
-  ...    def unlock(self):
+  ...        return 'urn:resourcelocktoken'
+  ...    def unlock(self, locktoken):
   ...        self.context._lockinfo = None
   ...        print "Unlocked the resource."
 
@@ -209,15 +210,15 @@
 
 Now enough of the errors that can occur, we will now lock the resource.
 
-  >>> errors = LOCK(resource, TestWebDAVRequest(
+  >>> locktoken = LOCK(resource, TestWebDAVRequest(
   ...    body = """<?xml version="1.0" encoding="utf-8" ?>
   ... <D:lockinfo xmlns:D="DAV:">
   ...   <D:lockscope><D:exclusive/></D:lockscope>
   ...   <D:locktype><D:write/></D:locktype>
   ... </D:lockinfo>""")).handleLock()
   Locked the resource.
-  >>> errors
-  []
+  >>> locktoken
+  'urn:resourcelocktoken'
 
   >>> manager = DAVLockmanager(resource)
   >>> manager.islocked()
@@ -230,7 +231,7 @@
   {'owner': None, 'scope': 'exclusive', 'duration': datetime.timedelta(0, 720), 'type': 'write', 'depth': 'infinity'}
   >>> resource._lockinfo = None # unlocks the resource.
 
-  >>> errors = LOCK(resource, TestWebDAVRequest(
+  >>> locktoken = LOCK(resource, TestWebDAVRequest(
   ...    body = """<?xml version="1.0" encoding="utf-8" ?>
   ... <D:lockinfo xmlns:D="DAV:">
   ...   <D:lockscope><D:exclusive/></D:lockscope>
@@ -240,8 +241,8 @@
   ...   </D:owner>
   ... </D:lockinfo>""")).handleLock()
   Locked the resource.
-  >>> errors
-  []
+  >>> locktoken
+  'urn:resourcelocktoken'
   >>> lockinfo = resource._lockinfo
   >>> print lockinfo['owner'] #doctest:+XMLDATA
   <owner xmlns="DAV:">
@@ -260,7 +261,7 @@
 
   >>> resource._lockinfo = None
 
-  >>> errors = LOCK(resource, TestWebDAVRequest(
+  >>> locktoken = LOCK(resource, TestWebDAVRequest(
   ...    environ = {'DEPTH': '0'},
   ...    body = """<?xml version="1.0" encoding="utf-8" ?>
   ... <D:lockinfo xmlns:D="DAV:">
@@ -271,8 +272,8 @@
   ...   </D:owner>
   ... </D:lockinfo>""")).handleLock()
   Locked the resource.
-  >>> errors
-  []
+  >>> locktoken
+  'urn:resourcelocktoken'
   >>> lockinfo = resource._lockinfo
   >>> print lockinfo['owner'] #doctest:+XMLDATA
   <owner xmlns="DAV:">
@@ -290,7 +291,7 @@
 Now if the resource is already locked then the `handleLock` returns an
 `z3c.dav.interfaces.AlreadyLocked` error.
 
-  >>> errors = LOCK(resource, TestWebDAVRequest(
+  >>> LOCK(resource, TestWebDAVRequest(
   ...    body = """<?xml version="1.0" encoding="utf-8" ?>
   ... <D:lockinfo xmlns:D="DAV:">
   ...   <D:lockscope><D:exclusive/></D:lockscope>
@@ -299,8 +300,9 @@
   ...     <D:href>http://example.org/~ejw/contact.html</D:href>
   ...   </D:owner>
   ... </D:lockinfo>""")).handleLock()
-  >>> errors #doctest:+ELLIPSIS
-  [<z3c.dav.interfaces.AlreadyLocked instance at ...>]
+  Traceback (most recent call last):
+  ...
+  WebDAVErrors
 
 LOCK
 ----



More information about the Checkins mailing list