Files
Curator/src/core/config.py
T

139 lines
5.4 KiB
Python

"""
Configuration management for Curator
Three levels of configuration:
1. Global config (.Curator.!gtag next to Curator.py) - app-wide settings
2. Folder config (.Curator.!ftag in project root) - folder-specific settings
3. File tags (.{filename}.!tag) - per-file metadata (handled in file.py)
"""
import json
from pathlib import Path
# Global config file (next to the main script)
GLOBAL_CONFIG_FILE = Path(__file__).parent.parent.parent / ".Curator.!gtag"
# Folder config filename
FOLDER_CONFIG_NAME = ".Curator.!ftag"
# =============================================================================
# GLOBAL CONFIG - Application settings
# =============================================================================
# Tag schema: the single source of truth for which tag categories exist, how
# they are derived from ČSFD, and how they map to the Filmotéka folder tree.
# Each entry:
# category — tag category name (e.g. "Žánr")
# csfd_field — CSFDMovie attribute feeding it (genres / year / countries /
# rating / directors / actors), or None for a user-only category
# transform — named value transform: None (str) or "decade_band" (rating)
# filmoteka_root — folder under the output: "" = at the root (e.g. genres),
# "Dle X" = grouping folder, None = filterable but no folders
# filename_template — optional per-category hardlink name used only inside this
# category's folders (e.g. "{year} - {title}{ext}"); fields:
# title / year / rating / ext / stem / filename. Absent/None =
# keep the pool filename. Pool files are never renamed by this.
DEFAULT_TAG_SCHEMA = [
{"category": "Žánr", "csfd_field": "genres", "transform": None, "filmoteka_root": ""},
{"category": "Rok", "csfd_field": "year", "transform": None, "filmoteka_root": "Dle roku"},
{"category": "Země původu", "csfd_field": "countries", "transform": None,
"filmoteka_root": "Dle země původu"},
{"category": "Hodnocení", "csfd_field": "rating", "transform": "decade_band",
"filmoteka_root": "Dle hodnocení"},
]
DEFAULT_GLOBAL_CONFIG = {
"window_geometry": "1200x800",
"window_maximized": False,
"last_folder": None,
"sidebar_width": 250,
"recent_folders": [],
"pool_dir": None, # managed pool root (single source of truth)
"filmoteka_dir": None, # generated Filmotéka output (hardlink tree)
"copyasis_folders": ["Seriály"], # pool subfolders mirrored 1:1 (copy-as-is)
"tag_schema": DEFAULT_TAG_SCHEMA, # tag categories + ČSFD/Filmotéka rules
}
def load_global_config() -> dict:
"""Load global application config"""
if GLOBAL_CONFIG_FILE.exists():
try:
with open(GLOBAL_CONFIG_FILE, "r", encoding="utf-8") as f:
config = json.load(f)
# Merge with defaults for any missing keys
for key, value in DEFAULT_GLOBAL_CONFIG.items():
if key not in config:
config[key] = value
return config
except Exception:
return DEFAULT_GLOBAL_CONFIG.copy()
return DEFAULT_GLOBAL_CONFIG.copy()
def save_global_config(cfg: dict):
"""Save global application config"""
with open(GLOBAL_CONFIG_FILE, "w", encoding="utf-8") as f:
json.dump(cfg, f, indent=2, ensure_ascii=False)
# =============================================================================
# FOLDER CONFIG - Per-folder settings
# =============================================================================
DEFAULT_FOLDER_CONFIG = {
"ignore_patterns": [],
"custom_tags": {}, # Additional tags specific to this folder
"recursive": True, # Whether to scan subfolders
"hardlink_output_dir": None, # Output directory for hardlink structure
"hardlink_categories": None, # Categories to include in hardlink (None = all)
}
def get_folder_config_path(folder: Path) -> Path:
"""Get path to folder config file"""
return folder / FOLDER_CONFIG_NAME
def load_folder_config(folder: Path) -> dict:
"""Load folder-specific config"""
config_path = get_folder_config_path(folder)
if config_path.exists():
try:
with open(config_path, "r", encoding="utf-8") as f:
config = json.load(f)
# Merge with defaults for any missing keys
for key, value in DEFAULT_FOLDER_CONFIG.items():
if key not in config:
config[key] = value
return config
except Exception:
return DEFAULT_FOLDER_CONFIG.copy()
return DEFAULT_FOLDER_CONFIG.copy()
def save_folder_config(folder: Path, cfg: dict):
"""Save folder-specific config"""
config_path = get_folder_config_path(folder)
with open(config_path, "w", encoding="utf-8") as f:
json.dump(cfg, f, indent=2, ensure_ascii=False)
def folder_has_config(folder: Path) -> bool:
"""Check if folder has a tagger config"""
return get_folder_config_path(folder).exists()
# =============================================================================
# BACKWARDS COMPATIBILITY
# =============================================================================
def load_config():
"""Legacy function - returns global config"""
return load_global_config()
def save_config(cfg: dict):
"""Legacy function - saves global config"""
save_global_config(cfg)