2023-10-27 21:12:28 +00:00
|
|
|
import logging
|
2023-09-08 02:45:00 +00:00
|
|
|
import re
|
2026-02-22 16:36:35 +00:00
|
|
|
from dataclasses import dataclass
|
2023-09-08 16:19:26 +00:00
|
|
|
from io import BytesIO
|
2023-09-08 02:45:00 +00:00
|
|
|
|
2023-10-27 21:12:28 +00:00
|
|
|
from asyncify import asyncify
|
2023-10-29 16:08:16 +00:00
|
|
|
from cachetools import cachedmethod
|
|
|
|
|
from redis import Redis
|
2023-09-08 02:45:00 +00:00
|
|
|
|
2023-10-29 16:08:16 +00:00
|
|
|
from .helpers import RedisCache, WebDAVclient, davkey
|
2023-09-08 02:45:00 +00:00
|
|
|
|
2023-10-27 21:12:28 +00:00
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
2026-02-22 16:36:35 +00:00
|
|
|
@dataclass(kw_only=True, frozen=True, slots=True)
|
|
|
|
|
class Settings:
|
|
|
|
|
url: str
|
|
|
|
|
username: str = "johndoe"
|
|
|
|
|
password: str = "s3cr3t!"
|
|
|
|
|
|
|
|
|
|
|
2023-09-08 02:45:00 +00:00
|
|
|
class WebDAV:
|
2026-02-22 16:36:35 +00:00
|
|
|
_webdav_client: WebDAVclient
|
|
|
|
|
_cache: RedisCache
|
|
|
|
|
|
|
|
|
|
def __init__(self, settings: Settings, redis: Redis, ttl_sec: int) -> None:
|
|
|
|
|
try:
|
|
|
|
|
self._webdav_client = WebDAVclient(
|
|
|
|
|
{
|
|
|
|
|
"webdav_hostname": settings.url,
|
|
|
|
|
"webdav_login": settings.username,
|
|
|
|
|
"webdav_password": settings.password,
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
assert self._webdav_client.check() is True
|
|
|
|
|
|
|
|
|
|
except AssertionError:
|
|
|
|
|
raise RuntimeError("WebDAV connection failed!")
|
|
|
|
|
|
|
|
|
|
self._cache = RedisCache(cache=redis, ttl=ttl_sec)
|
|
|
|
|
|
2023-10-27 21:12:28 +00:00
|
|
|
@asyncify
|
2026-02-22 16:36:35 +00:00
|
|
|
@cachedmethod(cache=lambda self: self._cache, key=davkey("list_files"))
|
|
|
|
|
def _list_files(self, directory: str = "") -> list[str]:
|
2023-09-08 02:45:00 +00:00
|
|
|
"""
|
2023-10-27 21:12:28 +00:00
|
|
|
List files in directory `directory` matching RegEx `regex`
|
2023-09-08 02:45:00 +00:00
|
|
|
"""
|
|
|
|
|
|
2026-02-22 16:36:35 +00:00
|
|
|
return self._webdav_client.list(directory)
|
|
|
|
|
|
|
|
|
|
async def list_files(
|
|
|
|
|
self,
|
|
|
|
|
directory: str = "",
|
|
|
|
|
*,
|
|
|
|
|
regex: re.Pattern[str] = re.compile(""),
|
|
|
|
|
) -> list[str]:
|
|
|
|
|
_logger.debug(f"list_files {directory!r} ({regex!r})")
|
2023-09-08 02:45:00 +00:00
|
|
|
|
2026-02-22 16:36:35 +00:00
|
|
|
ls = await self._list_files(directory)
|
2023-10-27 21:12:28 +00:00
|
|
|
return [path for path in ls if regex.search(path)]
|
2023-09-08 02:45:00 +00:00
|
|
|
|
2023-10-27 21:12:28 +00:00
|
|
|
@asyncify
|
2026-02-22 16:36:35 +00:00
|
|
|
@cachedmethod(cache=lambda self: self._cache, key=davkey("exists"))
|
|
|
|
|
def exists(self, path: str) -> bool:
|
2023-09-08 02:45:00 +00:00
|
|
|
"""
|
2023-10-27 21:12:28 +00:00
|
|
|
`True` iff there is a WebDAV resource at `path`
|
2023-09-08 02:45:00 +00:00
|
|
|
"""
|
|
|
|
|
|
2023-10-27 21:12:28 +00:00
|
|
|
_logger.debug(f"file_exists {path!r}")
|
2026-02-22 16:36:35 +00:00
|
|
|
return self._webdav_client.check(path)
|
2023-09-08 02:45:00 +00:00
|
|
|
|
2023-10-27 21:12:28 +00:00
|
|
|
@asyncify
|
2026-02-22 16:36:35 +00:00
|
|
|
@cachedmethod(cache=lambda self: self._cache, key=davkey("read_bytes"))
|
|
|
|
|
def read_bytes(self, path: str) -> bytes:
|
2023-09-08 02:45:00 +00:00
|
|
|
"""
|
2023-10-27 21:12:28 +00:00
|
|
|
Load WebDAV file from `path` as bytes
|
2023-09-08 02:45:00 +00:00
|
|
|
"""
|
|
|
|
|
|
2023-10-27 21:12:28 +00:00
|
|
|
_logger.debug(f"read_bytes {path!r}")
|
2023-09-08 16:19:26 +00:00
|
|
|
buffer = BytesIO()
|
2026-02-22 16:36:35 +00:00
|
|
|
self._webdav_client.download_from(buffer, path)
|
2023-09-08 19:08:13 +00:00
|
|
|
buffer.seek(0)
|
|
|
|
|
|
2023-09-08 16:19:26 +00:00
|
|
|
return buffer.read()
|
2023-09-08 02:45:00 +00:00
|
|
|
|
2026-02-22 16:36:35 +00:00
|
|
|
async def read_str(self, path: str, encoding="utf-8") -> str:
|
2023-09-08 02:45:00 +00:00
|
|
|
"""
|
2023-10-27 21:12:28 +00:00
|
|
|
Load WebDAV file from `path` as string
|
2023-09-08 02:45:00 +00:00
|
|
|
"""
|
|
|
|
|
|
2023-10-27 21:12:28 +00:00
|
|
|
_logger.debug(f"read_str {path!r}")
|
2026-02-22 16:36:35 +00:00
|
|
|
return (await self.read_bytes(path)).decode(encoding=encoding).strip()
|
2023-09-08 02:45:00 +00:00
|
|
|
|
2023-10-27 21:12:28 +00:00
|
|
|
@asyncify
|
2026-02-22 16:36:35 +00:00
|
|
|
def write_bytes(self, path: str, buffer: bytes) -> None:
|
2023-09-08 02:45:00 +00:00
|
|
|
"""
|
2023-10-27 21:12:28 +00:00
|
|
|
Write bytes from `buffer` into WebDAV file at `path`
|
2023-09-08 02:45:00 +00:00
|
|
|
"""
|
|
|
|
|
|
2023-10-27 21:12:28 +00:00
|
|
|
_logger.debug(f"write_bytes {path!r}")
|
2026-02-22 16:36:35 +00:00
|
|
|
self._webdav_client.upload_to(buffer, path)
|
2023-09-11 03:12:24 +00:00
|
|
|
|
2023-10-27 21:12:28 +00:00
|
|
|
# invalidate cache entry
|
2026-02-22 16:36:35 +00:00
|
|
|
# begin slice at 0 (there is no "self" argument)
|
|
|
|
|
del self._cache[davkey("read_bytes", slice(0, None))(path)]
|
2023-09-11 02:59:11 +00:00
|
|
|
|
2026-02-22 16:36:35 +00:00
|
|
|
async def write_str(self, path: str, content: str, encoding="utf-8") -> None:
|
2023-09-08 02:45:00 +00:00
|
|
|
"""
|
2023-10-27 21:12:28 +00:00
|
|
|
Write string from `content` into WebDAV file at `path`
|
2023-09-08 02:45:00 +00:00
|
|
|
"""
|
|
|
|
|
|
2023-10-27 21:12:28 +00:00
|
|
|
_logger.debug(f"write_str {path!r}")
|
2026-02-22 16:36:35 +00:00
|
|
|
await self.write_bytes(path, content.encode(encoding=encoding))
|