[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