diff --git a/htdocs/lib/settings/ImageUpload.js b/htdocs/lib/settings/ImageUpload.js index c158342..2337318 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'; + 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({ @@ -49,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){ @@ -58,8 +67,8 @@ $.fn.imageUpload = function() { } catch (e) { handleError(error); } - }).always(function(){ $uploadButton.prop('disabled', false); + $spinner.remove(); }); } }; 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..e12c101 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 @@ -170,25 +171,36 @@ 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"]: - 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]) 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: + 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]) + 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)):