aboutsummaryrefslogtreecommitdiff
path: root/server
diff options
context:
space:
mode:
authorrr-2017-01-07 11:59:43 +0100
committerrr-2017-01-08 10:25:29 +0100
commit036fa9ee3908f480b9e0f8aa3018bf7c8768e88f (patch)
treec83823023ae078affbd0a17857ccb10f9948b106 /server
parentf00cc5f3fa3a16a1f7055c772c3008d030f41fef (diff)
server/uploads: add file upload api
Diffstat (limited to 'server')
-rw-r--r--server/szurubooru/api/__init__.py1
-rw-r--r--server/szurubooru/api/upload_api.py10
-rw-r--r--server/szurubooru/errors.py4
-rw-r--r--server/szurubooru/facade.py16
-rw-r--r--server/szurubooru/func/file_uploads.py29
-rw-r--r--server/szurubooru/func/files.py6
-rw-r--r--server/szurubooru/rest/context.py30
7 files changed, 84 insertions, 12 deletions
diff --git a/server/szurubooru/api/__init__.py b/server/szurubooru/api/__init__.py
index 308b86b..2a2d5af 100644
--- a/server/szurubooru/api/__init__.py
+++ b/server/szurubooru/api/__init__.py
@@ -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
diff --git a/server/szurubooru/api/upload_api.py b/server/szurubooru/api/upload_api.py
new file mode 100644
index 0000000..eaf2880
--- /dev/null
+++ b/server/szurubooru/api/upload_api.py
@@ -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}
diff --git a/server/szurubooru/errors.py b/server/szurubooru/errors.py
index 33bdfce..f7edf85 100644
--- a/server/szurubooru/errors.py
+++ b/server/szurubooru/errors.py
@@ -36,6 +36,10 @@ class MissingRequiredFileError(ValidationError):
pass
+class MissingOrExpiredRequiredFileError(MissingRequiredFileError):
+ pass
+
+
class MissingRequiredParameterError(ValidationError):
pass
diff --git a/server/szurubooru/facade.py b/server/szurubooru/facade.py
index 5211ca8..e29aae1 100644
--- a/server/szurubooru/facade.py
+++ b/server/szurubooru/facade.py
@@ -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)
diff --git a/server/szurubooru/func/file_uploads.py b/server/szurubooru/func/file_uploads.py
new file mode 100644
index 0000000..95698e3
--- /dev/null
+++ b/server/szurubooru/func/file_uploads.py
@@ -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
diff --git a/server/szurubooru/func/files.py b/server/szurubooru/func/files.py
index 4834045..3ca8777 100644
--- a/server/szurubooru/func/files.py
+++ b/server/szurubooru/func/files.py
@@ -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))
diff --git a/server/szurubooru/rest/context.py b/server/szurubooru/rest/context.py
index 6f74a4c..081064e 100644
--- a/server/szurubooru/rest/context.py
+++ b/server/szurubooru/rest/context.py
@@ -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

© 2015 - 2026 Jakob L. Kreuze