mirror of
https://github.com/rr-/szurubooru.git
synced 2025-07-17 08:26:24 +00:00
server/uploads: add file upload api
This commit is contained in:
@ -6,3 +6,4 @@ import szurubooru.api.tag_category_api
|
||||
import szurubooru.api.comment_api
|
||||
import szurubooru.api.password_reset_api
|
||||
import szurubooru.api.snapshot_api
|
||||
import szurubooru.api.upload_api
|
||||
|
10
server/szurubooru/api/upload_api.py
Normal file
10
server/szurubooru/api/upload_api.py
Normal file
@ -0,0 +1,10 @@
|
||||
from szurubooru.rest import routes
|
||||
from szurubooru.func import auth, file_uploads
|
||||
|
||||
|
||||
@routes.post('/uploads/?')
|
||||
def create_temporary_file(ctx, _params=None):
|
||||
auth.verify_privilege(ctx.user, 'uploads:create')
|
||||
content = ctx.get_file('content', required=True, allow_tokens=False)
|
||||
token = file_uploads.save(content)
|
||||
return {'token': token}
|
@ -36,6 +36,10 @@ class MissingRequiredFileError(ValidationError):
|
||||
pass
|
||||
|
||||
|
||||
class MissingOrExpiredRequiredFileError(MissingRequiredFileError):
|
||||
pass
|
||||
|
||||
|
||||
class MissingRequiredParameterError(ValidationError):
|
||||
pass
|
||||
|
||||
|
@ -1,11 +1,13 @@
|
||||
''' Exports create_app. '''
|
||||
|
||||
import os
|
||||
import time
|
||||
import logging
|
||||
import threading
|
||||
import coloredlogs
|
||||
import sqlalchemy.orm.exc
|
||||
from szurubooru import config, errors, rest
|
||||
from szurubooru.func import posts
|
||||
from szurubooru.func import posts, file_uploads
|
||||
# pylint: disable=unused-import
|
||||
from szurubooru import api, middleware
|
||||
|
||||
@ -79,6 +81,15 @@ def validate_config():
|
||||
raise errors.ConfigError('Database is not configured')
|
||||
|
||||
|
||||
def purge_old_uploads():
|
||||
while True:
|
||||
try:
|
||||
file_uploads.purge_old_uploads()
|
||||
except Exception as ex:
|
||||
logging.exception(ex)
|
||||
time.sleep(60 * 5)
|
||||
|
||||
|
||||
def create_app():
|
||||
''' Create a WSGI compatible App object. '''
|
||||
validate_config()
|
||||
@ -88,6 +99,9 @@ def create_app():
|
||||
if config.config['show_sql']:
|
||||
logging.getLogger('sqlalchemy.engine').setLevel(logging.INFO)
|
||||
|
||||
purge_thread = threading.Thread(target=purge_old_uploads)
|
||||
purge_thread.daemon = True
|
||||
purge_thread.start()
|
||||
posts.populate_reverse_search()
|
||||
|
||||
rest.errors.handle(errors.AuthError, _on_auth_error)
|
||||
|
29
server/szurubooru/func/file_uploads.py
Normal file
29
server/szurubooru/func/file_uploads.py
Normal file
@ -0,0 +1,29 @@
|
||||
import datetime
|
||||
from szurubooru.func import files, util
|
||||
|
||||
|
||||
MAX_MINUTES = 60
|
||||
|
||||
|
||||
def _get_path(checksum):
|
||||
return 'temporary-uploads/%s.dat' % checksum
|
||||
|
||||
|
||||
def purge_old_uploads():
|
||||
now = datetime.datetime.now()
|
||||
for file in files.scan('temporary-uploads'):
|
||||
file_time = datetime.datetime.fromtimestamp(file.stat().st_ctime)
|
||||
if now - file_time > datetime.timedelta(minutes=MAX_MINUTES):
|
||||
files.delete('temporary-uploads/%s' % file.name)
|
||||
|
||||
|
||||
def get(checksum):
|
||||
return files.get('temporary-uploads/%s.dat' % checksum)
|
||||
|
||||
|
||||
def save(content):
|
||||
checksum = util.get_sha1(content)
|
||||
path = _get_path(checksum)
|
||||
if not files.has(path):
|
||||
files.save(path, content)
|
||||
return checksum
|
@ -16,6 +16,12 @@ def has(path):
|
||||
return os.path.exists(_get_full_path(path))
|
||||
|
||||
|
||||
def scan(path):
|
||||
if has(path):
|
||||
return os.scandir(_get_full_path(path))
|
||||
return []
|
||||
|
||||
|
||||
def move(source_path, target_path):
|
||||
return os.rename(_get_full_path(source_path), _get_full_path(target_path))
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
from szurubooru import errors
|
||||
from szurubooru.func import net
|
||||
from szurubooru.func import net, file_uploads
|
||||
|
||||
|
||||
def _lower_first(source):
|
||||
@ -43,18 +43,26 @@ class Context:
|
||||
def get_header(self, name):
|
||||
return self._headers.get(name, None)
|
||||
|
||||
def has_file(self, name):
|
||||
return name in self._files or name + 'Url' in self._params
|
||||
def has_file(self, name, allow_tokens=True):
|
||||
return (name in self._files
|
||||
or name + 'Url' in self._params
|
||||
or (allow_tokens and name + 'Token' in self._params))
|
||||
|
||||
def get_file(self, name, required=False):
|
||||
def get_file(self, name, required=False, allow_tokens=True):
|
||||
ret = None
|
||||
if name in self._files:
|
||||
return self._files[name]
|
||||
if name + 'Url' in self._params:
|
||||
return net.download(self._params[name + 'Url'])
|
||||
if not required:
|
||||
return None
|
||||
raise errors.MissingRequiredFileError(
|
||||
'Required file %r is missing.' % name)
|
||||
ret = self._files[name]
|
||||
elif name + 'Url' in self._params:
|
||||
ret = net.download(self._params[name + 'Url'])
|
||||
elif allow_tokens and name + 'Token' in self._params:
|
||||
ret = file_uploads.get(self._params[name + 'Token'])
|
||||
if required and not ret:
|
||||
raise errors.MissingOrExpiredRequiredFileError(
|
||||
'Required file %r is missing or has expired.' % name)
|
||||
if required and not ret:
|
||||
raise errors.MissingRequiredFileError(
|
||||
'Required file %r is missing.' % name)
|
||||
return ret
|
||||
|
||||
def has_param(self, name):
|
||||
return name in self._params
|
||||
|
Reference in New Issue
Block a user