Skip to content

Commit b9c0d12

Browse files
committed
chore(filer): zero copy upload file
1 parent 5d564ab commit b9c0d12

3 files changed

Lines changed: 49 additions & 4 deletions

File tree

rootfs/api/files/__init__.py

Whitespace-only changes.

rootfs/api/files/parsers.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import contextlib
2+
from django.utils.http import parse_header_parameters
3+
from django.core.files.uploadedfile import UploadedFile
4+
from rest_framework.exceptions import ParseError
5+
from rest_framework.parsers import FileUploadParser, DataAndFiles
6+
7+
8+
class FilerFile(UploadedFile):
9+
10+
def __init__(self, filename, filepath, input_data, content_length):
11+
self.filename = filename
12+
self.filepath = filepath
13+
super().__init__(input_data, name=filename, size=content_length)
14+
15+
16+
class FilerUploadParser(FileUploadParser):
17+
"""
18+
Filer upload parser.
19+
"""
20+
media_type = 'filer/octet-stream'
21+
22+
def parse(self, stream, media_type=None, parser_context=None):
23+
request = parser_context['request']
24+
filename = self.get_filename(stream, media_type, parser_context)
25+
if not filename:
26+
raise ParseError(self.errors['no_filename'])
27+
28+
try:
29+
content_length = int(request.META.get('HTTP_CONTENT_LENGTH',
30+
request.META.get('CONTENT_LENGTH', 0)))
31+
except (ValueError, TypeError):
32+
content_length = None
33+
34+
file_meta = self.get_file_meta(request.META)
35+
return DataAndFiles({}, {'file': FilerFile(
36+
file_meta['filename'], file_meta['filepath'], stream, content_length)})
37+
38+
def get_file_meta(self, META):
39+
"""
40+
Detects the uploaded file name. First searches a 'filename' url kwarg.
41+
Then tries to parse Content-Disposition header.
42+
"""
43+
with contextlib.suppress(AttributeError, KeyError, ValueError):
44+
_, params = parse_header_parameters(META['HTTP_CONTENT_DISPOSITION'])
45+
return params

rootfs/api/views.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
from rest_framework.permissions import IsAuthenticated, AllowAny
2323
from rest_framework.response import Response
2424
from rest_framework.viewsets import GenericViewSet
25-
from rest_framework.parsers import MultiPartParser
2625
from rest_framework.exceptions import PermissionDenied
2726

2827
from api import monitor, models, permissions, serializers, viewsets, authentication
@@ -40,6 +39,7 @@
4039
from api import admissions, utils, filer
4140
from api.backend import OauthCacheManager
4241
from api.apps_extra.social_core.actions import do_auth, do_complete
42+
from api.files.parsers import FilerUploadParser
4343

4444
User = get_user_model()
4545
logger = logging.getLogger(__name__)
@@ -896,7 +896,7 @@ def path(self, request, *args, **kwargs):
896896
class AppFilerClientViewSet(AppResourceViewSet):
897897
"""RESTful views for volumes apps with collaborators."""
898898
model = models.volume.Volume
899-
parser_classes = [MultiPartParser]
899+
parser_classes = [FilerUploadParser]
900900

901901
def get_client(self):
902902
volume = get_object_or_404(
@@ -926,9 +926,9 @@ def retrieve(self, request, **kwargs):
926926
)
927927

928928
def create(self, request, **kwargs):
929-
path = request.data.get('path', '')
930929
client = self.get_client()
931-
response = client.post(path, files=request.FILES)
930+
file = request.data['file']
931+
response = client.post(file.filepath, files=request.FILES)
932932
return Response(data=response.content, status=response.status_code)
933933

934934
def destroy(self, request, **kwargs):

0 commit comments

Comments
 (0)