This commit is contained in:
2026-01-23 16:46:48 -08:00
parent 797b5fee40
commit b3a4ba14e5
5 changed files with 193 additions and 106 deletions

View File

@@ -5,6 +5,8 @@ from __future__ import annotations
import re
import tempfile
import json
import sqlite3
import time
from pathlib import Path
from typing import Any, Dict, Optional, List
from SYS.logger import log
@@ -15,6 +17,8 @@ DEFAULT_CONFIG_FILENAME = "config.conf"
SCRIPT_DIR = Path(__file__).resolve().parent
_CONFIG_CACHE: Dict[str, Dict[str, Any]] = {}
_CONFIG_SAVE_MAX_RETRIES = 5
_CONFIG_SAVE_RETRY_DELAY = 0.15
def global_config() -> List[Dict[str, Any]]:
@@ -52,26 +56,36 @@ def get_hydrus_instance(
) -> Optional[Dict[str, Any]]:
"""Get a specific Hydrus instance config by name.
Supports multiple formats:
- Current: config["store"]["hydrusnetwork"][instance_name]
- Legacy: config["storage"]["hydrus"][instance_name]
- Old: config["HydrusNetwork"][instance_name]
Args:
config: Configuration dict
instance_name: Name of the Hydrus instance (default: "home")
Returns:
Dict with access key and URL, or None if not found
Supports modern config plus a fallback when no exact match exists.
"""
# Canonical: config["store"]["hydrusnetwork"]["home"]
store = config.get("store", {})
if isinstance(store, dict):
hydrusnetwork = store.get("hydrusnetwork", {})
if isinstance(hydrusnetwork, dict):
instance = hydrusnetwork.get(instance_name)
if isinstance(instance, dict):
return instance
if not isinstance(store, dict):
return None
hydrusnetwork = store.get("hydrusnetwork", {})
if not isinstance(hydrusnetwork, dict) or not hydrusnetwork:
return None
instance = hydrusnetwork.get(instance_name)
if isinstance(instance, dict):
return instance
target = str(instance_name or "").lower()
for name, conf in hydrusnetwork.items():
if isinstance(conf, dict) and str(name).lower() == target:
return conf
keys = sorted(hydrusnetwork.keys())
for key in keys:
if not str(key or "").startswith("new_"):
candidate = hydrusnetwork.get(key)
if isinstance(candidate, dict):
return candidate
first_key = keys[0]
candidate = hydrusnetwork.get(first_key)
if isinstance(candidate, dict):
return candidate
return None
@@ -349,7 +363,9 @@ def load_config(
def reload_config(
config_dir: Optional[Path] = None, filename: str = DEFAULT_CONFIG_FILENAME
) -> Dict[str, Any]:
cache_key = _make_cache_key(config_dir, filename, None)
base_dir = config_dir or SCRIPT_DIR
config_path = base_dir / filename
cache_key = _make_cache_key(config_dir, filename, config_path)
_CONFIG_CACHE.pop(cache_key, None)
return load_config(config_dir=config_dir, filename=filename)
@@ -359,42 +375,61 @@ def save_config(
config: Dict[str, Any],
config_dir: Optional[Path] = None,
filename: str = DEFAULT_CONFIG_FILENAME,
) -> None:
) -> int:
base_dir = config_dir or SCRIPT_DIR
config_path = base_dir / filename
# 1. Save to Database
try:
from SYS.database import db, save_config_value
def _write_entries() -> int:
count = 0
with db.transaction():
# Replace the table contents so removed entries disappear from the DB.
db.execute("DELETE FROM config")
for key, value in config.items():
if key in ('store', 'provider', 'tool'):
if isinstance(value, dict):
for subtype, instances in value.items():
if isinstance(instances, dict):
# provider/tool are usually config[cat][subtype][key]
# but store is config['store'][subtype][name][key]
if key == 'store':
for name, settings in instances.items():
if isinstance(settings, dict):
for k, v in settings.items():
save_config_value(key, subtype, name, k, v)
count += 1
else:
for k, v in instances.items():
save_config_value(key, subtype, "default", k, v)
count += 1
else:
# global settings
if not key.startswith("_"):
if not key.startswith("_") and value is not None:
save_config_value("global", "none", "none", key, value)
except Exception as e:
log(f"Failed to save config to database: {e}")
count += 1
return count
saved_entries = 0
attempts = 0
while True:
try:
saved_entries = _write_entries()
log(f"Saved {saved_entries} configuration entries to database.")
break
except sqlite3.OperationalError as exc:
attempts += 1
locked_error = "locked" in str(exc).lower()
if not locked_error or attempts >= _CONFIG_SAVE_MAX_RETRIES:
log(f"CRITICAL: Failed to save config to database: {exc}")
raise
delay = _CONFIG_SAVE_RETRY_DELAY * attempts
log(
f"Database busy locking medios.db (attempt {attempts}/{_CONFIG_SAVE_MAX_RETRIES}); retrying in {delay:.2f}s."
)
time.sleep(delay)
except Exception as exc:
log(f"CRITICAL: Failed to save config to database: {exc}")
raise
cache_key = _make_cache_key(config_dir, filename, config_path)
clear_config_cache()
_CONFIG_CACHE[cache_key] = config
return saved_entries
def load() -> Dict[str, Any]:
@@ -402,6 +437,6 @@ def load() -> Dict[str, Any]:
return load_config()
def save(config: Dict[str, Any]) -> None:
def save(config: Dict[str, Any]) -> int:
"""Persist *config* back to disk."""
save_config(config)
return save_config(config)