doc webdav: move to the node_descriptor API

We need to support chunked transfers, as well as Ranges, so we should no
longer use the node.get_data() but node.open_data('r').

bzr revid: p_christ@hol.gr-20101103112535-34sr4dhd3neunfv1
This commit is contained in:
P. Christeas
2010-11-03 13:25:35 +02:00
parent 7f882e14cf
commit 1c5c26a8af
3 changed files with 114 additions and 6 deletions
+105 -4
View File
@@ -24,6 +24,7 @@ import base64
import sys
import os
import time
import errno
from string import joinfields, split, lower
import netsvc
@@ -74,6 +75,89 @@ def _str2time(cre):
cre = cre[:fdot]
return time.mktime(time.strptime(cre,'%Y-%m-%d %H:%M:%S')) + frac
class BoundStream2(object):
"""Wraps around a seekable buffer, reads a determined range of data
Note that the supplied stream object MUST support a size() which
should return its data length (in bytes).
A variation of the class in websrv_lib.py
"""
def __init__(self, stream, offset=None, length=None, chunk_size=None):
self._stream = stream
self._offset = offset or 0
self._length = length or self._stream.size()
self._rem_length = length
assert length and isinstance(length, (int, long))
assert length and length >= 0, length
self._chunk_size = chunk_size
if offset is not None:
self._stream.seek(offset)
def read(self, size=-1):
if not self._stream:
raise IOError(errno.EBADF, "read() without stream")
if self._rem_length == 0:
return ''
elif self._rem_length < 0:
raise EOFError()
rsize = self._rem_length
if size > 0 and size < rsize:
rsize = size
if self._chunk_size and self._chunk_size < rsize:
rsize = self._chunk_size
data = self._stream.read(rsize)
self._rem_length -= len(data)
return data
def __len__(self):
return self._length
def tell(self):
res = self._stream.tell()
if self._offset:
res -= self._offset
return res
def __iter__(self):
return self
def next(self):
return self.read(65536)
def seek(self, pos, whence=os.SEEK_SET):
""" Seek, computing our limited range
"""
if whence == os.SEEK_SET:
if pos < 0 or pos > self._length:
raise IOError(errno.EINVAL,"Cannot seek")
self._stream.seek(pos - self._offset)
self._rem_length = self._length - pos
elif whence == os.SEEK_CUR:
if pos > 0:
if pos > self._rem_length:
raise IOError(errno.EINVAL,"Cannot seek past end")
elif pos < 0:
oldpos = self.tell()
if oldpos + pos < 0:
raise IOError(errno.EINVAL,"Cannot seek before start")
self._stream.seek(pos, os.SEEK_CUR)
self._rem_length -= pos
elif whence == os.SEEK_END:
if pos > 0:
raise IOError(errno.EINVAL,"Cannot seek past end")
else:
if self._length + pos < 0:
raise IOError(errno.EINVAL,"Cannot seek before start")
newpos = self._offset + self._length + pos
self._stream.seek(newpos, os.SEEK_SET)
self._rem_length = 0 - pos
class openerp_dav_handler(dav_interface):
"""
This class models a OpenERP interface for the DAV server
@@ -396,11 +480,28 @@ class openerp_dav_handler(dav_interface):
node = self.uri2object(cr, uid, pool, uri2)
if not node:
raise DAV_NotFound2(uri2)
# TODO: if node is a collection, for some specific set of
# clients ( web browsers; available in node context),
# we may return a pseydo-html page with the directory listing.
try:
res = node.open_data(cr,'r')
if rrange:
self.parent.log_error("Doc get_data cannot use range")
raise DAV_Error(409)
datas = node.get_data(cr)
assert isinstance(rrange, (tuple,list))
start, end = map(long, rrange)
if not start:
start = 0
assert start >= 0
if end and end < start:
self.parent.log_error("Invalid range for data: %s-%s" %(start, end))
raise DAV_Error(416, "Invalid range for data")
if end:
if end >= res.size():
raise DAV_Error(416, "Requested data exceeds available size")
length = (end + 1) - start
else:
length = res.size() - start
res = BoundStream2(res, offset=start, length=length)
except TypeError,e:
# for the collections that return this error, the DAV standard
# says we'd better just return 200 OK with empty data
@@ -413,7 +514,7 @@ class openerp_dav_handler(dav_interface):
self.parent.log_error("GET exception: %s",str(e))
self.parent.log_message("Exc: %s", traceback.format_exc())
raise DAV_Error, 409
return str(datas) # FIXME!
return res
finally:
if cr: cr.close()
+1 -1
View File
@@ -56,4 +56,4 @@
dc = te.DAVClient()
dc.get_creds(self, cr, uid)
tdp = addons.get_module_resource('document_webdav', 'test_davclient.py')
res = dc.gd_get(path=cr.dbname+'/Documents/test_davclient.py', compare=tdp)
res = dc.gd_get(path=cr.dbname+'/Documents/test_davclient.py', crange=(4,508), compare=tdp)
+8 -1
View File
@@ -654,7 +654,9 @@ class DAVClient(object):
hdrs['Range'] = 'bytes='+ (','.join(rs))
s, m, d = self._http_request(self.davpath + path, method='GET', hdrs=hdrs)
assert s in (200, 206), "Bad status: %s" % s
ctype = m.getheader('Content-Type').split(';',1)[0]
ctype = m.getheader('Content-Type')
if ctype and ';' in ctype:
ctype = ctype.split(';',1)[0]
if mime:
assert ctype == mime, m.getheader('Content-Type')
rrange = None
@@ -668,6 +670,11 @@ class DAVClient(object):
fd = open(compare, 'rb')
d2 = fd.read()
fd.close()
if crange:
if len(crange) > 1:
raise NotImplementedError
r = crange[0]
d2 = d2[r[0]:r[1]+1]
assert d2 == d, "Data does not match"
return ctype, rrange, d