f
This commit is contained in:
@@ -263,7 +263,31 @@ class API_folder_store:
|
|||||||
try:
|
try:
|
||||||
# Ensure the library root exists; sqlite cannot create parent dirs.
|
# Ensure the library root exists; sqlite cannot create parent dirs.
|
||||||
try:
|
try:
|
||||||
|
# User safety: Folder store must be created in a blank folder/no files in it.
|
||||||
|
# If the DB already exists, we skip this check (it's an existing library).
|
||||||
|
should_check_empty = not self.db_path.exists()
|
||||||
|
|
||||||
self.library_root.mkdir(parents=True, exist_ok=True)
|
self.library_root.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
if should_check_empty:
|
||||||
|
# Check if there are any files or directories in the library root (excluding the DB itself if it was just created)
|
||||||
|
# We use a generator and next() for efficiency.
|
||||||
|
existing_items = [item for item in self.library_root.iterdir() if item.name != self.DB_NAME]
|
||||||
|
if existing_items:
|
||||||
|
# Log the items found for debugging
|
||||||
|
item_names = [i.name for i in existing_items[:5]]
|
||||||
|
if len(existing_items) > 5:
|
||||||
|
item_names.append("...")
|
||||||
|
raise RuntimeError(
|
||||||
|
f"Safety Check Failed: Local library root must be empty for new stores.\n"
|
||||||
|
f"Directory: {self.library_root}\n"
|
||||||
|
f"Found {len(existing_items)} items: {', '.join(item_names)}\n"
|
||||||
|
f"Please use a clean directory to prevent accidental hashing of existing files."
|
||||||
|
)
|
||||||
|
|
||||||
|
except RuntimeError:
|
||||||
|
# Re-raise our specific safety error
|
||||||
|
raise
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
f"Cannot create/open library root directory: {self.library_root}: {exc}"
|
f"Cannot create/open library root directory: {self.library_root}: {exc}"
|
||||||
|
|||||||
@@ -693,6 +693,58 @@ def clear_config_cache() -> None:
|
|||||||
_CONFIG_CACHE.clear()
|
_CONFIG_CACHE.clear()
|
||||||
|
|
||||||
|
|
||||||
|
def _validate_config_safety(config: Dict[str, Any]) -> None:
|
||||||
|
"""Check for dangerous configurations, like folder stores in non-empty dirs."""
|
||||||
|
store = config.get("store")
|
||||||
|
if not isinstance(store, dict):
|
||||||
|
return
|
||||||
|
|
||||||
|
folder_stores = store.get("folder")
|
||||||
|
if not isinstance(folder_stores, dict):
|
||||||
|
return
|
||||||
|
|
||||||
|
for name, cfg in folder_stores.items():
|
||||||
|
if not isinstance(cfg, dict):
|
||||||
|
continue
|
||||||
|
|
||||||
|
path_str = cfg.get("path") or cfg.get("PATH")
|
||||||
|
if not path_str:
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
p = expand_path(path_str).resolve()
|
||||||
|
# If the path doesn't exist yet, it's fine (will be created empty)
|
||||||
|
if not p.exists():
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not p.is_dir():
|
||||||
|
continue
|
||||||
|
|
||||||
|
# DB name from API/folder.py
|
||||||
|
db_file = p / "medios-macina.db"
|
||||||
|
if db_file.exists():
|
||||||
|
# Existing portable library, allowed to re-attach
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Check if directory has any files (other than the DB we just checked)
|
||||||
|
items = list(p.iterdir())
|
||||||
|
if items:
|
||||||
|
item_names = [i.name for i in items[:3]]
|
||||||
|
if len(items) > 3:
|
||||||
|
item_names.append("...")
|
||||||
|
raise RuntimeError(
|
||||||
|
f"Configuration Error: Local library '{name}' target directory is not empty.\n"
|
||||||
|
f"Path: {p}\n"
|
||||||
|
f"Found {len(items)} items: {', '.join(item_names)}\n"
|
||||||
|
f"To prevent accidental mass-hashing, new libraries must be set to unique, empty folders."
|
||||||
|
)
|
||||||
|
except RuntimeError:
|
||||||
|
raise
|
||||||
|
except Exception:
|
||||||
|
# We don't want to crash on invalid paths during validation if they aren't 'unsafe'
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def save_config(
|
def save_config(
|
||||||
config: Dict[str, Any],
|
config: Dict[str, Any],
|
||||||
config_dir: Optional[Path] = None,
|
config_dir: Optional[Path] = None,
|
||||||
@@ -706,6 +758,9 @@ def save_config(
|
|||||||
f"Unsupported config format: {config_path.name} (only .conf is supported)"
|
f"Unsupported config format: {config_path.name} (only .conf is supported)"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Safety Check: Validate folder stores are in empty dirs or existing libraries
|
||||||
|
_validate_config_safety(config)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
config_path.write_text(_serialize_conf(config), encoding="utf-8")
|
config_path.write_text(_serialize_conf(config), encoding="utf-8")
|
||||||
except OSError as exc:
|
except OSError as exc:
|
||||||
|
|||||||
@@ -450,12 +450,15 @@ class ConfigModal(ModalScreen):
|
|||||||
elif bid == "save-btn":
|
elif bid == "save-btn":
|
||||||
if not self.validate_current_editor():
|
if not self.validate_current_editor():
|
||||||
return
|
return
|
||||||
self.save_all()
|
try:
|
||||||
self.notify("Configuration saved!")
|
self.save_all()
|
||||||
# Return to the main list view within the current category
|
self.notify("Configuration saved!")
|
||||||
self.editing_item_name = None
|
# Return to the main list view within the current category
|
||||||
self.editing_item_type = None
|
self.editing_item_name = None
|
||||||
self.refresh_view()
|
self.editing_item_type = None
|
||||||
|
self.refresh_view()
|
||||||
|
except Exception as exc:
|
||||||
|
self.notify(f"Save failed: {exc}", severity="error", timeout=10)
|
||||||
elif bid in self._button_id_map:
|
elif bid in self._button_id_map:
|
||||||
action, itype, name = self._button_id_map[bid]
|
action, itype, name = self._button_id_map[bid]
|
||||||
if action == "edit":
|
if action == "edit":
|
||||||
|
|||||||
Reference in New Issue
Block a user