2022-09-05 12:54:02 +00:00
|
|
|
"""
|
|
|
|
Dependables for defining Routers.
|
|
|
|
"""
|
|
|
|
|
2023-10-26 16:21:07 +00:00
|
|
|
import logging
|
2022-09-02 13:22:35 +00:00
|
|
|
import re
|
2023-10-26 15:46:12 +00:00
|
|
|
from dataclasses import dataclass, field
|
|
|
|
from typing import Awaitable, Callable, Generic, ParamSpec, Self, TypeVar
|
2022-09-02 13:22:35 +00:00
|
|
|
|
2023-10-26 14:31:12 +00:00
|
|
|
from fastapi import Depends, HTTPException, params, status
|
2022-09-02 13:22:35 +00:00
|
|
|
from webdav3.exceptions import RemoteResourceNotFound
|
|
|
|
|
2023-10-26 15:46:12 +00:00
|
|
|
from ...core.caldav import CalDAV
|
2023-10-26 15:58:54 +00:00
|
|
|
from ...core.config import Config, get_config
|
2023-10-20 08:43:15 +00:00
|
|
|
from ...core.webdav import WebDAV
|
2022-09-02 13:22:35 +00:00
|
|
|
|
2023-10-26 16:21:07 +00:00
|
|
|
_logger = logging.getLogger(__name__)
|
2022-09-08 14:02:50 +00:00
|
|
|
|
2022-09-05 00:23:00 +00:00
|
|
|
_RESPONSE_OK = {
|
|
|
|
status.HTTP_200_OK: {
|
|
|
|
"description": "Operation successful",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2023-10-23 21:32:25 +00:00
|
|
|
Params = ParamSpec("Params")
|
|
|
|
Return = TypeVar("Return")
|
|
|
|
|
2023-10-26 15:48:33 +00:00
|
|
|
type DependableFn[**Params, Return] = Callable[Params, Awaitable[Return]]
|
2023-10-26 13:58:02 +00:00
|
|
|
|
2023-10-23 21:32:25 +00:00
|
|
|
|
|
|
|
@dataclass(slots=True, frozen=True)
|
2023-10-25 18:50:03 +00:00
|
|
|
class Dependable(Generic[Params, Return]):
|
2023-10-26 15:48:33 +00:00
|
|
|
func: DependableFn[Params, Return]
|
2023-10-26 15:46:12 +00:00
|
|
|
responses: dict = field(default_factory=lambda: _RESPONSE_OK.copy())
|
2023-10-23 21:32:25 +00:00
|
|
|
|
2023-10-23 21:44:09 +00:00
|
|
|
|
2023-10-26 15:46:12 +00:00
|
|
|
@dataclass(slots=True, frozen=True)
|
2023-10-26 13:58:02 +00:00
|
|
|
class ListManager:
|
|
|
|
lister: Dependable[[], list[str]]
|
|
|
|
filter: Dependable[[str], list[str]]
|
|
|
|
getter: Dependable[[str], str]
|
|
|
|
|
2023-10-26 15:46:12 +00:00
|
|
|
@classmethod
|
|
|
|
def from_lister(cls, lister: Dependable[[], list[str]]) -> Self:
|
|
|
|
async def _filter_fn(
|
2023-10-26 13:58:02 +00:00
|
|
|
prefix: str,
|
2023-10-26 15:46:12 +00:00
|
|
|
names: list[str] = Depends(lister.func),
|
2023-10-26 13:58:02 +00:00
|
|
|
) -> list[str]:
|
2023-10-26 14:31:12 +00:00
|
|
|
if isinstance(names, params.Depends):
|
2023-10-26 15:46:12 +00:00
|
|
|
names = await lister.func()
|
2023-10-26 14:31:12 +00:00
|
|
|
|
|
|
|
_logger.debug("filter %s from %s", repr(prefix), repr(names))
|
|
|
|
|
2023-10-26 13:58:02 +00:00
|
|
|
return [item for item in names if item.lower().startswith(prefix.lower())]
|
|
|
|
|
2023-10-26 15:46:12 +00:00
|
|
|
async def _getter_fn(
|
2023-10-26 14:31:12 +00:00
|
|
|
prefix: str,
|
2023-10-26 15:46:12 +00:00
|
|
|
names: list[str] = Depends(_filter_fn),
|
2023-10-26 13:58:02 +00:00
|
|
|
) -> str:
|
2023-10-26 14:31:12 +00:00
|
|
|
if isinstance(names, params.Depends):
|
2023-10-26 15:46:12 +00:00
|
|
|
names = await _filter_fn(prefix)
|
2023-10-26 14:31:12 +00:00
|
|
|
|
|
|
|
_logger.debug("get %s from %s", repr(prefix), repr(names))
|
|
|
|
|
2023-10-26 13:58:02 +00:00
|
|
|
match names:
|
|
|
|
case [name]:
|
|
|
|
return name
|
|
|
|
|
|
|
|
case []:
|
|
|
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
|
|
|
|
|
|
|
|
case _:
|
|
|
|
raise HTTPException(status_code=status.HTTP_409_CONFLICT)
|
|
|
|
|
2023-10-26 15:46:12 +00:00
|
|
|
return cls(
|
|
|
|
lister=lister,
|
|
|
|
filter=Dependable(_filter_fn),
|
|
|
|
getter=Dependable(
|
|
|
|
func=_getter_fn,
|
2023-10-26 13:58:02 +00:00
|
|
|
responses={
|
|
|
|
**_RESPONSE_OK,
|
|
|
|
status.HTTP_404_NOT_FOUND: {
|
|
|
|
"description": "Prefix not found",
|
|
|
|
"content": None,
|
|
|
|
},
|
|
|
|
status.HTTP_409_CONFLICT: {
|
|
|
|
"description": "Ambiguous prefix",
|
|
|
|
"content": None,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
),
|
|
|
|
)
|
2023-10-23 21:44:09 +00:00
|
|
|
|
2023-10-26 15:46:12 +00:00
|
|
|
@classmethod
|
2023-10-26 15:48:33 +00:00
|
|
|
def from_lister_fn(cls, lister_fn: DependableFn[[], list[str]]) -> Self:
|
2023-10-26 15:46:12 +00:00
|
|
|
return cls.from_lister(Dependable(lister_fn))
|
|
|
|
|
2022-09-05 00:23:00 +00:00
|
|
|
|
2023-10-23 21:32:25 +00:00
|
|
|
def get_remote_path(
|
2023-10-22 14:25:19 +00:00
|
|
|
path_name: str,
|
2023-10-26 15:48:33 +00:00
|
|
|
) -> DependableFn[[], str]:
|
2023-10-23 21:32:25 +00:00
|
|
|
async def _get_remote_path() -> str:
|
|
|
|
cfg = await get_config()
|
|
|
|
return getattr(cfg, path_name)
|
|
|
|
|
2023-10-26 13:58:02 +00:00
|
|
|
return _get_remote_path
|
2023-10-23 18:56:01 +00:00
|
|
|
|
|
|
|
|
2023-10-26 14:31:12 +00:00
|
|
|
RP_FILE = get_remote_path("file_dir")
|
|
|
|
RP_IMAGE = get_remote_path("image_dir")
|
|
|
|
RP_TEXT = get_remote_path("text_dir")
|
|
|
|
|
|
|
|
|
|
|
|
def get_file_lister(
|
2023-10-26 15:48:33 +00:00
|
|
|
rp: DependableFn[[], str],
|
2023-10-22 14:25:19 +00:00
|
|
|
*,
|
2023-10-23 21:32:25 +00:00
|
|
|
re: re.Pattern[str],
|
2023-10-26 13:58:02 +00:00
|
|
|
) -> Dependable[[], list[str]]:
|
2023-10-22 14:25:19 +00:00
|
|
|
"""
|
|
|
|
List files in remote `path` matching the RegEx `re`
|
|
|
|
"""
|
|
|
|
|
2023-10-26 13:58:02 +00:00
|
|
|
async def _list_files(
|
|
|
|
remote_path: str = Depends(rp),
|
|
|
|
) -> list[str]:
|
2023-10-26 14:31:12 +00:00
|
|
|
if isinstance(remote_path, params.Depends):
|
|
|
|
remote_path = await rp()
|
|
|
|
|
|
|
|
_logger.debug("list %s", repr(remote_path))
|
|
|
|
|
2023-10-23 21:32:25 +00:00
|
|
|
try:
|
2023-10-26 13:58:02 +00:00
|
|
|
return await WebDAV.list_files(remote_path, regex=re)
|
2023-10-22 14:25:19 +00:00
|
|
|
|
2023-10-23 21:32:25 +00:00
|
|
|
except RemoteResourceNotFound:
|
2023-10-26 13:58:02 +00:00
|
|
|
_logger.error("WebDAV path %s lost!", repr(remote_path))
|
2023-10-23 21:32:25 +00:00
|
|
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
|
|
|
|
|
|
|
|
return Dependable(
|
|
|
|
func=_list_files,
|
|
|
|
responses={
|
|
|
|
**_RESPONSE_OK,
|
|
|
|
status.HTTP_404_NOT_FOUND: {
|
2023-10-26 13:58:02 +00:00
|
|
|
"description": "Remote path not found",
|
2023-10-23 21:32:25 +00:00
|
|
|
"content": None,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
)
|
2023-10-22 14:25:19 +00:00
|
|
|
|
|
|
|
|
2023-10-26 15:46:12 +00:00
|
|
|
LM_FILE = ListManager.from_lister(
|
2023-10-26 14:31:12 +00:00
|
|
|
get_file_lister(rp=RP_FILE, re=re.compile(r"[^/]$", flags=re.IGNORECASE))
|
|
|
|
)
|
2023-10-26 15:46:12 +00:00
|
|
|
LM_IMAGE = ListManager.from_lister(
|
2023-10-26 14:31:12 +00:00
|
|
|
get_file_lister(
|
|
|
|
rp=RP_IMAGE, re=re.compile(r"\.(gif|jpe?g|tiff?|png|bmp)$", flags=re.IGNORECASE)
|
|
|
|
)
|
|
|
|
)
|
2023-10-26 15:46:12 +00:00
|
|
|
LM_TEXT = ListManager.from_lister(
|
2023-10-26 14:31:12 +00:00
|
|
|
get_file_lister(rp=RP_TEXT, re=re.compile(r"\.(txt|md)$", flags=re.IGNORECASE))
|
|
|
|
)
|
|
|
|
|
2023-10-26 15:46:12 +00:00
|
|
|
|
|
|
|
async def list_calendar_names() -> list[str]:
|
|
|
|
"""
|
|
|
|
List calendar names
|
|
|
|
"""
|
|
|
|
|
|
|
|
return await CalDAV.calendars
|
|
|
|
|
|
|
|
|
|
|
|
LM_CALENDARS = ListManager.from_lister_fn(list_calendar_names)
|
2023-10-22 14:25:19 +00:00
|
|
|
|
|
|
|
|
2023-10-26 15:58:54 +00:00
|
|
|
async def list_aggregate_names(
|
|
|
|
cfg: Config = Depends(get_config),
|
|
|
|
) -> list[str]:
|
|
|
|
"""
|
|
|
|
List aggregate calendar names
|
|
|
|
"""
|
|
|
|
|
|
|
|
if isinstance(cfg, params.Depends):
|
|
|
|
cfg = await get_config()
|
2023-10-26 15:46:12 +00:00
|
|
|
|
2023-10-26 15:58:54 +00:00
|
|
|
return list(cfg.calendar.aggregates.keys())
|
2023-10-26 15:46:12 +00:00
|
|
|
|
|
|
|
|
2023-10-26 15:58:54 +00:00
|
|
|
LM_AGGREGATES = ListManager.from_lister_fn(list_aggregate_names)
|