12 |
12 |
13 __docformat__ = 'restructuredtext' |
13 __docformat__ = 'restructuredtext' |
14 |
14 |
15 |
15 |
16 # import standard library |
16 # import standard library |
17 from http.client import NOT_MODIFIED |
17 from http.client import NOT_MODIFIED, PARTIAL_CONTENT |
18 |
18 |
19 # import interfaces |
19 # import interfaces |
20 from pyams_file.interfaces import IFile, IMediaFile |
20 from pyams_file.interfaces import IFile |
21 from zope.dublincore.interfaces import IZopeDublinCore |
21 from zope.dublincore.interfaces import IZopeDublinCore |
22 |
22 |
23 # import packages |
23 # import packages |
24 from pyramid.response import Response |
24 from pyramid.response import Response |
25 from pyramid.view import view_config |
25 from pyramid.view import view_config |
|
26 |
|
27 |
|
28 MAX_RANGE_LENGTH = 1 << 21 # 2 Mb |
26 |
29 |
27 |
30 |
28 @view_config(context=IFile) |
31 @view_config(context=IFile) |
29 def FileView(request): |
32 def FileView(request): |
30 """Default file view""" |
33 """Default file view""" |
36 if if_modified_since and (int(modified.timestamp()) <= int(if_modified_since.timestamp())): |
39 if if_modified_since and (int(modified.timestamp()) <= int(if_modified_since.timestamp())): |
37 return Response(content_type=context.content_type.decode('utf-8'), |
40 return Response(content_type=context.content_type.decode('utf-8'), |
38 status=NOT_MODIFIED) |
41 status=NOT_MODIFIED) |
39 |
42 |
40 response = Response(content_type=context.content_type.decode('utf-8'), |
43 response = Response(content_type=context.content_type.decode('utf-8'), |
41 content_disposition='attachment; filename="{0}"'.format(context.filename), |
|
42 last_modified=modified) |
44 last_modified=modified) |
43 response.body_file = context.get_blob(mode='c') |
45 body_file = context.get_blob(mode='c') |
44 return response |
|
45 |
46 |
46 |
|
47 @view_config(context=IMediaFile) |
|
48 def MediaFileView(request): |
|
49 """Default media file view""" |
|
50 context = request.context |
|
51 |
|
52 # check for last modification date |
|
53 modified = IZopeDublinCore(context).modified |
|
54 if_modified_since = request.if_modified_since |
|
55 if if_modified_since and (int(modified.timestamp()) <= int(if_modified_since.timestamp())): |
|
56 return Response(content_type=context.content_type.decode('utf-8'), |
|
57 status=NOT_MODIFIED) |
|
58 |
|
59 response = Response(content_type=context.content_type.decode('utf-8'), |
|
60 last_modified=modified) |
|
61 if request.params.get('download') is not None: |
47 if request.params.get('download') is not None: |
62 response.content_disposition = 'attachment; filename="{0}"'.format(context.filename) |
48 response.content_disposition = 'attachment; filename="{0}"'.format(context.filename) |
63 response.body_file = context.get_blob(mode='c') |
49 |
|
50 if request.range is not None: |
|
51 body = body_file.read() |
|
52 body_length = len(body) |
|
53 range_start = request.range.start or 0 |
|
54 if 'Firefox' in request.user_agent: # avoid partial range for Firefox videos |
|
55 range_end = body_length |
|
56 else: |
|
57 range_end = request.range.end or min(body_length, range_start + MAX_RANGE_LENGTH) |
|
58 ranged_body = body[range_start:range_end] |
|
59 response.status = PARTIAL_CONTENT |
|
60 response.headers['Content-Range'] = 'bytes {first}-{last}/{len}'.format(first=range_start, |
|
61 last=range_start + len(ranged_body) - 1, |
|
62 len=body_length) |
|
63 response.body = ranged_body |
|
64 else: |
|
65 response.body_file = body_file |
|
66 |
64 return response |
67 return response |