Minor refactoring, streaming downloads for large files

pull/600/head
Piero Toffanin 2019-01-15 17:51:32 -05:00
rodzic 57a7a70925
commit 074a6fce64
2 zmienionych plików z 46 dodań i 27 usunięć

Wyświetl plik

@ -1,9 +1,10 @@
import mimetypes
import os import os
from wsgiref.util import FileWrapper from wsgiref.util import FileWrapper
import mimetypes
from django.core.exceptions import ObjectDoesNotExist, SuspiciousFileOperation, ValidationError from django.core.exceptions import ObjectDoesNotExist, SuspiciousFileOperation, ValidationError
from django.db import transaction from django.db import transaction
from django.http import FileResponse
from django.http import HttpResponse from django.http import HttpResponse
from rest_framework import status, serializers, viewsets, filters, exceptions, permissions, parsers from rest_framework import status, serializers, viewsets, filters, exceptions, permissions, parsers
from rest_framework.decorators import detail_route from rest_framework.decorators import detail_route
@ -253,33 +254,52 @@ class TaskTilesJson(TaskNestedView):
return Response(json) return Response(json)
def download_file_response(request, filePath, content_disposition):
filename = os.path.basename(filePath)
filesize = os.stat(filePath).st_size
file = open(filePath, "rb")
# More than 100mb, normal http response, otherwise stream
# Django docs say to avoid streaming when possible
stream = filesize > 1e8 or request.GET.get('_force_stream', False)
if stream:
response = FileResponse(file)
else:
response = HttpResponse(FileWrapper(file),
content_type=(mimetypes.guess_type(filename)[0] or "application/zip"))
response['Content-Type'] = mimetypes.guess_type(filename)[0] or "application/zip"
response['Content-Disposition'] = "{}; filename={}".format(content_disposition, filename)
response['Content-Length'] = filesize
# For testing
if stream:
response['_stream'] = 'yes'
return response
""" """
Task downloads are simply aliases to download the task's assets Task downloads are simply aliases to download the task's assets
(but require a shorter path and look nicer the API user) (but require a shorter path and look nicer the API user)
""" """
class TaskDownloads(TaskNestedView): class TaskDownloads(TaskNestedView):
def get(self, request, pk=None, project_pk=None, asset=""): def get(self, request, pk=None, project_pk=None, asset=""):
""" """
Downloads a task asset (if available) Downloads a task asset (if available)
""" """
task = self.get_and_check_task(request, pk) task = self.get_and_check_task(request, pk)
# Check and download # Check and download
try: try:
asset_path = task.get_asset_download_path(asset) asset_path = task.get_asset_download_path(asset)
except FileNotFoundError: except FileNotFoundError:
raise exceptions.NotFound("Asset does not exist") raise exceptions.NotFound("Asset does not exist")
if not os.path.exists(asset_path): if not os.path.exists(asset_path):
raise exceptions.NotFound("Asset does not exist") raise exceptions.NotFound("Asset does not exist")
asset_filename = os.path.basename(asset_path) return download_file_response(request, asset_path, 'attachment')
file = open(asset_path, "rb")
response = HttpResponse(FileWrapper(file),
content_type=(mimetypes.guess_type(asset_filename)[0] or "application/zip"))
response['Content-Disposition'] = "attachment; filename={}".format(asset)
return response
""" """
Raw access to the task's asset folder resources Raw access to the task's asset folder resources
@ -301,10 +321,4 @@ class TaskAssets(TaskNestedView):
if (not os.path.exists(asset_path)) or os.path.isdir(asset_path): if (not os.path.exists(asset_path)) or os.path.isdir(asset_path):
raise exceptions.NotFound("Asset does not exist") raise exceptions.NotFound("Asset does not exist")
asset_filename = os.path.basename(asset_path) return download_file_response(request, asset_path, 'inline')
file = open(asset_path, "rb")
response = HttpResponse(FileWrapper(file),
content_type=(mimetypes.guess_type(asset_filename)[0] or "application/zip"))
response['Content-Disposition'] = "inline; filename={}".format(asset_filename)
return response

Wyświetl plik

@ -319,6 +319,11 @@ class TestApiTask(BootTransactionTestCase):
res = client.get("/api/projects/{}/tasks/{}/download/{}".format(project.id, task.id, asset)) res = client.get("/api/projects/{}/tasks/{}/download/{}".format(project.id, task.id, asset))
self.assertTrue(res.status_code == status.HTTP_200_OK) self.assertTrue(res.status_code == status.HTTP_200_OK)
# We can stream downloads
res = client.get("/api/projects/{}/tasks/{}/download/{}?_force_stream=1".format(project.id, task.id, task.ASSETS_MAP.keys()[0]))
self.assertTrue(res.status_code == status.HTTP_200_OK)
self.assertTrue(res.has_header('_stream'))
# A textured mesh archive file should exist # A textured mesh archive file should exist
self.assertTrue(os.path.exists(task.assets_path(task.ASSETS_MAP["textured_model.zip"]["deferred_path"]))) self.assertTrue(os.path.exists(task.assets_path(task.ASSETS_MAP["textured_model.zip"]["deferred_path"])))