#!/usr/bin/python # -*- coding: utf-8 -*- # Hive Appier Framework # Copyright (c) 2008-2024 Hive Solutions Lda. # # This file is part of Hive Appier Framework. # # Hive Appier Framework is free software: you can redistribute it and/or modify # it under the terms of the Apache License as published by the Apache # Foundation, either version 2.0 of the License, or (at your option) any # later version. # # Hive Appier Framework is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # Apache License for more details. # # You should have received a copy of the Apache License along with # Hive Appier Framework. If not, see . __author__ = "João Magalhães " """ The author(s) of the module """ __copyright__ = "Copyright (c) 2008-2024 Hive Solutions Lda." """ The copyright for the module """ __license__ = "Apache License, Version 2.0" """ The license for the module """ import os import tempfile from . import config from . import exceptions class StorageEngine(object): @classmethod def load(cls, file, *args, **kwargs): raise exceptions.NotImplementedError() @classmethod def store(cls, file, *args, **kwargs): raise exceptions.NotImplementedError() @classmethod def delete(cls, file, *args, **kwargs): raise exceptions.NotImplementedError() @classmethod def read(cls, file, *args, **kwargs): raise exceptions.NotImplementedError() @classmethod def seek(self, file, *args, **kwargs): raise exceptions.NotImplementedError() @classmethod def cleanup(self, file, *args, **kwargs): raise exceptions.NotImplementedError() @classmethod def is_seekable(self): return False @classmethod def is_stored(self): return False @classmethod def _compute(cls, file, *args, **kwargs): file._compute(*args, **kwargs) class BaseEngine(StorageEngine): @classmethod def load(cls, file, *args, **kwargs): force = kwargs.get("force", False) if not file.file_name: return if file.data and not force: return path = tempfile.mkdtemp() path_f = os.path.join(path, file.file_name) file.file.save(path_f) handle = open(path_f, "rb") try: file.data = handle.read() finally: handle.close() cls._compute() @classmethod def store(cls, file, *args, **kwargs): pass @classmethod def delete(cls, file, *args, **kwargs): pass @classmethod def read(cls, file, *args, **kwargs): # tries to determine the requested size for# the file # reading in case none is defined the handled flag # handling is ignored and the data returned immediately size = kwargs.get("size", None) if not size: return file.data # verifies if the handled flag is already set (data) # has been already retrieved and if that's not the # case sets the handled flag and returns the data handled = hasattr(file, "_handled") file._handled = True if not handled: return file.data # runs the final cleanup operation and returns an empty # value to end the reading sequence cls.cleanup(file) return None @classmethod def cleanup(cls, file, *args, **kwargs): if not hasattr(file, "handled"): return del file._handled @classmethod def is_stored(self): return True class FsEngine(StorageEngine): @classmethod def store(cls, file, *args, **kwargs): file_path = cls._file_path(file) file_data = file.data or b"" handle = open(file_path, "wb") try: handle.write(file_data) finally: handle.close() @classmethod def load(cls, file, *args, **kwargs): cls._compute(file) @classmethod def delete(cls, file, *args, **kwargs): file_path = cls._file_path(file, ensure=False) os.remove(file_path) @classmethod def read(cls, file, *args, **kwargs): data = None size = kwargs.get("size", None) handle = cls._handle(file) try: data = handle.read(size or -1) finally: is_final = True if not size or not data else False is_final and cls.cleanup(file) return data @classmethod def seek(cls, file, *args, **kwargs): offset = kwargs.get("offset", None) if offset == None: return handle = cls._handle(file) handle.seek(offset) @classmethod def cleanup(cls, file, *args, **kwargs): if not hasattr(file, "_handle"): return file._handle.close() del file._handle @classmethod def is_seekable(self): return True @classmethod def _compute(cls, file): file_path = cls._file_path(file, ensure=False) size = os.path.getsize(file_path) mtime = os.path.getmtime(file_path) file.hash = str(mtime) file.size = size file.etag = str(mtime) @classmethod def _handle(cls, file): file_path = cls._file_path(file, ensure=False) handle = hasattr(file, "_handle") and file._handle if not handle: handle = open(file_path, "rb") file._handle = handle return handle @classmethod def _file_path(cls, file, ensure=True, base=None): # verifies that the standard params value is defined and # if that's no the case defaults the value, then tries to # retrieve a series of parameters for file path discovery params = file.params or {} file_path = params.get("file_path", None) # tries to resolve the base path that is going to be used # for the storing of the information, note that in case # the values is provided explicitly it overrides the one # defined through configuration variable base = base or config.conf("FS_PATH", "~/.data") # defines the default file path in case it's not defined from # the params (using the guid value) and then normalizes such # value using a series of operations file_path = file_path or os.path.join(base, file.guid) file_path = os.path.expanduser(file_path) file_path = os.path.normpath(file_path) dir_path = os.path.dirname(file_path) # verifies if the ensure flag is not set of if the directory # path associated with the current file path exists and if # that's the case returns the file path immediately if not ensure: return file_path if os.path.exists(dir_path): return file_path # creates the directories (concrete and parents) as requested # and then returns the "final" file path value to the caller # method so that it can be used for reading or writing os.makedirs(dir_path) return file_path