[file] Added POST/PUT /file endpoints.

This commit is contained in:
Fabio Manganiello 2024-08-24 16:22:10 +02:00
parent 0010342fb7
commit 2c481c54af
Signed by untrusted user: blacklight
GPG key ID: D90FBA7F76362774

View file

@ -1,7 +1,8 @@
import os import os
import pathlib
from contextlib import contextmanager from contextlib import contextmanager
from datetime import datetime as dt from datetime import datetime as dt
from typing import Optional, Tuple from typing import IO, Optional, Tuple
from tornado.web import stream_request_body from tornado.web import stream_request_body
@ -17,6 +18,8 @@ class FileRoute(StreamingRoute):
""" """
BUFSIZE = 1024 BUFSIZE = 1024
_bytes_written = 0
_out_f: Optional[IO[bytes]] = None
@classmethod @classmethod
def path(cls) -> str: def path(cls) -> str:
@ -39,6 +42,10 @@ class FileRoute(StreamingRoute):
def file_size(self) -> int: def file_size(self) -> int:
return os.path.getsize(self.file_path) return os.path.getsize(self.file_path)
@property
def _content_length(self) -> int:
return int(self.request.headers.get('Content-Length', 0))
@property @property
def range(self) -> Tuple[Optional[int], Optional[int]]: def range(self) -> Tuple[Optional[int], Optional[int]]:
range_hdr = self.request.headers.get('Range') range_hdr = self.request.headers.get('Range')
@ -105,6 +112,77 @@ class FileRoute(StreamingRoute):
self.finish() self.finish()
def on_finish(self) -> None:
if self._out_f:
try:
if not (self._out_f and self._out_f.closed):
self._out_f.close()
except Exception as e:
self.logger.warning('Error while closing the output file: %s', e)
self._out_f = None
return super().on_finish()
def _validate_upload(self, force: bool = False) -> bool:
if not self.file_path:
self.write_error(400, 'Missing path argument')
return False
if not self._out_f:
if not force and os.path.exists(self.file_path):
self.write_error(409, f'{self.file_path} already exists')
return False
self._bytes_written = 0
dir_path = os.path.dirname(self.file_path)
try:
pathlib.Path(dir_path).mkdir(parents=True, exist_ok=True)
self._out_f = open( # pylint: disable=consider-using-with
self.file_path, 'wb'
)
except PermissionError:
self.write_error(403, 'Permission denied')
return False
return True
def finish(self, *args, **kwargs): # type: ignore
try:
return super().finish(*args, **kwargs)
except Exception as e:
self.logger.warning('Error while finishing the request: %s', e)
def data_received(self, chunk: bytes):
# Ignore unless we're in POST/PUT mode
if self.request.method not in ('POST', 'PUT'):
return
force = self.request.method == 'PUT'
if not self._validate_upload(force=force):
self.finish()
return
if not chunk:
self.logger.debug('Received EOF from client')
self.finish()
return
assert self._out_f
self._out_f.write(chunk)
self._out_f.flush()
self._bytes_written += len(chunk)
self.logger.debug(
'Written chunk of size %d to %s, progress: %d/%d',
len(chunk),
self.file_path,
self._bytes_written,
self._content_length,
)
self.flush()
def get(self) -> None: def get(self) -> None:
with self._serve() as f: with self._serve() as f:
if f: if f:
@ -119,3 +197,9 @@ class FileRoute(StreamingRoute):
def head(self) -> None: def head(self) -> None:
with self._serve(): with self._serve():
pass pass
def post(self) -> None:
self.logger.info('Receiving file POST upload request for %r', self.file_path)
def put(self) -> None:
self.logger.info('Receiving file PUT upload request for %r', self.file_path)