f
This commit is contained in:
101
SYS/config.py
101
SYS/config.py
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user