update and cleanup repo
This commit is contained in:
+122
-228
@@ -95,8 +95,6 @@ def clear_config_cache() -> None:
|
||||
def _log_config_load_summary(config: Dict[str, Any]) -> None:
|
||||
try:
|
||||
plugin_block = config.get("plugin")
|
||||
if not isinstance(plugin_block, dict):
|
||||
plugin_block = config.get("provider")
|
||||
if isinstance(plugin_block, dict):
|
||||
# Count distinct plugin names; note multi-instance plugins appear once per name
|
||||
plugin_names = list(plugin_block.keys())
|
||||
@@ -265,7 +263,9 @@ def set_nested_config_value(
|
||||
def get_hydrus_instance(
|
||||
config: Dict[str, Any], instance_name: str = "home"
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
"""Get a specific Hydrus instance config by name from plugin/provider config."""
|
||||
"""Get a specific Hydrus instance config by name from plugin config."""
|
||||
_canonicalize_plugin_config(config)
|
||||
|
||||
def _lookup_in(source: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
||||
if not isinstance(source, dict) or not source:
|
||||
return None
|
||||
@@ -286,16 +286,13 @@ def get_hydrus_instance(
|
||||
candidate = source.get(first_key) if first_key else None
|
||||
return candidate if isinstance(candidate, dict) else None
|
||||
|
||||
# New format: config["plugin"]["hydrusnetwork"] or config["provider"]["hydrusnetwork"]
|
||||
# (both point to the same dict after normalization)
|
||||
for section in ("plugin", "provider"):
|
||||
section_cfg = config.get(section)
|
||||
if isinstance(section_cfg, dict):
|
||||
hydrus_cfg = section_cfg.get("hydrusnetwork")
|
||||
if isinstance(hydrus_cfg, dict):
|
||||
result = _lookup_in(hydrus_cfg)
|
||||
if result is not None:
|
||||
return result
|
||||
plugin_cfg = config.get("plugin")
|
||||
if isinstance(plugin_cfg, dict):
|
||||
hydrus_cfg = plugin_cfg.get("hydrusnetwork")
|
||||
if isinstance(hydrus_cfg, dict):
|
||||
result = _lookup_in(hydrus_cfg)
|
||||
if result is not None:
|
||||
return result
|
||||
|
||||
return None
|
||||
|
||||
@@ -339,17 +336,17 @@ def get_hydrus_url(config: Dict[str, Any], instance_name: str = "home") -> Optio
|
||||
return str(url).strip() if url else None
|
||||
|
||||
|
||||
def get_provider_block(config: Dict[str, Any], name: str) -> Dict[str, Any]:
|
||||
_normalize_plugin_config_aliases(config)
|
||||
provider_cfg = config.get("provider")
|
||||
if not isinstance(provider_cfg, dict):
|
||||
def get_plugin_block(config: Dict[str, Any], name: str) -> Dict[str, Any]:
|
||||
_canonicalize_plugin_config(config)
|
||||
plugin_cfg = config.get("plugin")
|
||||
if not isinstance(plugin_cfg, dict):
|
||||
return {}
|
||||
normalized = _normalize_provider_name(name)
|
||||
if normalized:
|
||||
block = provider_cfg.get(normalized)
|
||||
block = plugin_cfg.get(normalized)
|
||||
if isinstance(block, dict):
|
||||
return block
|
||||
for key, block in provider_cfg.items():
|
||||
for key, block in plugin_cfg.items():
|
||||
if not isinstance(block, dict):
|
||||
continue
|
||||
if _normalize_provider_name(key) == normalized:
|
||||
@@ -358,13 +355,13 @@ def get_provider_block(config: Dict[str, Any], name: str) -> Dict[str, Any]:
|
||||
|
||||
|
||||
def get_soulseek_username(config: Dict[str, Any]) -> Optional[str]:
|
||||
block = get_provider_block(config, "soulseek")
|
||||
block = get_plugin_block(config, "soulseek")
|
||||
val = block.get("username") or block.get("USERNAME")
|
||||
return str(val).strip() if val else None
|
||||
|
||||
|
||||
def get_soulseek_password(config: Dict[str, Any]) -> Optional[str]:
|
||||
block = get_provider_block(config, "soulseek")
|
||||
block = get_plugin_block(config, "soulseek")
|
||||
val = block.get("password") or block.get("PASSWORD")
|
||||
return str(val).strip() if val else None
|
||||
|
||||
@@ -415,33 +412,33 @@ def resolve_output_dir(config: Dict[str, Any]) -> Path:
|
||||
|
||||
|
||||
def get_local_storage_path(config: Dict[str, Any]) -> Optional[Path]:
|
||||
"""Get local storage path from config.
|
||||
"""Return the configured default local plugin destination path.
|
||||
|
||||
Supports multiple formats:
|
||||
- Old: config["storage"]["local"]["path"]
|
||||
- Old: config["Local"]["path"]
|
||||
|
||||
Args:
|
||||
config: Configuration dict
|
||||
|
||||
Returns:
|
||||
Path object if found, None otherwise
|
||||
This helper is intentionally narrow: it reports a real local library/export
|
||||
root only when the canonical `plugin.local` config defines one. Callers that
|
||||
want a staging/output directory should use `resolve_output_dir(...)` instead.
|
||||
"""
|
||||
# Fall back to storage.local.path format
|
||||
storage = config.get("storage", {})
|
||||
if isinstance(storage, dict):
|
||||
local_config = storage.get("local", {})
|
||||
if isinstance(local_config, dict):
|
||||
path_str = local_config.get("path")
|
||||
if path_str:
|
||||
return expand_path(path_str)
|
||||
local_block = get_plugin_block(config, "local")
|
||||
if not isinstance(local_block, dict) or not local_block:
|
||||
return None
|
||||
|
||||
# Fall back to old Local format
|
||||
local_config = config.get("Local", {})
|
||||
if isinstance(local_config, dict):
|
||||
path_str = local_config.get("path")
|
||||
if path_str:
|
||||
return expand_path(path_str)
|
||||
if _is_multi_instance_plugin_config(local_block):
|
||||
if "default" in local_block and isinstance(local_block.get("default"), dict):
|
||||
local_config = local_block.get("default")
|
||||
else:
|
||||
local_config = next(
|
||||
(value for value in local_block.values() if isinstance(value, dict)),
|
||||
None,
|
||||
)
|
||||
else:
|
||||
local_config = local_block
|
||||
|
||||
if not isinstance(local_config, dict):
|
||||
return None
|
||||
|
||||
path_str = local_config.get("path") or local_config.get("PATH")
|
||||
if path_str:
|
||||
return expand_path(path_str)
|
||||
|
||||
return None
|
||||
|
||||
@@ -449,7 +446,7 @@ def get_local_storage_path(config: Dict[str, Any]) -> Optional[Path]:
|
||||
def get_debrid_api_key(config: Dict[str, Any], service: str = "All-debrid") -> Optional[str]:
|
||||
"""Get Debrid API key from config.
|
||||
|
||||
Checks the plugin/provider block first (canonical format).
|
||||
Checks the plugin block first (canonical format).
|
||||
|
||||
Args:
|
||||
config: Configuration dict
|
||||
@@ -458,37 +455,23 @@ def get_debrid_api_key(config: Dict[str, Any], service: str = "All-debrid") -> O
|
||||
Returns:
|
||||
API key string if found, None otherwise
|
||||
"""
|
||||
# 1) Canonical plugin/provider block: config["plugin"]["alldebrid"]["api_key"]
|
||||
provider_block = config.get("provider") or config.get("plugin")
|
||||
if isinstance(provider_block, dict):
|
||||
alldebrid_entry = provider_block.get("alldebrid")
|
||||
_canonicalize_plugin_config(config)
|
||||
|
||||
# 1) Canonical plugin block: config["plugin"]["alldebrid"]["api_key"]
|
||||
plugin_block = config.get("plugin")
|
||||
if isinstance(plugin_block, dict):
|
||||
alldebrid_entry = plugin_block.get("alldebrid")
|
||||
if isinstance(alldebrid_entry, dict):
|
||||
for k in ("api_key", "API_KEY", "apikey", "APIKEY"):
|
||||
val = alldebrid_entry.get(k)
|
||||
if isinstance(val, str) and val.strip():
|
||||
return val.strip()
|
||||
|
||||
# 2) Migrated legacy debrid plugin entry: config["plugin"]["debrid"]["all-debrid"]["api_key"]
|
||||
if isinstance(provider_block, dict):
|
||||
service_key = str(service).strip().lower()
|
||||
debrid_plugin = provider_block.get("debrid")
|
||||
if isinstance(debrid_plugin, dict):
|
||||
entry = debrid_plugin.get(service_key)
|
||||
if isinstance(entry, dict):
|
||||
api_key = entry.get("api_key")
|
||||
return str(api_key).strip() if api_key else None
|
||||
if isinstance(entry, str):
|
||||
return entry.strip() or None
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def get_provider_credentials(config: Dict[str, Any], provider: str) -> Optional[Dict[str, str]]:
|
||||
"""Get provider credentials (email/password) from config.
|
||||
|
||||
Supports both formats:
|
||||
- New: config["provider"][provider] = {"email": "...", "password": "..."}
|
||||
- Old: config[provider.capitalize()] = {"email": "...", "password": "..."}
|
||||
def get_plugin_credentials(config: Dict[str, Any], provider: str) -> Optional[Dict[str, str]]:
|
||||
"""Get plugin credentials (email/password) from config.
|
||||
|
||||
Args:
|
||||
config: Configuration dict
|
||||
@@ -497,22 +480,11 @@ def get_provider_credentials(config: Dict[str, Any], provider: str) -> Optional[
|
||||
Returns:
|
||||
Dict with credentials if found, None otherwise
|
||||
"""
|
||||
# Try new format first
|
||||
provider_config = config.get("provider", {})
|
||||
if isinstance(provider_config, dict):
|
||||
creds = provider_config.get(provider.lower(), {})
|
||||
if isinstance(creds, dict) and creds:
|
||||
return creds
|
||||
_canonicalize_plugin_config(config)
|
||||
|
||||
# Fall back to old format (capitalized key)
|
||||
old_key_map = {
|
||||
"openlibrary": "OpenLibrary",
|
||||
"archive": "Archive",
|
||||
"soulseek": "Soulseek",
|
||||
}
|
||||
old_key = old_key_map.get(provider.lower())
|
||||
if old_key:
|
||||
creds = config.get(old_key, {})
|
||||
plugin_config = config.get("plugin", {})
|
||||
if isinstance(plugin_config, dict):
|
||||
creds = plugin_config.get(provider.lower(), {})
|
||||
if isinstance(creds, dict) and creds:
|
||||
return creds
|
||||
|
||||
@@ -522,19 +494,19 @@ def get_provider_credentials(config: Dict[str, Any], provider: str) -> Optional[
|
||||
def resolve_cookies_path(
|
||||
config: Dict[str, Any], script_dir: Optional[Path] = None
|
||||
) -> Optional[Path]:
|
||||
# Only support modular config style:
|
||||
# [tool=ytdlp]
|
||||
# Only support plugin config style:
|
||||
# [plugin=ytdlp]
|
||||
# cookies="C:\\path\\cookies.txt"
|
||||
values: list[Any] = []
|
||||
try:
|
||||
tool = config.get("tool")
|
||||
if isinstance(tool, dict):
|
||||
ytdlp = tool.get("ytdlp")
|
||||
plugin = config.get("plugin")
|
||||
if isinstance(plugin, dict):
|
||||
ytdlp = plugin.get("ytdlp")
|
||||
if isinstance(ytdlp, dict):
|
||||
values.append(ytdlp.get("cookies"))
|
||||
values.append(ytdlp.get("cookiefile"))
|
||||
except Exception as exc:
|
||||
logger.debug("resolve_cookies_path: failed to read tool.ytdlp cookies: %s", exc, exc_info=True)
|
||||
logger.debug("resolve_cookies_path: failed to read plugin.ytdlp cookies: %s", exc, exc_info=True)
|
||||
|
||||
base_dir = _resolve_app_root(script_dir)
|
||||
for value in values:
|
||||
@@ -627,54 +599,26 @@ def resolve_plugin_asset_path(
|
||||
return None
|
||||
|
||||
|
||||
def _normalize_plugin_config_aliases(config: Dict[str, Any]) -> None:
|
||||
def _canonicalize_plugin_config(config: Dict[str, Any]) -> None:
|
||||
if not isinstance(config, dict):
|
||||
return
|
||||
|
||||
config.pop("provider", None)
|
||||
config.pop("store", None)
|
||||
plugin_block = config.get("plugin")
|
||||
provider_block = config.get("provider")
|
||||
|
||||
normalized_provider: Dict[str, Any] = {}
|
||||
|
||||
if isinstance(provider_block, dict):
|
||||
for key, value in provider_block.items():
|
||||
normalized_key = _normalize_provider_name(key)
|
||||
if normalized_key and normalized_key not in normalized_provider:
|
||||
normalized_provider[normalized_key] = value
|
||||
normalized_plugin: Dict[str, Any] = {}
|
||||
|
||||
if isinstance(plugin_block, dict):
|
||||
for key, value in plugin_block.items():
|
||||
normalized_key = _normalize_provider_name(key)
|
||||
if normalized_key and normalized_key not in normalized_provider:
|
||||
normalized_provider[normalized_key] = value
|
||||
if normalized_key:
|
||||
normalized_plugin[normalized_key] = value
|
||||
|
||||
# Fold legacy config["store"] entries into the plugin namespace.
|
||||
# store format: {type: {instance_name: {key: val}}} — multi-instance.
|
||||
# After folding, remove config["store"] so it is no longer consulted.
|
||||
store_block = config.pop("store", None)
|
||||
if isinstance(store_block, dict):
|
||||
for store_type, instances in store_block.items():
|
||||
if not isinstance(instances, dict):
|
||||
continue
|
||||
normalized_key = _normalize_provider_name(store_type)
|
||||
if not normalized_key:
|
||||
continue
|
||||
existing = normalized_provider.get(normalized_key)
|
||||
if not isinstance(existing, dict):
|
||||
existing = {}
|
||||
normalized_provider[normalized_key] = existing
|
||||
for instance_name, settings in instances.items():
|
||||
if isinstance(settings, dict) and instance_name not in existing:
|
||||
existing[instance_name] = dict(settings)
|
||||
|
||||
if normalized_provider:
|
||||
config["provider"] = normalized_provider
|
||||
config["plugin"] = normalized_provider
|
||||
if normalized_plugin or isinstance(plugin_block, dict):
|
||||
config["plugin"] = normalized_plugin
|
||||
else:
|
||||
if isinstance(provider_block, dict):
|
||||
config["plugin"] = provider_block
|
||||
elif isinstance(plugin_block, dict):
|
||||
config["provider"] = plugin_block
|
||||
config.pop("plugin", None)
|
||||
|
||||
def _extract_api_key(value: Any) -> Optional[str]:
|
||||
if isinstance(value, dict):
|
||||
@@ -698,40 +642,24 @@ def _sync_alldebrid_api_key(config: Dict[str, Any]) -> None:
|
||||
if not isinstance(config, dict):
|
||||
return
|
||||
|
||||
_normalize_plugin_config_aliases(config)
|
||||
|
||||
providers = config.get("provider")
|
||||
if not isinstance(providers, dict):
|
||||
providers = {}
|
||||
config["provider"] = providers
|
||||
|
||||
provider_entry = providers.get("alldebrid")
|
||||
provider_section: Dict[str, Any] | None = None
|
||||
provider_key = None
|
||||
if isinstance(provider_entry, dict):
|
||||
provider_section = provider_entry
|
||||
provider_key = _extract_api_key(provider_section)
|
||||
elif isinstance(provider_entry, str):
|
||||
provider_key = provider_entry.strip()
|
||||
if provider_key:
|
||||
provider_section = {"api_key": provider_key}
|
||||
providers["alldebrid"] = provider_section
|
||||
|
||||
# If no key found in provider block, check for a migrated debrid plugin entry.
|
||||
# (rows_to_config migrates store.debrid.all-debrid → plugin.debrid.all-debrid)
|
||||
if not provider_key:
|
||||
plugin_block = config.get("plugin") or providers
|
||||
debrid_plugin = plugin_block.get("debrid") if isinstance(plugin_block, dict) else None
|
||||
if isinstance(debrid_plugin, dict):
|
||||
service_entry = debrid_plugin.get("all-debrid")
|
||||
legacy_key = _extract_api_key(service_entry) if service_entry else None
|
||||
if legacy_key:
|
||||
if provider_section is None:
|
||||
provider_section = {}
|
||||
providers["alldebrid"] = provider_section
|
||||
provider_section.setdefault("api_key", legacy_key)
|
||||
_canonicalize_plugin_config(config)
|
||||
|
||||
plugins = config.get("plugin")
|
||||
if not isinstance(plugins, dict):
|
||||
plugins = {}
|
||||
config["plugin"] = plugins
|
||||
|
||||
plugin_entry = plugins.get("alldebrid")
|
||||
plugin_section: Dict[str, Any] | None = None
|
||||
plugin_key = None
|
||||
if isinstance(plugin_entry, dict):
|
||||
plugin_section = plugin_entry
|
||||
plugin_key = _extract_api_key(plugin_section)
|
||||
elif isinstance(plugin_entry, str):
|
||||
plugin_key = plugin_entry.strip()
|
||||
if plugin_key:
|
||||
plugin_section = {"api_key": plugin_key}
|
||||
plugins["alldebrid"] = plugin_section
|
||||
|
||||
def _is_multi_instance_plugin_config(value: Any) -> bool:
|
||||
"""Return True if `value` looks like a multi-instance plugin config (dict-of-dicts).
|
||||
@@ -754,12 +682,9 @@ def _is_multi_instance_plugin_config(value: Any) -> bool:
|
||||
|
||||
def _flatten_config_entries(config: Dict[str, Any]) -> Dict[Tuple[str, str, str, str], Any]:
|
||||
entries: Dict[Tuple[str, str, str, str], Any] = {}
|
||||
_normalize_plugin_config_aliases(config)
|
||||
_canonicalize_plugin_config(config)
|
||||
for key, value in config.items():
|
||||
if key == 'plugin':
|
||||
# plugin == provider after normalization; skip duplicate
|
||||
continue
|
||||
if key == 'provider' and isinstance(value, dict):
|
||||
if key == 'plugin' and isinstance(value, dict):
|
||||
for subtype, plugin_cfg in value.items():
|
||||
if not isinstance(plugin_cfg, dict):
|
||||
continue
|
||||
@@ -773,21 +698,13 @@ def _flatten_config_entries(config: Dict[str, Any]) -> Dict[Tuple[str, str, str,
|
||||
else:
|
||||
# Single-instance: {key: val}
|
||||
for k, v in plugin_cfg.items():
|
||||
entries[('provider', subtype, 'default', k)] = v
|
||||
elif key in ('store', 'tool') and isinstance(value, dict):
|
||||
entries[('plugin', subtype, 'default', k)] = v
|
||||
elif key == 'tool' and isinstance(value, dict):
|
||||
for subtype, instances in value.items():
|
||||
if not isinstance(instances, dict):
|
||||
continue
|
||||
if key == 'store':
|
||||
# Legacy store: migrate to plugin category
|
||||
for name, settings in instances.items():
|
||||
if not isinstance(settings, dict):
|
||||
continue
|
||||
for k, v in settings.items():
|
||||
entries[('plugin', subtype, name, k)] = v
|
||||
else: # tool
|
||||
for k, v in instances.items():
|
||||
entries[(key, subtype, 'default', k)] = v
|
||||
for k, v in instances.items():
|
||||
entries[('tool', subtype, 'default', k)] = v
|
||||
elif not key.startswith('_') and value is not None:
|
||||
entries[('global', 'none', 'none', key)] = value
|
||||
return entries
|
||||
@@ -817,14 +734,6 @@ def _config_from_flattened_entries(
|
||||
config[key] = value
|
||||
continue
|
||||
|
||||
if category == "store":
|
||||
# Legacy: migrate to plugin namespace at reconstitution time
|
||||
plugin_block = config.setdefault("plugin", {})
|
||||
subtype_block = plugin_block.setdefault(subtype, {})
|
||||
item_block = subtype_block.setdefault(item_name, {})
|
||||
item_block[key] = value
|
||||
continue
|
||||
|
||||
if category == "plugin":
|
||||
plugin_block = config.setdefault("plugin", {})
|
||||
subtype_block = plugin_block.setdefault(subtype, {})
|
||||
@@ -835,7 +744,7 @@ def _config_from_flattened_entries(
|
||||
item_block[key] = value
|
||||
continue
|
||||
|
||||
if category in {"provider", "tool"}:
|
||||
if category == "tool":
|
||||
category_block = config.setdefault(category, {})
|
||||
subtype_block = category_block.setdefault(subtype, {})
|
||||
subtype_block[key] = value
|
||||
@@ -849,7 +758,7 @@ def _config_from_flattened_entries(
|
||||
if isinstance(item_block, dict):
|
||||
item_block[key] = value
|
||||
|
||||
_normalize_plugin_config_aliases(config)
|
||||
_canonicalize_plugin_config(config)
|
||||
_sync_alldebrid_api_key(config)
|
||||
return config
|
||||
|
||||
@@ -880,9 +789,9 @@ def _merge_non_conflicting_config_changes(
|
||||
def _extract_expected_alldebrid_key(config: Dict[str, Any]) -> Optional[str]:
|
||||
expected_key = None
|
||||
try:
|
||||
providers = config.get("provider", {}) if isinstance(config, dict) else {}
|
||||
if isinstance(providers, dict):
|
||||
entry = providers.get("alldebrid")
|
||||
plugins = config.get("plugin", {}) if isinstance(config, dict) else {}
|
||||
if isinstance(plugins, dict):
|
||||
entry = plugins.get("alldebrid")
|
||||
if entry is not None:
|
||||
if isinstance(entry, dict):
|
||||
for k in ("api_key", "API_KEY", "apikey", "APIKEY"):
|
||||
@@ -908,18 +817,10 @@ def load_config(*, emit_summary: bool = False) -> Dict[str, Any]:
|
||||
_CONFIG_SUMMARY_PENDING = False
|
||||
return _CONFIG_CACHE
|
||||
|
||||
# One-time DB migration: move category='store' rows to category='plugin'.
|
||||
# This is idempotent — a no-op if no store rows exist.
|
||||
try:
|
||||
from SYS.database import migrate_store_category_to_plugin
|
||||
migrate_store_category_to_plugin()
|
||||
except Exception:
|
||||
logger.debug("Store→plugin DB migration skipped or failed", exc_info=True)
|
||||
|
||||
# Load strictly from database
|
||||
db_config = get_config_all()
|
||||
if db_config:
|
||||
_normalize_plugin_config_aliases(db_config)
|
||||
_canonicalize_plugin_config(db_config)
|
||||
_sync_alldebrid_api_key(db_config)
|
||||
_CONFIG_CACHE = db_config
|
||||
_LAST_SAVED_CONFIG = deepcopy(db_config)
|
||||
@@ -1007,7 +908,7 @@ def _release_save_lock(lock_dir: Path) -> None:
|
||||
|
||||
def save_config(config: Dict[str, Any]) -> int:
|
||||
global _CONFIG_CACHE, _LAST_SAVED_CONFIG
|
||||
_normalize_plugin_config_aliases(config)
|
||||
_canonicalize_plugin_config(config)
|
||||
_sync_alldebrid_api_key(config)
|
||||
|
||||
# Acquire cross-process save lock to avoid concurrent saves from different
|
||||
@@ -1065,31 +966,39 @@ def save_config(config: Dict[str, Any]) -> int:
|
||||
# Proceed with writing when no conflicting external changes detected
|
||||
conn.execute("DELETE FROM config")
|
||||
for key, value in config_to_write.items():
|
||||
if key in ('store', 'provider', 'tool') and isinstance(value, dict):
|
||||
if key in ('plugin', 'tool') and isinstance(value, dict):
|
||||
for subtype, instances in value.items():
|
||||
if not isinstance(instances, dict):
|
||||
continue
|
||||
if key == 'store':
|
||||
for name, settings in instances.items():
|
||||
if isinstance(settings, dict):
|
||||
if key == 'plugin':
|
||||
normalized_subtype = _normalize_provider_name(subtype)
|
||||
if not normalized_subtype:
|
||||
continue
|
||||
if _is_multi_instance_plugin_config(instances):
|
||||
for name, settings in instances.items():
|
||||
if not isinstance(settings, dict):
|
||||
continue
|
||||
for k, v in settings.items():
|
||||
val_str = json.dumps(v) if not isinstance(v, str) else v
|
||||
conn.execute(
|
||||
"INSERT OR REPLACE INTO config (category, subtype, item_name, key, value) VALUES (?, ?, ?, ?, ?)",
|
||||
(key, subtype, name, k, val_str),
|
||||
("plugin", normalized_subtype, name, k, val_str),
|
||||
)
|
||||
count += 1
|
||||
else:
|
||||
for k, v in instances.items():
|
||||
val_str = json.dumps(v) if not isinstance(v, str) else v
|
||||
conn.execute(
|
||||
"INSERT OR REPLACE INTO config (category, subtype, item_name, key, value) VALUES (?, ?, ?, ?, ?)",
|
||||
("plugin", normalized_subtype, "default", k, val_str),
|
||||
)
|
||||
count += 1
|
||||
else:
|
||||
normalized_subtype = subtype
|
||||
if key == 'provider':
|
||||
normalized_subtype = _normalize_provider_name(subtype)
|
||||
if not normalized_subtype:
|
||||
continue
|
||||
for k, v in instances.items():
|
||||
val_str = json.dumps(v) if not isinstance(v, str) else v
|
||||
conn.execute(
|
||||
"INSERT OR REPLACE INTO config (category, subtype, item_name, key, value) VALUES (?, ?, ?, ?, ?)",
|
||||
(key, normalized_subtype, "default", k, val_str),
|
||||
("tool", subtype, "default", k, val_str),
|
||||
)
|
||||
count += 1
|
||||
else:
|
||||
@@ -1197,30 +1106,15 @@ def save_config_and_verify(config: Dict[str, Any], retries: int = 3, delay: floa
|
||||
# Nothing special to verify; return success.
|
||||
return saved
|
||||
|
||||
# Reload directly from disk and compare the canonical debrid/provider keys
|
||||
# Reload directly from disk and compare the canonical plugin key.
|
||||
clear_config_cache()
|
||||
reloaded = load_config()
|
||||
# Provider-level key
|
||||
prov_block = reloaded.get("provider", {}) if isinstance(reloaded, dict) else {}
|
||||
prov_key = None
|
||||
if isinstance(prov_block, dict):
|
||||
aentry = prov_block.get("alldebrid")
|
||||
if isinstance(aentry, dict):
|
||||
for k in ("api_key", "API_KEY", "apikey", "APIKEY"):
|
||||
v = aentry.get(k)
|
||||
if isinstance(v, str) and v.strip():
|
||||
prov_key = v.strip()
|
||||
break
|
||||
elif isinstance(aentry, str) and aentry.strip():
|
||||
prov_key = aentry.strip()
|
||||
|
||||
# Store-level key
|
||||
try:
|
||||
store_key = get_debrid_api_key(reloaded, service="All-debrid")
|
||||
reloaded_key = _extract_expected_alldebrid_key(reloaded)
|
||||
except Exception:
|
||||
store_key = None
|
||||
reloaded_key = None
|
||||
|
||||
if prov_key == expected_key or store_key == expected_key:
|
||||
if reloaded_key == expected_key:
|
||||
try:
|
||||
# Log a short, masked fingerprint to aid debugging without exposing the key itself
|
||||
import hashlib
|
||||
|
||||
Reference in New Issue
Block a user