From ad8877f83c8519f0415e5e42e069f44d74515745 Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Fri, 7 May 2021 16:57:54 +0200 Subject: [PATCH 1/4] add webp support for uploadable images --- htdocs/lib/settings/ImageUpload.js | 2 +- owrx/controllers/assets.py | 2 +- owrx/controllers/imageupload.py | 7 ++++++- owrx/controllers/settings/general.py | 19 +++++++++++++------ 4 files changed, 21 insertions(+), 9 deletions(-) diff --git a/htdocs/lib/settings/ImageUpload.js b/htdocs/lib/settings/ImageUpload.js index c158342..30e4c27 100644 --- a/htdocs/lib/settings/ImageUpload.js +++ b/htdocs/lib/settings/ImageUpload.js @@ -23,7 +23,7 @@ $.fn.imageUpload = function() { $uploadButton.prop('disabled', true); var input = document.createElement('input'); input.type = 'file'; - input.accept = 'image/jpeg, image/png'; + input.accept = 'image/jpeg, image/png, image/webp'; input.onchange = function(e) { var reader = new FileReader() diff --git a/owrx/controllers/assets.py b/owrx/controllers/assets.py index 8ab2535..80f2c67 100644 --- a/owrx/controllers/assets.py +++ b/owrx/controllers/assets.py @@ -96,7 +96,7 @@ class OwrxAssetsController(AssetsController): } if file in mappedFiles and ("mapped" not in self.request.query or self.request.query["mapped"][0] != "false"): config = CoreConfig() - for ext in ["png", "jpg"]: + for ext in ["png", "jpg", "webp"]: user_file = "{}/{}.{}".format(config.get_data_directory(), mappedFiles[file], ext) if os.path.exists(user_file) and os.path.isfile(user_file): return user_file diff --git a/owrx/controllers/imageupload.py b/owrx/controllers/imageupload.py index a65a822..7686659 100644 --- a/owrx/controllers/imageupload.py +++ b/owrx/controllers/imageupload.py @@ -36,6 +36,9 @@ class ImageUploadController(AuthorizationMixin, AssetsController): def _is_jpg(self, contents): return contents[0:3] == bytes([0xFF, 0xD8, 0xFF]) + def _is_webp(self, contents): + return contents[0:4] == bytes([0x52, 0x49, 0x46, 0x46]) and contents[8:12] == bytes([0x57, 0x45, 0x42, 0x50]) + def processImage(self): if "id" not in self.request.query: self.send_json_response({"error": "missing id"}, code=400) @@ -55,8 +58,10 @@ class ImageUploadController(AuthorizationMixin, AssetsController): filetype = None if self._is_png(contents): filetype = "png" - if self._is_jpg(contents): + elif self._is_jpg(contents): filetype = "jpg" + elif self._is_webp(contents): + filetype = "webp" if filetype is None: self.send_json_response({"error": "unsupported file type"}, code=400) return diff --git a/owrx/controllers/settings/general.py b/owrx/controllers/settings/general.py index da9c631..5fd1bfe 100644 --- a/owrx/controllers/settings/general.py +++ b/owrx/controllers/settings/general.py @@ -19,6 +19,7 @@ from owrx.breadcrumb import Breadcrumb, BreadcrumbItem from owrx.controllers.settings import SettingsBreadcrumb import shutil import os +import re from glob import glob import logging @@ -175,7 +176,7 @@ class GeneralSettingsController(SettingsFormController): config = CoreConfig() if data[image_id] == "restore": # remove all possible file extensions - for ext in ["png", "jpg"]: + for ext in ["png", "jpg", "webp"]: try: os.unlink("{}/{}.{}".format(config.get_data_directory(), image_id, ext)) except FileNotFoundError: @@ -184,11 +185,17 @@ class GeneralSettingsController(SettingsFormController): if not data[image_id].startswith(image_id): logger.warning("invalid file name: %s", data[image_id]) else: - # get file extension (luckily, all options are three characters long) - ext = data[image_id][-3:] - data_file = "{}/{}.{}".format(config.get_data_directory(), image_id, ext) - temporary_file = "{}/{}".format(config.get_temporary_directory(), data[image_id]) - shutil.copy(temporary_file, data_file) + # get file extension (at least 3 characters) + # should be all lowercase since they are set by the upload script + pattern = re.compile(".*\\.([a-z]{3,})$") + matches = pattern.match(data[image_id]) + if matches is None: + logger.warning("could not determine file extension for %s", image_id) + else: + ext = matches.group(1) + data_file = "{}/{}.{}".format(config.get_data_directory(), image_id, ext) + temporary_file = "{}/{}".format(config.get_temporary_directory(), data[image_id]) + shutil.copy(temporary_file, data_file) del data[image_id] # remove any accumulated temporary files on save for file in glob("{}/{}*".format(config.get_temporary_directory(), image_id)): From 484b829b90c1885d4637f6de147fd1af5e608070 Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Fri, 7 May 2021 17:19:11 +0200 Subject: [PATCH 2/4] fix problem when switching image file types --- owrx/controllers/settings/general.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/owrx/controllers/settings/general.py b/owrx/controllers/settings/general.py index 5fd1bfe..e12c101 100644 --- a/owrx/controllers/settings/general.py +++ b/owrx/controllers/settings/general.py @@ -171,16 +171,20 @@ class GeneralSettingsController(SettingsFormController): ), ] + def remove_existing_image(self, image_id): + config = CoreConfig() + # remove all possible file extensions + for ext in ["png", "jpg", "webp"]: + try: + os.unlink("{}/{}.{}".format(config.get_data_directory(), image_id, ext)) + except FileNotFoundError: + pass + def handle_image(self, data, image_id): if image_id in data: config = CoreConfig() if data[image_id] == "restore": - # remove all possible file extensions - for ext in ["png", "jpg", "webp"]: - try: - os.unlink("{}/{}.{}".format(config.get_data_directory(), image_id, ext)) - except FileNotFoundError: - pass + self.remove_existing_image(image_id) elif data[image_id]: if not data[image_id].startswith(image_id): logger.warning("invalid file name: %s", data[image_id]) @@ -192,6 +196,7 @@ class GeneralSettingsController(SettingsFormController): if matches is None: logger.warning("could not determine file extension for %s", image_id) else: + self.remove_existing_image(image_id) ext = matches.group(1) data_file = "{}/{}.{}".format(config.get_data_directory(), image_id, ext) temporary_file = "{}/{}".format(config.get_temporary_directory(), data[image_id]) From 0206a6f94cbcdafd41e5605993c74ee59536ba9b Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Fri, 7 May 2021 17:33:10 +0200 Subject: [PATCH 3/4] introduce spinner during file uploads --- htdocs/lib/settings/ImageUpload.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/htdocs/lib/settings/ImageUpload.js b/htdocs/lib/settings/ImageUpload.js index 30e4c27..29acd8e 100644 --- a/htdocs/lib/settings/ImageUpload.js +++ b/htdocs/lib/settings/ImageUpload.js @@ -20,18 +20,22 @@ $.fn.imageUpload = function() { $this.removeClass('is-invalid'); }; $uploadButton.click(function(){ - $uploadButton.prop('disabled', true); var input = document.createElement('input'); input.type = 'file'; input.accept = 'image/jpeg, image/png, image/webp'; input.onchange = function(e) { + $uploadButton.prop('disabled', true); + var $spinner = $(''); + $uploadButton.prepend($spinner); + var reader = new FileReader() reader.readAsArrayBuffer(e.target.files[0]); reader.onprogress = function(e) { if (e.loaded > maxSize) { handleError('Maximum file size exceeded'); $uploadButton.prop('disabled', false); + $spinner.remove(); reader.abort(); } }; @@ -39,6 +43,7 @@ $.fn.imageUpload = function() { if (e.loaded > maxSize) { handleError('Maximum file size exceeded'); $uploadButton.prop('disabled', false); + $spinner.remove(); return; } $.ajax({ @@ -60,6 +65,7 @@ $.fn.imageUpload = function() { } }).always(function(){ $uploadButton.prop('disabled', false); + $spinner.remove(); }); } }; From 1b31c5fc90cb2d3801909aa94b17bf956565e7e9 Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Fri, 7 May 2021 17:44:24 +0200 Subject: [PATCH 4/4] keep the spinner visible while the image loads --- htdocs/lib/settings/ImageUpload.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/htdocs/lib/settings/ImageUpload.js b/htdocs/lib/settings/ImageUpload.js index 29acd8e..2337318 100644 --- a/htdocs/lib/settings/ImageUpload.js +++ b/htdocs/lib/settings/ImageUpload.js @@ -54,6 +54,10 @@ $.fn.imageUpload = function() { contentType: 'application/octet-stream', }).done(function(data){ $input.val(data.file); + $img.one('load', function() { + $uploadButton.prop('disabled', false); + $spinner.remove(); + }); $img.prop('src', '../imageupload?file=' + data.file); clearError(); }).fail(function(xhr, error){ @@ -63,7 +67,6 @@ $.fn.imageUpload = function() { } catch (e) { handleError(error); } - }).always(function(){ $uploadButton.prop('disabled', false); $spinner.remove(); });