""" VNEWS Shorts Runtime Cache - External Updater Module GitHub Actions fetches YouTube shorts via yt-dlp -> POST to /api/shorts/update Space saves to RAM cache + persistent file if /data available """ import os import json import time import threading # Runtime cache (RAM) _shorts_runtime_cache = None _shorts_cache_ts = 0 _shorts_cache_lock = threading.Lock() # Secret for authenticating update requests SHORTS_UPDATE_SECRET = os.environ.get("SHORTS_UPDATE_SECRET", "vnews-shorts-2026") # Paths SHORTS_CACHE_FILE = "/data/shorts_runtime_cache.json" if os.path.isdir("/data") else "/app/shorts_runtime_cache.json" def get_runtime_cache(): """Get cached shorts (from RAM or file fallback)""" global _shorts_runtime_cache, _shorts_cache_ts with _shorts_cache_lock: if _shorts_runtime_cache is not None: age = time.time() - _shorts_cache_ts if age < 7200: # 2h fresh return _shorts_runtime_cache # Try file fallback try: if os.path.exists(SHORTS_CACHE_FILE): with open(SHORTS_CACHE_FILE, "r", encoding="utf-8") as f: data = json.load(f) age = time.time() - data.get("ts", 0) if age < 86400: # 24h stale limit items = data.get("items", []) with _shorts_cache_lock: _shorts_runtime_cache = items _shorts_cache_ts = data.get("ts", time.time()) return items except Exception as e: print(f"[cache] read error: {e}") return None def set_runtime_cache(items): """Update runtime cache from external data""" global _shorts_runtime_cache, _shorts_cache_ts ts = time.time() with _shorts_cache_lock: _shorts_runtime_cache = items _shorts_cache_ts = ts # Also write to file (persistent if /data mounted) try: os.makedirs(os.path.dirname(SHORTS_CACHE_FILE), exist_ok=True) payload = {"items": items, "ts": ts, "count": len(items)} with open(SHORTS_CACHE_FILE, "w", encoding="utf-8") as f: json.dump(payload, f, ensure_ascii=False, indent=2) print(f"[cache] saved {len(items)} shorts to {SHORTS_CACHE_FILE}") except Exception as e: print(f"[cache] write skipped: {e}") return len(items) def get_cache_status(): """Return status dict for the cache""" cache = None with _shorts_cache_lock: if _shorts_runtime_cache is not None: cache = _shorts_runtime_cache age = int(time.time() - _shorts_cache_ts) else: age = -1 return { "cached": cache is not None, "count": len(cache) if cache else 0, "age_seconds": age, "has_persistent": os.path.isdir("/data"), "cache_file_exists": os.path.exists(SHORTS_CACHE_FILE), }