summaryrefslogtreecommitdiff
path: root/addons/document_webdav/nodes.py
diff options
context:
space:
mode:
authorP. Christeas <p_christ@hol.gr>2010-12-07 15:40:41 +0200
committerP. Christeas <p_christ@hol.gr>2010-12-07 15:40:41 +0200
commit6b2c832f5f9cc1667cd50230f8f0a0139d352201 (patch)
tree61e04e42c975e98e93e061bd6c7b2f50e6cfb2cd /addons/document_webdav/nodes.py
parent9a4c99980315d80e2da5a3af9a1d870bde4cf734 (diff)
doc webdav: Implement locking support
This replaces the pseydo-locking of python-webdav library with a real db-based locking. Locks are stored as DAV properties, which will effectively also be listed in the PROPFIND response of the nodes. With locking in place, Office suites can collaborate on documents online. bzr revid: p_christ@hol.gr-20101207134041-8negkvxrbscv7fs7
Diffstat (limited to 'addons/document_webdav/nodes.py')
-rw-r--r--addons/document_webdav/nodes.py197
1 files changed, 190 insertions, 7 deletions
diff --git a/addons/document_webdav/nodes.py b/addons/document_webdav/nodes.py
index 8bea54ab7d3..fc48a47056a 100644
--- a/addons/document_webdav/nodes.py
+++ b/addons/document_webdav/nodes.py
@@ -22,14 +22,14 @@
from document import nodes
from tools.safe_eval import safe_eval as eval
+import time
+import urllib
+import uuid
try:
from tools.dict_tools import dict_filter
except ImportError:
from document.dict_tools import dict_filter
-import urllib
-
-
class node_acl_mixin(object):
def _get_dav_owner(self, cr):
return self.uuser
@@ -116,6 +116,153 @@ class node_acl_mixin(object):
return val
return None
+ def _dav_lock_hlpr(self, cr, lock_data, par_class, prop_model,
+ prop_ref_field, res_id):
+ """ Helper, which uses the dav properties table for placing locks
+
+ @param lock_data a dictionary of input to this function.
+ @return list of tuples, DAV:activelock _contents_ structure.
+ See webdav.py:class Prop2Xml() for semantics
+
+ Note: although the DAV response shall be an <activelock/>, this
+ function will only return the elements inside the activelock,
+ because the calling function needs to append the <lockroot/> in
+ it. See webdav.py:mk_lock_response()
+
+ In order to reuse code, this function can be called with
+ lock_data['unlock_mode']=True, in order to unlock.
+
+ @return bool in unlock mode, (davstruct, prop_id, token) in lock/refresh,
+ or (False, prop_id, token) if already locked,
+ or (False, False, False) if lock not found to refresh
+ """
+ assert prop_model
+ assert res_id
+ assert isinstance(lock_data, dict), '%r' % lock_data
+ propobj = self.context._dirobj.pool.get(prop_model)
+ uid = self.context.uid
+ ctx = self.context.context.copy()
+ ctx.update(self.dctx)
+ ctx.update({'uid': uid, 'dbname': self.context.dbname })
+ ctx['node_classname'] = "%s.%s" % (self.__class__.__module__, self.__class__.__name__)
+ dict_filter(self.context.extra_ctx, ['username', 'groupname', 'webdav_path'], ctx)
+ sdomain = [(prop_ref_field, '=', res_id), ('namespace', '=', 'DAV:'),
+ ('name','=', 'lockdiscovery')]
+ props_to_delete = []
+ lock_found = False
+ lock_val = None
+ tmout2 = int(lock_data.get('timeout', 3*3600))
+
+ prop_ids = propobj.search(cr, uid, sdomain, context=ctx)
+ if prop_ids:
+ for pbro in propobj.browse(cr, uid, prop_ids, context=ctx):
+ val = pbro.value
+ if pbro.do_subst:
+ if val.startswith("('") and val.endswith(")"):
+ glbls = { 'urlquote': urllib.quote, }
+ val = eval(val, glbls, ctx)
+ else:
+ # all locks should be at "subst" format
+ continue
+ if not (val and isinstance(val, tuple)
+ and val[0:2] == ( 'activelock','DAV:')):
+ # print "Value is not activelock:", val
+ continue
+
+ old_token = False
+ old_owner = False
+ try:
+ # discover the timeout. If anything goes wrong, delete
+ # the lock (cleanup)
+ tmout = False
+ for parm in val[2]:
+ if parm[1] != 'DAV:':
+ continue
+ if parm[0] == 'timeout':
+ if isinstance(parm[2], basestring) \
+ and parm[2].startswith('Second-'):
+ tmout = int(parm[2][7:])
+ elif parm[0] == 'locktoken':
+ if isinstance(parm[2], basestring):
+ old_token = parm[2]
+ elif isinstance(parm[2], tuple) and \
+ parm[2][0:2] == ('href','DAV:'):
+ old_token = parm[2][2]
+ else:
+ # print "Mangled token in DAV property: %r" % parm[2]
+ props_to_delete.append(pbro.id)
+ continue
+ elif parm[0] == 'owner':
+ old_owner = parm[2] # not used yet
+ if tmout:
+ mdate = pbro.write_date or pbro.create_date
+ mdate = time.mktime(time.strptime(mdate,'%Y-%m-%d %H:%M:%S'))
+ if mdate + tmout < time.time():
+ props_to_delete.append(pbro.id)
+ continue
+ else:
+ props_to_delete.append(pbro.id)
+ continue
+ except ValueError:
+ props_to_delete.append(pbro.id)
+ continue
+
+ # A valid lock is found here
+ if lock_data.get('refresh', False):
+ if old_token != lock_data.get('token'):
+ continue
+ # refresh mode. Just touch anything and the ORM will update
+ # the write uid+date, won't it?
+ # Note: we don't update the owner, because incoming refresh
+ # wouldn't have a body, anyway.
+ propobj.write(cr, uid, [pbro.id,], { 'name': 'lockdiscovery'})
+ elif lock_data.get('unlock_mode', False):
+ if old_token != lock_data.get('token'):
+ continue
+ props_to_delete.append(pbro.id)
+
+ lock_found = pbro.id
+ lock_val = val
+
+ if tmout2 > 3*3600: # 3 hours maximum
+ tmout2 = 3*3600
+ elif tmout2 < 300:
+ # 5 minutes minimum, but an unlock request can always
+ # break it at any time. Ensures no negative values, either.
+ tmout2 = 300
+
+ if props_to_delete:
+ # explicitly delete, as admin, any of the ids we have identified.
+ propobj.unlink(cr, 1, props_to_delete)
+
+ if lock_data.get('unlock_mode', False):
+ return lock_found and True
+ elif (not lock_found) and not (lock_data.get('refresh', False)):
+ # Create a new lock, attach and return it.
+ new_token = uuid.uuid4().urn
+ lock_val = ('activelock', 'DAV:',
+ [ ('locktype', 'DAV:', (lock_data.get('locktype',False) or 'write','DAV:')),
+ ('lockscope', 'DAV:', (lock_data.get('lockscope',False) or 'exclusive','DAV:')),
+ # ? ('depth', 'DAV:', lock_data.get('depth','0') ),
+ ('timeout','DAV:', 'Second-%d' % tmout2),
+ ('locktoken', 'DAV:', ('href', 'DAV:', new_token)),
+ # ('lockroot', 'DAV: ..., we don't store that, appended by caller
+ ])
+ new_owner = lock_data.get('lockowner',False) or ctx.get('username', False)
+ if new_owner:
+ lock_val[2].append( ('owner', 'DAV:', new_owner) )
+ prop_id = propobj.create(cr, uid, { prop_ref_field: res_id,
+ 'namespace': 'DAV:', 'name': 'lockdiscovery',
+ 'do_subst': True, 'value': repr(lock_val) })
+ return (lock_val[2], prop_id, new_token )
+ elif not lock_found: # and refresh
+ return (False, False, False)
+ elif lock_found and not lock_data.get('refresh', False):
+ # already locked
+ return (False, lock_found, old_token)
+ else:
+ return (lock_val[2], lock_found, old_token )
+
class node_dir(node_acl_mixin, nodes.node_dir):
""" override node_dir and add DAV functionality
"""
@@ -141,7 +288,8 @@ class node_dir(node_acl_mixin, nodes.node_dir):
class node_file(node_acl_mixin, nodes.node_file):
DAV_PROPS = { "DAV:": ('owner', 'group',
'supported-privilege-set',
- 'current-user-privilege-set'),
+ 'current-user-privilege-set',
+ ),
}
DAV_M_NS = { "DAV:" : '_get_dav',}
http_options = { 'DAV': ['access-control', ] }
@@ -152,10 +300,45 @@ class node_file(node_acl_mixin, nodes.node_file):
def get_dav_props(self, cr):
return self._get_dav_props_hlpr(cr, nodes.node_dir,
- None, 'file_id', self.file_id)
- #'document.webdav.dir.property', 'dir_id', self.dir_id)
+ 'document.webdav.file.property', 'file_id', self.file_id)
- #def get_dav_eprop(self, cr, ns, prop):
+ def dav_lock(self, cr, lock_data):
+ """ Locks or unlocks the node, using DAV semantics.
+
+ Unlocking will be done when lock_data['unlock_mode'] == True
+
+ See _dav_lock_hlpr() for calling details.
+
+ It is fundamentally OK to use this function from non-DAV endpoints,
+ but they will all have to emulate the tuple-in-list structure of
+ the DAV lock data. RFC if this translation should be done inside
+ the _dav_lock_hlpr (to ease other protocols).
+ """
+ return self._dav_lock_hlpr(cr, lock_data, nodes.node_file,
+ 'document.webdav.file.property', 'file_id', self.file_id)
+
+ def dav_unlock(self, cr, token):
+ """Releases the token lock held for the node
+
+ This is a utility complement of dav_lock()
+ """
+ lock_data = { 'token': token, 'unlock_mode': True }
+ return self._dav_lock_hlpr(cr, lock_data, nodes.node_file,
+ 'document.webdav.file.property', 'file_id', self.file_id)
+
+ def get_dav_eprop(self, cr, ns, prop):
+ if ns == 'DAV:' and prop == 'supportedlock':
+ return [ ('lockentry', 'DAV:',
+ [ ('lockscope','DAV:', ('shared', 'DAV:')),
+ ('locktype','DAV:', ('write', 'DAV:')),
+ ]),
+ ('lockentry', 'DAV:',
+ [ ('lockscope','DAV:', ('exclusive', 'DAV:')),
+ ('locktype','DAV:', ('write', 'DAV:')),
+ ] )
+ ]
+ return self._get_dav_eprop_hlpr(cr, ns, prop, nodes.node_file,
+ 'document.webdav.file.property', 'file_id', self.file_id)
class node_database(nodes.node_database):
def get_dav_resourcetype(self, cr):