This commit is contained in:
nose
2025-12-13 12:09:50 -08:00
parent 30eb628aa3
commit 52a79b0086
16 changed files with 729 additions and 655 deletions

View File

@@ -5,17 +5,22 @@ import sys
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple
import httpx
from SYS.logger import debug, log
from SYS.utils_constant import mime_maps
from Store._base import Store
_HYDRUS_INIT_CHECK_CACHE: dict[tuple[str, str], tuple[bool, Optional[str]]] = {}
class HydrusNetwork(Store):
"""File storage backend for Hydrus client.
Each instance represents a specific Hydrus client connection.
Maintains its own HydrusClient with session key.
Maintains its own HydrusClient.
"""
def __new__(cls, *args: Any, **kwargs: Any) -> "HydrusNetwork":
@@ -64,22 +69,67 @@ class HydrusNetwork(Store):
self.NAME = instance_name
self.API = api_key
self.URL = url
# Create persistent client with session key for this instance
self._client = HydrusClient(url=url, access_key=api_key)
self.URL = url.rstrip("/")
# Self health-check: acquire a session key immediately so broken configs
# fail-fast and the registry can skip registering this backend.
try:
if self._client is not None:
self._client.ensure_session_key()
except Exception as exc:
# Best-effort cleanup so partially constructed objects don't linger.
# Total count (best-effort, used for startup diagnostics)
self.total_count: Optional[int] = None
# Self health-check: validate the URL is reachable and the access key is accepted.
# This MUST NOT attempt to acquire a session key.
cache_key = (self.URL, self.API)
cached = _HYDRUS_INIT_CHECK_CACHE.get(cache_key)
if cached is not None:
ok, err = cached
if not ok:
raise RuntimeError(f"Hydrus '{self.NAME}' unavailable: {err or 'Unavailable'}")
else:
api_version_url = f"{self.URL}/api_version"
verify_key_url = f"{self.URL}/verify_access_key"
try:
self._client = None
except Exception:
pass
raise RuntimeError(f"Hydrus '{self.NAME}' unavailable: {exc}") from exc
with httpx.Client(timeout=5.0, verify=False, follow_redirects=True) as client:
version_resp = client.get(api_version_url)
version_resp.raise_for_status()
version_payload = version_resp.json()
if not isinstance(version_payload, dict):
raise RuntimeError("Hydrus /api_version returned an unexpected response")
verify_resp = client.get(
verify_key_url,
headers={"Hydrus-Client-API-Access-Key": self.API},
)
verify_resp.raise_for_status()
verify_payload = verify_resp.json()
if not isinstance(verify_payload, dict):
raise RuntimeError("Hydrus /verify_access_key returned an unexpected response")
_HYDRUS_INIT_CHECK_CACHE[cache_key] = (True, None)
except Exception as exc:
err = str(exc)
_HYDRUS_INIT_CHECK_CACHE[cache_key] = (False, err)
raise RuntimeError(f"Hydrus '{self.NAME}' unavailable: {err}") from exc
# Create a persistent client for this instance (auth via access key by default).
self._client = HydrusClient(url=self.URL, access_key=self.API)
# Best-effort total count (fast on Hydrus side; does not fetch IDs/hashes).
try:
payload = self._client.search_files(
tags=["system:everything"],
return_hashes=False,
return_file_ids=False,
return_file_count=True,
)
count_val = None
if isinstance(payload, dict):
count_val = payload.get("file_count")
if count_val is None:
count_val = payload.get("file_count_inclusive")
if count_val is None:
count_val = payload.get("num_files")
if isinstance(count_val, int):
self.total_count = count_val
except Exception as exc:
debug(f"Hydrus total count unavailable for '{self.NAME}': {exc}", file=sys.stderr)
def name(self) -> str:
return self.NAME