diff --git a/openra/apps.py b/openra/apps.py index 44895f4..5dc2c60 100644 --- a/openra/apps.py +++ b/openra/apps.py @@ -20,5 +20,6 @@ def ready(self): ".management.commands.import_latest_engines", ".facades", '.services.engine_file_repository', - '.services.map_file_repository' + '.services.map_file_repository', + '.services.screenshot_repository', ]) diff --git a/openra/classes/exceptions.py b/openra/classes/exceptions.py index f1f7268..e9f58cb 100644 --- a/openra/classes/exceptions.py +++ b/openra/classes/exceptions.py @@ -24,3 +24,6 @@ def get_full_details(self): def print_full_details(self): print(self.get_full_details()) + + def __str__(self): + return self.get_full_details() diff --git a/openra/classes/file_location.py b/openra/classes/file_location.py index e493c91..da5bada 100644 --- a/openra/classes/file_location.py +++ b/openra/classes/file_location.py @@ -1,3 +1,5 @@ +from __future__ import annotations +import io from django.conf import os from fs.base import FS, copy from fs.tempfs import TempFS @@ -17,7 +19,7 @@ def __init__(self, fs: FS, path: str, file: str): self.file = file def get_fs_path(self): - return os.path.join(self.path + self.file) + return os.path.join(self.path, self.file) def get_os_dir(self): try: @@ -37,6 +39,30 @@ def get_os_path(self): except Exception as exception: raise ExceptionFileLocationGetOSPath(exception, self.fs, self.path, self.file) + def ensure_file_exists(self): + if not self.fs.exists(self.get_fs_path()): + if self.path: + self.fs.makedirs(self.path) + self.fs.create(self.get_fs_path()) + + def copy_to_file_location(self, location: FileLocation): + try: + location.ensure_file_exists() + copy.copy_file( + self.fs, + os.path.join( + self.path, + self.file + ), + location.fs, + location.get_fs_path() + ) + + return location + + except Exception as exception: + raise ExceptionFileLocationCopyToFileLocation(exception, self.fs, self.path, self.file, location) + def copy_to_tempfs(self, filename: str): try: temp_fs = TempFS() @@ -58,6 +84,23 @@ def copy_to_tempfs(self, filename: str): except Exception as exception: raise ExceptionFileLocationCopyToTempFS(exception, self.fs, self.path, self.file, filename) + def get_file_clone(self): + try: + + file = io.BytesIO() + + self.fs.download( + self.get_fs_path(), + file + ) + + file.seek(0) + + return file + + except Exception as exception: + raise ExceptionFileLocationGetFileClone(exception, self.fs, self.path, self.file) + class ExceptionFileLocationGetOSDir(ExceptionBase): def __init__(self, exception, fs: FS, path: str, file: str): @@ -75,6 +118,19 @@ def __init__(self, exception, fs: FS, path: str, file: str): self.message = "An exception occured while trying to get the os path" +class ExceptionFileLocationCopyToFileLocation(ExceptionBase): + def __init__(self, exception, fs: FS, path: str, file: str, target: FileLocation): + super().__init__() + self.message = "An exception occured while trying to copy a file to a TempFS" + self.detail.append('from fs type: ' + str(type(fs))) + self.detail.append('from path: ' + path) + self.detail.append('from file: ' + file) + self.detail.append('to fs type: ' + str(type(target.fs))) + self.detail.append('to path: ' + target.path) + self.detail.append('to file: ' + target.file) + self.detail.append('message: ' + str(exception)) + + class ExceptionFileLocationCopyToTempFS(ExceptionBase): def __init__(self, exception, fs: FS, path: str, file: str, target: str): super().__init__() @@ -84,3 +140,13 @@ def __init__(self, exception, fs: FS, path: str, file: str, target: str): self.detail.append('file: ' + file) self.detail.append('target: ' + target) self.detail.append('message: ' + str(exception)) + + +class ExceptionFileLocationGetFileClone(ExceptionBase): + def __init__(self, exception, fs: FS, path: str, file: str): + super().__init__() + self.message = "An exception occured while trying to clone a file" + self.detail.append('fs type: ' + str(type(fs))) + self.detail.append('path: ' + path) + self.detail.append('file: ' + file) + self.detail.append('message: ' + str(exception)) diff --git a/openra/classes/screenshot_resource.py b/openra/classes/screenshot_resource.py new file mode 100644 index 0000000..e0efc5d --- /dev/null +++ b/openra/classes/screenshot_resource.py @@ -0,0 +1,23 @@ + +from openra.classes.exceptions import ExceptionBase + + +class ScreenshotResource: + + type: str + id: int + + def __init__(self, type: str, id: int): + if type not in ['maps']: + raise ExceptionScreenshotResourceTypeInvalid(type, id) + + self.type = type + self.id = id + + +class ExceptionScreenshotResourceTypeInvalid(ExceptionBase): + def __init__(self, type: str, id: int): + super().__init__() + self.message = "Invalid resource type for screenshot resource" + self.detail.append('Type : ' + type) + self.detail.append('Id: ' + str(id)) diff --git a/openra/containers.py b/openra/containers.py index 78a6ec6..0f1cbf7 100644 --- a/openra/containers.py +++ b/openra/containers.py @@ -11,6 +11,8 @@ from openra.services.log import Log from openra.services.map_file_repository import MapFileRepository from openra.services.map_search import MapSearch +from openra.services.screenshot_repository import ScreenshotRepository +from openra.services.uploaded_file_importer import UploadedFileImporter from openra.services.utility import Utility @@ -41,6 +43,14 @@ class Container(containers.DeclarativeContainer): ) ) + uploaded_file_importer = providers.Singleton( + UploadedFileImporter + ) + + screenshot_repository = providers.Singleton( + ScreenshotRepository + ) + engine_file_repository = providers.Singleton( EngineFileRepository ) diff --git a/openra/services/screenshot_repository.py b/openra/services/screenshot_repository.py new file mode 100644 index 0000000..cd07b69 --- /dev/null +++ b/openra/services/screenshot_repository.py @@ -0,0 +1,93 @@ +import io +from dependency_injector.wiring import Provide, inject +from django.conf import os +from django.contrib.auth.models import User +from django.core.files.uploadedfile import UploadedFile +from django.utils import timezone +from fs.base import FS, copy +from openra.classes.exceptions import ExceptionBase +from openra.classes.file_location import FileLocation +from openra.classes.screenshot_resource import ScreenshotResource +from openra.models import Screenshots +from PIL import Image + +from openra.services.uploaded_file_importer import UploadedFileImporter + + +class ScreenshotRepository: + + _data_fs: FS + _uploaded_file_importer: UploadedFileImporter + + @inject + def __init__( + self, + data_fs: FS = Provide['data_fs'], + uploaded_file_importer: UploadedFileImporter = Provide['uploaded_file_importer'] + ): + self._data_fs = data_fs + self._uploaded_file_importer = uploaded_file_importer + + def create_from_uploaded_file(self, uploaded_file: UploadedFile, user: User, resource: ScreenshotResource, map_preview: bool): + + if uploaded_file.content_type not in ['image/jpeg', 'image/png', 'image/gif']: + raise ExceptionInvalidMimeType(uploaded_file.name, uploaded_file.content_type) + + extension = uploaded_file.content_type.split('/')[1] + + uploaded = self._uploaded_file_importer.import_file( + uploaded_file, + uploaded_file.name + ) + + image = Image.open( + uploaded.get_file_clone() + ) + + image.thumbnail(( + 250, + 250 + )) + + thumbnail = io.BytesIO() + + image.save(thumbnail, extension) + + thumbnail.seek(0) + + model = Screenshots( + user=user, + ex_id=resource.id, + ex_name=resource.type, + posted=timezone.now(), + map_preview=map_preview, + ) + + model.save() + + directory = os.path.join('screenshots', str(model.id)) + + uploaded.copy_to_file_location( + FileLocation( + self._data_fs, + directory, + str(resource.id) + '.' + extension + ) + ) + + preview_path = os.path.join('screenshots', str(model.id), str(resource.id) + '-mini.' + extension) + + self._data_fs.writefile( + preview_path, + thumbnail + ) + + return model + + +class ExceptionInvalidMimeType(ExceptionBase): + def __init__(self, filename: str, mimetype): + super().__init__() + self.message = "Mimetype invalid for a screenshot" + self.detail.append('Filename : ' + filename) + self.detail.append('Mimetype: ' + str(mimetype)) diff --git a/openra/services/uploaded_file_importer.py b/openra/services/uploaded_file_importer.py new file mode 100644 index 0000000..9c37d74 --- /dev/null +++ b/openra/services/uploaded_file_importer.py @@ -0,0 +1,38 @@ +from django.core.files.uploadedfile import File +from fs.tempfs import TempFS + +from openra.classes.exceptions import ExceptionBase +from openra.classes.file_location import FileLocation + + +class UploadedFileImporter: + + def import_file(self, request_file: File, filename: str): + + try: + temp_fs = self._create_temp_fs() + + for chunk in request_file.chunks(): + temp_fs.appendbytes( + filename, + chunk + ) + + return FileLocation( + temp_fs, + '', + filename + ) + except Exception as exception: + raise ExceptionUploadedFileImporter(exception, filename) + + def _create_temp_fs(self): + return TempFS() + + +class ExceptionUploadedFileImporter(ExceptionBase): + def __init__(self, exception, filename: str): + super().__init__() + self.message = "Uploaded file importer caught an exception while attempting to upload this file" + self.detail.append('filename: ' + filename) + self.detail.append('message: ' + str(exception)) diff --git a/openra/templates/addScreenshotForm.html b/openra/templates/addScreenshotForm.html index 007662e..25fa8fb 100644 --- a/openra/templates/addScreenshotForm.html +++ b/openra/templates/addScreenshotForm.html @@ -1,6 +1,6 @@ {% if request.user.is_authenticated %}