async def download_folder_as_zip(self): zipfile_name = self.path.name or '{}-archive'.format(self.provider.NAME) self.set_header('Content-Type', 'application/zip') self.set_header('Content-Disposition', make_disposition(zipfile_name + '.zip')) result = await self.provider.zip(self.path) await self.write_stream(result)
async def download_folder_as_zip(self): zipfile_name = self.path.name or '{}-archive'.format( self.provider.NAME) self.set_header('Content-Type', 'application/zip') self.set_header('Content-Disposition', make_disposition(zipfile_name + '.zip')) result = await self.provider.zip(self.path) await self.write_stream(result)
async def download_file(self): if 'Range' not in self.request.headers: request_range = None else: logger.debug('Range header is: {}'.format(self.request.headers['Range'])) request_range = utils.parse_request_range(self.request.headers['Range']) logger.debug('Range header parsed as: {}'.format(request_range)) version = self.requested_version stream = await self.provider.download( self.path, revision=version, range=request_range, accept_url='direct' not in self.request.query_arguments, mode=self.get_query_argument('mode', default=None), display_name=self.get_query_argument('displayName', default=None), ) if isinstance(stream, str): return self.redirect(stream) if getattr(stream, 'partial', None): # Use getattr here as not all stream may have a partial attribute # Plus it fixes tests self.set_status(206) self.set_header('Content-Range', stream.content_range) if stream.content_type is not None: self.set_header('Content-Type', stream.content_type) logger.debug('stream size is: {}'.format(stream.size)) if stream.size is not None: self.set_header('Content-Length', str(stream.size)) # Build `Content-Disposition` header from `displayName` override, # headers of provider response, or file path, whichever is truthy first name = ( self.get_query_argument('displayName', default=None) or getattr(stream, 'name', None) or self.path.name ) self.set_header('Content-Disposition', make_disposition(name)) _, ext = os.path.splitext(name) # If the file extention is in mime_types # override the content type to fix issues with safari shoving in new file extensions if ext in mime_types: self.set_header('Content-Type', mime_types[ext]) await self.write_stream(stream) if getattr(stream, 'partial', False) and isinstance(stream, ResponseStreamReader): await stream.response.release() logger.debug('bytes received is: {}'.format(self.bytes_downloaded))
async def download_file(self): if 'Range' not in self.request.headers: request_range = None else: logger.debug('Range header is: {}'.format( self.request.headers['Range'])) request_range = utils.parse_request_range( self.request.headers['Range']) logger.debug('Range header parsed as: {}'.format(request_range)) version = self.requested_version stream = await self.provider.download( self.path, revision=version, range=request_range, accept_url='direct' not in self.request.query_arguments, mode=self.get_query_argument('mode', default=None), display_name=self.get_query_argument('displayName', default=None), ) if isinstance(stream, str): return self.redirect(stream) if getattr(stream, 'partial', None): # Use getattr here as not all stream may have a partial attribute # Plus it fixes tests self.set_status(206) self.set_header('Content-Range', stream.content_range) if stream.content_type is not None: self.set_header('Content-Type', stream.content_type) logger.debug('stream size is: {}'.format(stream.size)) if stream.size is not None: self.set_header('Content-Length', str(stream.size)) # Build `Content-Disposition` header from `displayName` override, # headers of provider response, or file path, whichever is truthy first name = (self.get_query_argument('displayName', default=None) or getattr(stream, 'name', None) or self.path.name) self.set_header('Content-Disposition', make_disposition(name)) _, ext = os.path.splitext(name) # If the file extention is in mime_types # override the content type to fix issues with safari shoving in new file extensions if ext in mime_types: self.set_header('Content-Type', mime_types[ext]) await self.write_stream(stream) if getattr(stream, 'partial', False) and isinstance( stream, ResponseStreamReader): await stream.response.release() logger.debug('bytes received is: {}'.format(self.bytes_downloaded))
async def download( self, path: WaterButlerPath, accept_url=False, range=None, # type: ignore **kwargs) -> typing.Union[str, ResponseStreamReader]: """Download the object with the given path. API Docs: GET Object: https://cloud.google.com/storage/docs/xml-api/get-object Download an Object: https://cloud.google.com/storage/docs/xml-api/get-object-download The behavior of download differs depending on the value of ``accept_url``. If ``accept_url == False``, WB makes a standard signed request and returns a ``ResponseStreamReader``. If ``accept_url == True``, WB builds and signs the ``GET`` request with an extra query parameter ``response-content-disposition`` to trigger the download with the display name. The signed URL is returned. :param path: the WaterButlerPath for the object to download :type path: :class:`.WaterButlerPath` :param bool accept_url: should return a direct time-limited download url from the provider :param tuple range: the Range HTTP request header :param dict kwargs: ``display_name`` - the display name of the file on OSF and for download :rtype: str or :class:`.streams.ResponseStreamReader` """ if path.is_folder: raise DownloadError('Cannot download folders', code=HTTPStatus.BAD_REQUEST) req_method = 'GET' obj_name = utils.get_obj_name(path, is_folder=False) if accept_url: display_name = kwargs.get('display_name') or path.name query = { 'response-content-disposition': make_disposition(display_name) } # There is no need to delay URL building and signing signed_url = self._build_and_sign_url(req_method, obj_name, **query) # type: ignore return signed_url signed_url = functools.partial(self._build_and_sign_url, req_method, obj_name, **{}) resp = await self.make_request(req_method, signed_url, range=range, expects=(HTTPStatus.OK, HTTPStatus.PARTIAL_CONTENT), throws=DownloadError) return ResponseStreamReader(resp)
async def download(self, path, accept_url=False, revision=None, range=None, **kwargs): """Returns a ResponseWrapper (Stream) for the specified path raises FileNotFoundError if the status from S3 is not 200 :param str path: Path to the key you want to download :param dict \*\*kwargs: Additional arguments that are ignored :rtype: :class:`waterbutler.core.streams.ResponseStreamReader` :raises: :class:`waterbutler.core.exceptions.DownloadError` """ await self._check_region() if not path.is_file: raise exceptions.DownloadError('No file specified for download', code=400) if not revision or revision.lower() == 'latest': query_parameters = None else: query_parameters = {'versionId': revision} display_name = kwargs.get('display_name') or path.name response_headers = { 'response-content-disposition': make_disposition(display_name) } url = functools.partial(self.bucket.new_key(path.path).generate_url, settings.TEMP_URL_SECS, query_parameters=query_parameters, response_headers=response_headers) if accept_url: return url() resp = await self.make_request( 'GET', url, range=range, expects=( 200, 206, ), throws=exceptions.DownloadError, ) return streams.ResponseStreamReader(resp)
async def get(self): """Download as a Zip archive.""" zipfile_name = self.path.name or '{}-archive'.format(self.provider.NAME) self.set_header('Content-Type', 'application/zip') self.set_header( 'Content-Disposition', make_disposition(zipfile_name + '.zip') ) result = await self.provider.zip(**self.arguments) await self.write_stream(result) self._send_hook('download_zip', path=self.path)
async def get(self): """Download a file.""" try: self.arguments['accept_url'] = TRUTH_MAP[self.arguments.get( 'accept_url', 'true').lower()] except KeyError: raise tornado.web.HTTPError(status_code=400) if 'Range' in self.request.headers: request_range = utils.parse_request_range( self.request.headers['Range']) else: request_range = None result = await self.provider.download(range=request_range, **self.arguments) if isinstance(result, str): self.redirect(result) self._send_hook('download_file', path=self.path) return if getattr(result, 'partial', None): # Use getattr here as not all stream may have a partial attribute # Plus it fixes tests self.set_status(206) self.set_header('Content-Range', result.content_range) if result.content_type is not None: self.set_header('Content-Type', result.content_type) if result.size is not None: self.set_header('Content-Length', str(result.size)) # Build `Content-Disposition` header from `displayName` override, # headers of provider response, or file path, whichever is truthy first name = self.arguments.get('displayName') or getattr( result, 'name', None) or self.path.name self.set_header('Content-Disposition', make_disposition(name)) _, ext = os.path.splitext(name) # If the file extention is in mime_types # override the content type to fix issues with safari shoving in new file extensions if ext in mime_types: self.set_header('Content-Type', mime_types[ext]) await self.write_stream(result) self._send_hook('download_file', path=self.path)
async def download(self, path: WaterButlerPath, accept_url=False, range=None, # type: ignore **kwargs) -> typing.Union[str, ResponseStreamReader]: """Download the object with the given path. API Docs: GET Object: https://cloud.google.com/storage/docs/xml-api/get-object Download an Object: https://cloud.google.com/storage/docs/xml-api/get-object-download The behavior of download differs depending on the value of ``accept_url``. If ``accept_url == False``, WB makes a standard signed request and returns a ``ResponseStreamReader``. If ``accept_url == True``, WB builds and signs the ``GET`` request with an extra query parameter ``response-content-disposition`` to trigger the download with the display name. The signed URL is returned. :param path: the WaterButlerPath for the object to download :type path: :class:`.WaterButlerPath` :param bool accept_url: should return a direct time-limited download url from the provider :param tuple range: the Range HTTP request header :param dict kwargs: ``display_name`` - the display name of the file on OSF and for download :rtype: str or :class:`.streams.ResponseStreamReader` """ if path.is_folder: raise DownloadError('Cannot download folders', code=HTTPStatus.BAD_REQUEST) req_method = 'GET' obj_name = utils.get_obj_name(path, is_folder=False) if accept_url: display_name = kwargs.get('display_name') or path.name query = {'response-content-disposition': make_disposition(display_name)} # There is no need to delay URL building and signing signed_url = self._build_and_sign_url(req_method, obj_name, **query) # type: ignore return signed_url signed_url = functools.partial(self._build_and_sign_url, req_method, obj_name, **{}) resp = await self.make_request( req_method, signed_url, range=range, expects=(HTTPStatus.OK, HTTPStatus.PARTIAL_CONTENT), throws=DownloadError ) return ResponseStreamReader(resp)
async def get(self): """Download a file.""" try: self.arguments['accept_url'] = TRUTH_MAP[self.arguments.get('accept_url', 'true').lower()] except KeyError: raise tornado.web.HTTPError(status_code=400) if 'Range' in self.request.headers: request_range = utils.parse_request_range(self.request.headers['Range']) else: request_range = None result = await self.provider.download(range=request_range, **self.arguments) if isinstance(result, str): self.redirect(result) self._send_hook('download_file', path=self.path) return if getattr(result, 'partial', None): # Use getattr here as not all stream may have a partial attribute # Plus it fixes tests self.set_status(206) self.set_header('Content-Range', result.content_range) if result.content_type is not None: self.set_header('Content-Type', result.content_type) if result.size is not None: self.set_header('Content-Length', str(result.size)) # Build `Content-Disposition` header from `displayName` override, # headers of provider response, or file path, whichever is truthy first name = self.arguments.get('displayName') or getattr(result, 'name', None) or self.path.name self.set_header('Content-Disposition', make_disposition(name)) _, ext = os.path.splitext(name) # If the file extention is in mime_types # override the content type to fix issues with safari shoving in new file extensions if ext in mime_types: self.set_header('Content-Type', mime_types[ext]) await self.write_stream(result) self._send_hook('download_file', path=self.path)
async def download(self, path, accept_url=False, revision=None, range=None, **kwargs): """Returns a ResponseWrapper (Stream) for the specified path raises FileNotFoundError if the status from S3 is not 200 :param str path: Path to the key you want to download :param dict \*\*kwargs: Additional arguments that are ignored :rtype: :class:`waterbutler.core.streams.ResponseStreamReader` :raises: :class:`waterbutler.core.exceptions.DownloadError` """ await self._check_region() if not path.is_file: raise exceptions.DownloadError('No file specified for download', code=400) if not revision or revision.lower() == 'latest': query_parameters = None else: query_parameters = {'versionId': revision} display_name = kwargs.get('display_name') or path.name response_headers = { 'response-content-disposition': make_disposition(display_name) } url = functools.partial( self.bucket.new_key(path.path).generate_url, settings.TEMP_URL_SECS, query_parameters=query_parameters, response_headers=response_headers ) if accept_url: return url() resp = await self.make_request( 'GET', url, range=range, expects=(200, 206, ), throws=exceptions.DownloadError, ) return streams.ResponseStreamReader(resp)
def test_content_disposition(self, filename, expected): disposition = utils.make_disposition(filename) assert disposition == expected