Store library settings in the pool index and drop legacy sidecars
This commit is contained in:
+68
-97
@@ -7,30 +7,33 @@ from .tag_manager import TagManager
|
||||
from .pool_index import PoolIndex
|
||||
from .utils import list_files
|
||||
from typing import Iterable
|
||||
import fnmatch
|
||||
from src.core.config import (
|
||||
load_global_config, save_global_config,
|
||||
load_folder_config, save_folder_config
|
||||
load_global_config, save_global_config, DEFAULT_TAG_SCHEMA
|
||||
)
|
||||
|
||||
# Top-level folders inside the managed pool
|
||||
POOL_MOVIES = "Filmy"
|
||||
POOL_SERIES = "Seriály"
|
||||
|
||||
# Curator metadata files that must never be treated as content
|
||||
# Library-level settings that live in the pool index (not the global config).
|
||||
_LIBRARY_SETTINGS_DEFAULTS = {
|
||||
"tag_schema": DEFAULT_TAG_SCHEMA,
|
||||
"copyasis_folders": [POOL_SERIES],
|
||||
}
|
||||
|
||||
# Curator metadata files that must never be treated as content. The legacy
|
||||
# per-file (.!tag) and per-folder (.!ftag) sidecars are no longer written, but
|
||||
# stray ones are still skipped so they never leak into the Filmotéka.
|
||||
METADATA_SUFFIXES = (".!tag", ".!ftag", ".!gtag", ".!index")
|
||||
|
||||
|
||||
class FileManager:
|
||||
def __init__(self, tagmanager: TagManager):
|
||||
self.filelist: list[File] = []
|
||||
self.folders: list[Path] = []
|
||||
self.tagmanager = tagmanager
|
||||
self.on_files_changed = None # callback do GUI
|
||||
self.global_config = load_global_config()
|
||||
self.folder_configs: dict[Path, dict] = {} # folder -> config
|
||||
self.current_folder: Path | None = None
|
||||
self.index: PoolIndex | None = None # unified pool metadata index
|
||||
self.index: PoolIndex | None = None # unified pool metadata + settings
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Pool (single source of truth) and Filmotéka (generated output)
|
||||
@@ -51,27 +54,75 @@ class FileManager:
|
||||
pool = self.pool_dir
|
||||
return pool / POOL_SERIES if pool else None
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Pool index + library-level settings (stored in the index, per pool)
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def _open_index(self, pool: Path) -> PoolIndex:
|
||||
"""(Re)load the pool index and migrate any legacy global settings."""
|
||||
self.index = PoolIndex(pool)
|
||||
self._migrate_global_settings()
|
||||
return self.index
|
||||
|
||||
def _ensure_index(self) -> PoolIndex | None:
|
||||
"""Return the pool index, opening it lazily when a pool is configured."""
|
||||
if self.index is None and self.pool_dir:
|
||||
self._open_index(self.pool_dir)
|
||||
return self.index
|
||||
|
||||
def _migrate_global_settings(self) -> None:
|
||||
"""Move library settings from the old global config into the index once.
|
||||
|
||||
Earlier versions stored ``tag_schema`` / ``copyasis_folders`` in the
|
||||
global ``.!gtag``. They now belong to the pool; seed the index from the
|
||||
old values (if present and not already migrated) and drop them from the
|
||||
global config so it carries app data only.
|
||||
"""
|
||||
if self.index is None:
|
||||
return
|
||||
moved = False
|
||||
for key in _LIBRARY_SETTINGS_DEFAULTS:
|
||||
if key in self.global_config:
|
||||
if key not in self.index.settings:
|
||||
self.index.set_setting(key, self.global_config[key])
|
||||
del self.global_config[key]
|
||||
moved = True
|
||||
if moved:
|
||||
save_global_config(self.global_config)
|
||||
|
||||
def _get_setting(self, key: str):
|
||||
"""Read a library setting from the index (falls back to the default)."""
|
||||
default = _LIBRARY_SETTINGS_DEFAULTS[key]
|
||||
idx = self._ensure_index()
|
||||
if idx is None:
|
||||
return default
|
||||
return idx.get_setting(key, default)
|
||||
|
||||
def _set_setting(self, key: str, value) -> None:
|
||||
"""Persist a library setting into the index (needs a configured pool)."""
|
||||
idx = self._ensure_index()
|
||||
if idx is None:
|
||||
raise RuntimeError("Pool není nastaven — není kam uložit nastavení.")
|
||||
idx.set_setting(key, value)
|
||||
|
||||
@property
|
||||
def copyasis_folders(self) -> list[str]:
|
||||
"""Names of pool subfolders mirrored 1:1 (copy-as-is) into the output."""
|
||||
return self.global_config.get("copyasis_folders", [POOL_SERIES])
|
||||
return self._get_setting("copyasis_folders")
|
||||
|
||||
def set_copyasis_folders(self, names: list[str]) -> None:
|
||||
"""Set the copy-as-is subfolder list and persist it."""
|
||||
cleaned = [n.strip() for n in names if n.strip()]
|
||||
self.global_config["copyasis_folders"] = cleaned
|
||||
save_global_config(self.global_config)
|
||||
self._set_setting("copyasis_folders", cleaned)
|
||||
|
||||
@property
|
||||
def tag_schema(self) -> list[dict]:
|
||||
"""Tag categories + ČSFD/Filmotéka rules (see config.DEFAULT_TAG_SCHEMA)."""
|
||||
from src.core.config import DEFAULT_TAG_SCHEMA
|
||||
return self.global_config.get("tag_schema", DEFAULT_TAG_SCHEMA)
|
||||
return self._get_setting("tag_schema")
|
||||
|
||||
def set_tag_schema(self, schema: list[dict]) -> None:
|
||||
"""Set the tag schema and persist it."""
|
||||
self.global_config["tag_schema"] = schema
|
||||
save_global_config(self.global_config)
|
||||
self._set_setting("tag_schema", schema)
|
||||
|
||||
def filmoteka_category_roots(self) -> dict[str, str]:
|
||||
"""Category → output root-folder map derived from the tag schema.
|
||||
@@ -131,7 +182,7 @@ class FileManager:
|
||||
if not (movies and movies.is_dir() and pool):
|
||||
return
|
||||
|
||||
self.index = PoolIndex(pool)
|
||||
self._open_index(pool)
|
||||
for each in list_files(movies):
|
||||
if each.name.endswith(METADATA_SUFFIXES):
|
||||
continue
|
||||
@@ -252,7 +303,7 @@ class FileManager:
|
||||
movies.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
if self.index is None:
|
||||
self.index = PoolIndex(pool)
|
||||
self._open_index(pool)
|
||||
|
||||
source = Path(source)
|
||||
safe_title = title.strip() or source.stem
|
||||
@@ -325,80 +376,6 @@ class FileManager:
|
||||
self.on_files_changed(self.filelist)
|
||||
return file_obj
|
||||
|
||||
def append(self, folder: Path) -> None:
|
||||
"""Add a folder to scan for files"""
|
||||
self.folders.append(folder)
|
||||
self.current_folder = folder
|
||||
|
||||
# Update global config with last folder
|
||||
self.global_config["last_folder"] = str(folder)
|
||||
|
||||
# Update recent folders list
|
||||
recent = self.global_config.get("recent_folders", [])
|
||||
folder_str = str(folder)
|
||||
if folder_str in recent:
|
||||
recent.remove(folder_str)
|
||||
recent.insert(0, folder_str)
|
||||
self.global_config["recent_folders"] = recent[:10] # Keep max 10
|
||||
|
||||
save_global_config(self.global_config)
|
||||
|
||||
# Load folder-specific config
|
||||
folder_config = load_folder_config(folder)
|
||||
self.folder_configs[folder] = folder_config
|
||||
|
||||
# Get ignore patterns from folder config
|
||||
ignore_patterns = folder_config.get("ignore_patterns", [])
|
||||
|
||||
for each in list_files(folder):
|
||||
# Skip all Curator metadata files (.!tag / .!ftag / .!gtag / .!index)
|
||||
if each.name.endswith(METADATA_SUFFIXES):
|
||||
continue
|
||||
|
||||
full_path = each.as_posix()
|
||||
|
||||
# Check against ignore patterns
|
||||
if any(
|
||||
fnmatch.fnmatch(each.name, pat) or fnmatch.fnmatch(full_path, pat)
|
||||
for pat in ignore_patterns
|
||||
):
|
||||
continue
|
||||
|
||||
file_obj = File(each, self.tagmanager)
|
||||
self.filelist.append(file_obj)
|
||||
|
||||
def get_folder_config(self, folder: Path = None) -> dict:
|
||||
"""Get config for a folder (or current folder if not specified)"""
|
||||
if folder is None:
|
||||
folder = self.current_folder
|
||||
if folder is None:
|
||||
return {}
|
||||
if folder not in self.folder_configs:
|
||||
self.folder_configs[folder] = load_folder_config(folder)
|
||||
return self.folder_configs[folder]
|
||||
|
||||
def save_folder_config(self, folder: Path = None, config: dict = None):
|
||||
"""Save config for a folder"""
|
||||
if folder is None:
|
||||
folder = self.current_folder
|
||||
if folder is None:
|
||||
return
|
||||
if config is None:
|
||||
config = self.folder_configs.get(folder, {})
|
||||
self.folder_configs[folder] = config
|
||||
save_folder_config(folder, config)
|
||||
|
||||
def set_ignore_patterns(self, patterns: list[str], folder: Path = None):
|
||||
"""Set ignore patterns for a folder"""
|
||||
config = self.get_folder_config(folder)
|
||||
config["ignore_patterns"] = patterns
|
||||
self.save_folder_config(folder, config)
|
||||
|
||||
def get_ignore_patterns(self, folder: Path = None) -> list[str]:
|
||||
"""Get ignore patterns for a folder"""
|
||||
config = self.get_folder_config(folder)
|
||||
return config.get("ignore_patterns", [])
|
||||
|
||||
def assign_tag_to_file_objects(self, files_objs: list[File], tag):
|
||||
"""Přiřadí tag (Tag nebo 'category/name' string) ke každému souboru v seznamu."""
|
||||
for f in files_objs:
|
||||
@@ -460,9 +437,3 @@ class FileManager:
|
||||
if all(tag in file_tags for tag in target_full_paths):
|
||||
filtered.append(f)
|
||||
return filtered
|
||||
|
||||
# Legacy property for backwards compatibility
|
||||
@property
|
||||
def config(self):
|
||||
"""Legacy: returns global config"""
|
||||
return self.global_config
|
||||
|
||||
Reference in New Issue
Block a user