src/pyams_file/views/file.py
changeset 27 792822dde705
parent 22 3a1769d9a4e2
child 38 b1f7b1173aa3
equal deleted inserted replaced
26:8eb284fba75a 27:792822dde705
    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