diff --git a/blueprints/root/modules/euroring.py b/blueprints/root/modules/euroring.py index bf91bd6..dbdb002 100644 --- a/blueprints/root/modules/euroring.py +++ b/blueprints/root/modules/euroring.py @@ -1,91 +1,28 @@ from __future__ import annotations import atexit -import json import os import re -import tempfile from time import time from urllib.parse import urlsplit -import fcntl import requests from apscheduler.schedulers.background import BackgroundScheduler from apscheduler.triggers.interval import IntervalTrigger - -def _cache_dir() -> str: - path = os.environ.get("LAIR_CACHE_DIR", "/tmp/lair-cache") - os.makedirs(path, exist_ok=True) - return path +from blueprints.risdeveau.modules.api.scheduler_guard import should_start_scheduler +from blueprints.risdeveau.modules.api.shared_cache import ( + atomic_write_json, + cache_file, + load_json_if_newer, +) -def _cache_file(name: str) -> str: - return os.path.join(_cache_dir(), f"{name}.json") - - -def _atomic_write_json(path: str, payload: dict) -> None: - parent = os.path.dirname(path) or "." - fd, tmp = tempfile.mkstemp(prefix=".tmp-", dir=parent) - try: - with os.fdopen(fd, "w", encoding="utf-8") as f: - json.dump(payload, f, ensure_ascii=False, separators=(",", ":")) - os.replace(tmp, path) - finally: - try: - if os.path.exists(tmp): - os.unlink(tmp) - except Exception: - pass - - -_CACHE_MTIME = 0.0 -_LOCK_FD: int | None = None - - -def _load_json_if_newer(path: str, last_mtime: float) -> tuple[dict | None, float]: - try: - stat = os.stat(path) - except FileNotFoundError: - return None, last_mtime - except Exception: - return None, last_mtime - - mtime = float(stat.st_mtime) - if mtime <= float(last_mtime): - return None, last_mtime - - try: - with open(path, "r", encoding="utf-8") as f: - return json.load(f), mtime - except Exception: - return None, last_mtime - - -def _should_start_scheduler() -> bool: - global _LOCK_FD - if _LOCK_FD is not None: - return True - - lock_path = os.environ.get("LAIR_SCHED_LOCK", "/tmp/lair-scheduler.lock") - fd = os.open(lock_path, os.O_CREAT | os.O_RDWR, 0o644) - - try: - fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB) - _LOCK_FD = fd - return True - except BlockingIOError: - os.close(fd) - return False - except Exception: - try: - os.close(fd) - finally: - return False - - -EURORING_SOURCE_URL = "https://euroring.neocities.org/scripts/onionring-variables.js" -EURORING_SITE_URL = "https://lair.moe" +EURORING_SOURCE_URL = os.environ.get( + "EURORING_SOURCE_URL", + "https://euroring.neocities.org/scripts/onionring-variables.js", +) +EURORING_SITE_URL = os.environ.get("EURORING_SITE_URL", "https://lair.moe") EURORING_TIMEOUT = float(os.environ.get("EURORING_TIMEOUT", "10")) @@ -102,8 +39,9 @@ data = { "last_updated": 0, } -_IS_WRITER = _should_start_scheduler() -_CACHE_PATH = _cache_file("webring") +_IS_WRITER = should_start_scheduler() if data["enabled"] else False +_CACHE_PATH = cache_file("webring") +_CACHE_MTIME = 0.0 def _site_key(url: str) -> tuple[str, int | None, str, str]: @@ -127,7 +65,10 @@ def _parse_js_string_value(text: str, variable: str) -> str | None: def _parse_bool_value(text: str, variable: str) -> bool | None: - match = re.search(rf"var\s+{re.escape(variable)}\s*=\s*(true|false)\s*;", text) + match = re.search( + rf"var\s+{re.escape(variable)}\s*=\s*(true|false)\s*;", + text, + ) if not match: return None return match.group(1) == "true" @@ -195,7 +136,7 @@ def refresh_cache() -> None: if _IS_WRITER: return - payload, mtime = _load_json_if_newer(_CACHE_PATH, _CACHE_MTIME) + payload, mtime = load_json_if_newer(_CACHE_PATH, _CACHE_MTIME) if payload is None: return @@ -203,10 +144,12 @@ def refresh_cache() -> None: _CACHE_MTIME = mtime + def _persist_cache() -> None: if not _IS_WRITER: return - _atomic_write_json(_CACHE_PATH, data) + atomic_write_json(_CACHE_PATH, data) + def fetch_webring() -> None: