advent22/api/advent22_api/core/webdav.py

137 lines
3.6 KiB
Python
Raw Normal View History

2023-10-27 21:12:28 +00:00
import functools
import logging
2023-09-08 02:45:00 +00:00
import re
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
import requests
from asyncify import asyncify
from cachetools import TTLCache, cachedmethod
from cachetools.keys import hashkey
2023-09-08 02:45:00 +00:00
from webdav3.client import Client as WebDAVclient
2023-09-08 18:17:18 +00:00
from .settings import SETTINGS
2023-09-08 02:45:00 +00:00
2023-10-27 21:12:28 +00:00
_logger = logging.getLogger(__name__)
def davkey(name, _, *args, **kwargs):
"""Return a cache key for use with cached methods."""
return hashkey(name, *args, **kwargs)
2023-09-08 02:45:00 +00:00
class WebDAV:
2023-10-27 21:12:28 +00:00
class __WebDAVclient(WebDAVclient):
def execute_request(
self,
action,
path,
data=None,
headers_ext=None,
) -> requests.Response:
res = super().execute_request(action, path, data, headers_ext)
# the "Content-Length" header can randomly be missing on txt files,
# this should fix that (probably serverside bug)
if action == "download" and "Content-Length" not in res.headers:
res.headers["Content-Length"] = str(len(res.text))
return res
_webdav_client = __WebDAVclient(
2023-09-08 02:45:00 +00:00
{
"webdav_hostname": SETTINGS.webdav.url,
"webdav_login": SETTINGS.webdav.username,
"webdav_password": SETTINGS.webdav.password,
}
)
2023-10-27 21:12:28 +00:00
_cache = TTLCache(
ttl=SETTINGS.webdav.cache_ttl,
maxsize=SETTINGS.webdav.cache_size,
)
2023-09-08 02:45:00 +00:00
@classmethod
2023-10-27 21:12:28 +00:00
@asyncify
@cachedmethod(
cache=lambda cls: cls._cache,
key=functools.partial(davkey, "list_files"),
)
def list_files(
2023-09-08 02:45:00 +00:00
cls,
directory: str = "",
2023-09-08 18:17:18 +00:00
*,
2023-09-08 02:45:00 +00:00
regex: re.Pattern[str] = re.compile(""),
) -> list[str]:
"""
2023-10-27 21:12:28 +00:00
List files in directory `directory` matching RegEx `regex`
2023-09-08 02:45:00 +00:00
"""
2023-10-27 21:12:28 +00:00
_logger.debug(f"list_files {directory!r}")
ls = cls._webdav_client.list(directory)
2023-09-08 02:45:00 +00:00
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
@classmethod
2023-10-27 21:12:28 +00:00
@asyncify
@cachedmethod(
cache=lambda cls: cls._cache,
key=functools.partial(davkey, "exists"),
)
def exists(cls, 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}")
return cls._webdav_client.check(path)
2023-09-08 02:45:00 +00:00
@classmethod
2023-10-27 21:12:28 +00:00
@asyncify
@cachedmethod(
cache=lambda cls: cls._cache,
key=functools.partial(davkey, "read_bytes"),
)
def read_bytes(cls, 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()
2023-10-27 21:12:28 +00:00
cls._webdav_client.download_from(buffer, path)
buffer.seek(0)
2023-09-08 16:19:26 +00:00
return buffer.read()
2023-09-08 02:45:00 +00:00
@classmethod
2023-09-08 16:19:26 +00:00
async def read_str(cls, 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}")
2023-09-08 16:19:26 +00:00
return (await cls.read_bytes(path)).decode(encoding=encoding).strip()
2023-09-08 02:45:00 +00:00
@classmethod
2023-10-27 21:12:28 +00:00
@asyncify
def write_bytes(cls, 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}")
cls._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
cls._cache.pop(hashkey("read_bytes", path))
2023-09-11 02:59:11 +00:00
2023-09-08 02:45:00 +00:00
@classmethod
2023-09-08 16:19:26 +00:00
async def write_str(cls, 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}")
await cls.write_bytes(path, content.encode(encoding=encoding))