Commit b1589abf authored by Ivan Tyagov's avatar Ivan Tyagov

Merge branch 'master' into 'master'

DataArray: get array slice by HTTP Range Request

See merge request !5
parents 049e87e5 c2481a75
...@@ -30,6 +30,11 @@ from AccessControl import ClassSecurityInfo ...@@ -30,6 +30,11 @@ from AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions, PropertySheet from Products.ERP5Type import Permissions, PropertySheet
from Products.ERP5.Document.BigFile import BigFile from Products.ERP5.Document.BigFile import BigFile
from wendelin.bigarray.array_zodb import ZBigArray from wendelin.bigarray.array_zodb import ZBigArray
from Products.ERP5.Document.File import _MARKER
from ZPublisher import HTTPRangeSupport
from webdav.common import rfc1123_date
from DateTime import DateTime
from mimetools import choose_boundary
import transaction import transaction
class DataArray(BigFile): class DataArray(BigFile):
...@@ -69,16 +74,150 @@ class DataArray(BigFile): ...@@ -69,16 +74,150 @@ class DataArray(BigFile):
Set numpy array to this ERP5 Data Array. Set numpy array to this ERP5 Data Array.
""" """
self.array = value self.array = value
# ZBigArray requirement: before we can compute it (with subobject # ZBigArray requirement: before we can compute it (with subobject
# .zfile) have to be made explicitly known to connection or current # .zfile) have to be made explicitly known to connection or current
# transaction committed (XXX: impossible to use as raises ConflictErrors) # transaction committed (XXX: impossible to use as raises ConflictErrors)
transaction.commit() transaction.commit()
def getArraySlice(self, start, end): def getArraySlice(self, start, end):
""" """
Implement array slicing in its most simple list alike form. Implement array slicing in its most simple list alike form.
Any other advanced slicing techniques currently possible by getting Any other advanced slicing techniques currently possible by getting
array reference directly. array reference directly.
""" """
return self.getArray()[start:end] return self.getArray()[start:end]
\ No newline at end of file
security.declareProtected(Permissions.AccessContentsInformation, 'getSize')
def getSize(self, default=None):
"""
Implement getSize interface for ndarray
"""
return self.getArray().nbytes
security.declareProtected(Permissions.View, 'index_html')
def index_html(self, REQUEST, RESPONSE, format=_MARKER, inline=_MARKER, **kw):
"""
Support streaming
"""
if self._range_request_handler(REQUEST, RESPONSE):
# we served a chunk of content in response to a range request.
return ''
return ''
def _range_request_handler(self, REQUEST, RESPONSE):
RESPONSE.setHeader("Content-Type", "application/octet-stream")
# HTTP Range header handling: return True if we've served a range
# chunk out of our data.
range = REQUEST.get_header('Range', None)
request_range = REQUEST.get_header('Request-Range', None)
if request_range is not None:
# Netscape 2 through 4 and MSIE 3 implement a draft version
# Later on, we need to serve a different mime-type as well.
range = request_range
if_range = REQUEST.get_header('If-Range', None)
if range is not None:
ranges = HTTPRangeSupport.parseRange(range)
# get byte view of array because we interpret ranges in bytes
data = self.getArray()[:].view("uint8").ravel()
if if_range is not None:
# Only send ranges if the data isn't modified, otherwise send
# the whole object. Support both ETags and Last-Modified dates!
if len(if_range) > 1 and if_range[:2] == 'ts':
# ETag:
if if_range != self.http__etag():
# Modified, so send a normal response. We delete
# the ranges, which causes us to skip to the 200
# response.
ranges = None
else:
# Date
date = if_range.split( ';')[0]
try: mod_since=long(DateTime(date).timeTime())
except: mod_since=None
if mod_since is not None:
last_mod = self._data_mtime()
if last_mod is None:
last_mod = 0
last_mod = long(last_mod)
if last_mod > mod_since:
# Modified, so send a normal response. We delete
# the ranges, which causes us to skip to the 200
# response.
ranges = None
if ranges:
# Search for satisfiable ranges.
satisfiable = 0
for start, end in ranges:
if start < self.getSize():
satisfiable = 1
break
if not satisfiable:
RESPONSE.setHeader('Content-Range',
'bytes */%d' % self.getSize())
RESPONSE.setHeader('Accept-Ranges', 'bytes')
RESPONSE.setHeader('Last-Modified', rfc1123_date(self._data_mtime()))
RESPONSE.setHeader('Content-Type', self.content_type)
RESPONSE.setHeader('Content-Length', self.getSize())
RESPONSE.setStatus(416)
return True
ranges = HTTPRangeSupport.expandRanges(ranges, self.getSize())
if len(ranges) == 1:
# Easy case, set extra header and return partial set.
start, end = ranges[0]
size = end - start
RESPONSE.setHeader('Last-Modified', rfc1123_date(self._data_mtime()))
RESPONSE.setHeader('Content-Type', self.content_type)
RESPONSE.setHeader('Content-Length', size)
RESPONSE.setHeader('Accept-Ranges', 'bytes')
RESPONSE.setHeader('Content-Range',
'bytes %d-%d/%d' % (start, end - 1, self.getSize()))
RESPONSE.setStatus(206) # Partial content
RESPONSE.write(data[start:end].tobytes())
else:
boundary = choose_boundary()
# Calculate the content length
size = (8 + len(boundary) + # End marker length
len(ranges) * ( # Constant lenght per set
49 + len(boundary) + len(self.content_type) +
len('%d' % self.getSize())))
for start, end in ranges:
# Variable length per set
size = (size + len('%d%d' % (start, end - 1)) +
end - start)
# Some clients implement an earlier draft of the spec, they
# will only accept x-byteranges.
draftprefix = (request_range is not None) and 'x-' or ''
RESPONSE.setHeader('Content-Length', size)
RESPONSE.setHeader('Accept-Ranges', 'bytes')
RESPONSE.setHeader('Last-Modified', rfc1123_date(self._data_mtime()))
RESPONSE.setHeader('Content-Type',
'multipart/%sbyteranges; boundary=%s' % (
draftprefix, boundary))
RESPONSE.setStatus(206) # Partial content
for start, end in ranges:
RESPONSE.write('\r\n--%s\r\n' % boundary)
RESPONSE.write('Content-Type: %s\r\n' %
self.content_type)
RESPONSE.write(
'Content-Range: bytes %d-%d/%d\r\n\r\n' % (
start, end - 1, self.getSize()))
RESPONSE.write(data[start:end].tobytes())
RESPONSE.write('\r\n--%s--\r\n' % boundary)
return True
...@@ -45,7 +45,11 @@ ...@@ -45,7 +45,11 @@
<item> <item>
<key> <string>text_content_warning_message</string> </key> <key> <string>text_content_warning_message</string> </key>
<value> <value>
<tuple/> <tuple>
<string>W: 99, 42: Redefining built-in \'format\' (redefined-builtin)</string>
<string>W:113, 4: Redefining built-in \'range\' (redefined-builtin)</string>
<string>W:140, 10: No exception type(s) specified (bare-except)</string>
</tuple>
</value> </value>
</item> </item>
<item> <item>
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment