import abc import os from typing import TypeVar, Generic, TypeAlias, Optional, AsyncGenerator from app.Services.lifespan_service import LifespanService FileMetaDataT = TypeVar('FileMetaDataT') PathLikeType: TypeAlias = str | os.PathLike LocalFilePathType: TypeAlias = PathLikeType | bytes RemoteFilePathType: TypeAlias = PathLikeType LocalFileMetaDataType: TypeAlias = FileMetaDataT RemoteFileMetaDataType: TypeAlias = FileMetaDataT class BaseStorage(LifespanService, abc.ABC, Generic[FileMetaDataT]): def __init__(self): self.static_dir: os.PathLike self.thumbnails_dir: os.PathLike self.deleted_dir: os.PathLike self.file_metadata: FileMetaDataT @abc.abstractmethod async def is_exist(self, remote_file: RemoteFilePathType) -> bool: """ Check if a remote_file exists. :param remote_file: The file path relative to static_dir :return: True if the file exists, False otherwise """ raise NotImplementedError @abc.abstractmethod async def size(self, remote_file: RemoteFilePathType) -> int: """ Get the size of a file in static_dir :param remote_file: The file path relative to static_dir :return: file's size """ raise NotImplementedError @abc.abstractmethod async def url(self, remote_file: RemoteFilePathType) -> str: """ Get the original URL of a file in static_dir. This url will be placed in the payload field of the qdrant. :param remote_file: The file path relative to static_dir :return: file's "original URL" """ raise NotImplementedError @abc.abstractmethod async def presign_url(self, remote_file: RemoteFilePathType, expire_second: int = 3600) -> str: """ Get the presign URL of a file in static_dir. :param remote_file: The file path relative to static_dir :param expire_second: Valid time for presign url :return: file's "presign URL" """ raise NotImplementedError @abc.abstractmethod async def fetch(self, remote_file: RemoteFilePathType) -> bytes: """ Fetch a file from static_dir :param remote_file: The file path relative to static_dir :return: file's content """ raise NotImplementedError @abc.abstractmethod async def upload(self, local_file: "LocalFilePathType", remote_file: RemoteFilePathType) -> None: """ Move a local picture file to the static_dir. :param local_file: The absolute path to the local file or bytes. :param remote_file: The file path relative to static_dir """ raise NotImplementedError @abc.abstractmethod async def copy(self, old_remote_file: RemoteFilePathType, new_remote_file: RemoteFilePathType) -> None: """ Copy a file in static_dir. :param old_remote_file: The file path relative to static_dir :param new_remote_file: The file path relative to static_dir """ raise NotImplementedError @abc.abstractmethod async def move(self, old_remote_file: RemoteFilePathType, new_remote_file: RemoteFilePathType) -> None: """ Move a file in static_dir. :param old_remote_file: The file path relative to static_dir :param new_remote_file: The file path relative to static_dir """ raise NotImplementedError @abc.abstractmethod async def delete(self, remote_file: RemoteFilePathType) -> None: """ Move a file in static_dir. :param remote_file: The file path relative to static_dir """ raise NotImplementedError @abc.abstractmethod async def list_files(self, path: RemoteFilePathType, pattern: Optional[str] = "*", batch_max_files: Optional[int] = None, valid_extensions: Optional[set[str]] = None) \ -> AsyncGenerator[list[RemoteFilePathType], None]: """ Asynchronously generates a list of files from a given base directory path that match a specified pattern and set of file extensions. :param path: The relative base directory path from which relative to static_dir to start listing files. :param pattern: A glob pattern to filter files based on their names. Defaults to '*' which selects all files. :param batch_max_files: The maximum number of files to return. If None, all matching files are returned. :param valid_extensions: An extra set of file extensions to include (e.g., {".jpg", ".png"}). If None, files are not filtered by extension. :return: An asynchronous generator yielding lists of RemoteFilePathType objects representing the matching files. Usage example: async for batch in list_files(base_path=".", pattern="*", max_files=100, valid_extensions={".jpg", ".png"}): print(f"Batch: {batch}") """ raise NotImplementedError @abc.abstractmethod async def update_metadata(self, local_file_metadata: LocalFileMetaDataType, remote_file_metadata: RemoteFileMetaDataType) -> None: raise NotImplementedError