"""Delete file relationships.""" from __future__ import annotations from typing import Any, Dict, Optional, Sequence import json from pathlib import Path import sys from helper.logger import log import pipeline as ctx from ._shared import Cmdlet, CmdletArg, parse_cmdlet_args, normalize_result_input from helper.local_library import LocalLibrarySearchOptimizer from config import get_local_storage_path def _refresh_relationship_view_if_current(target_hash: Optional[str], target_path: Optional[str], other: Optional[str], config: Dict[str, Any]) -> None: """If the current subject matches the target, refresh relationships via get-relationship.""" try: from cmdlets import get_relationship as get_rel_cmd # type: ignore except Exception: return try: subject = ctx.get_last_result_subject() if subject is None: return def norm(val: Any) -> str: return str(val).lower() target_hashes = [norm(v) for v in [target_hash, other] if v] target_paths = [norm(v) for v in [target_path, other] if v] subj_hashes: list[str] = [] subj_paths: list[str] = [] if isinstance(subject, dict): subj_hashes = [norm(v) for v in [subject.get("hydrus_hash"), subject.get("hash"), subject.get("hash_hex"), subject.get("file_hash")] if v] subj_paths = [norm(v) for v in [subject.get("file_path"), subject.get("path"), subject.get("target")] if v] else: subj_hashes = [norm(getattr(subject, f, None)) for f in ("hydrus_hash", "hash", "hash_hex", "file_hash") if getattr(subject, f, None)] subj_paths = [norm(getattr(subject, f, None)) for f in ("file_path", "path", "target") if getattr(subject, f, None)] is_match = False if target_hashes and any(h in subj_hashes for h in target_hashes): is_match = True if target_paths and any(p in subj_paths for p in target_paths): is_match = True if not is_match: return refresh_args: list[str] = [] if target_hash: refresh_args.extend(["-hash", target_hash]) get_rel_cmd._run(subject, refresh_args, config) except Exception: pass def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int: """Delete relationships from files. Args: result: Input result(s) from previous cmdlet args: Command arguments config: CLI configuration Returns: Exit code (0 = success) """ try: # Parse arguments parsed_args = parse_cmdlet_args(args, CMDLET) delete_all_flag = parsed_args.get("all", False) rel_type_filter = parsed_args.get("type") # Get storage path local_storage_path = get_local_storage_path(config) if not local_storage_path: log("Local storage path not configured", file=sys.stderr) return 1 # Normalize input results = normalize_result_input(result) if not results: log("No results to process", file=sys.stderr) return 1 deleted_count = 0 for single_result in results: try: # Get file path from result file_path_from_result = None if isinstance(single_result, dict): file_path_from_result = ( single_result.get("file_path") or single_result.get("path") or single_result.get("target") ) else: file_path_from_result = ( getattr(single_result, "file_path", None) or getattr(single_result, "path", None) or getattr(single_result, "target", None) or str(single_result) ) if not file_path_from_result: log("Could not extract file path from result", file=sys.stderr) return 1 file_path_obj = Path(str(file_path_from_result)) if not file_path_obj.exists(): log(f"File not found: {file_path_obj}", file=sys.stderr) return 1 with LocalLibrarySearchOptimizer(local_storage_path) as db: file_id = db.db.get_file_id(file_path_obj) if not file_id: log(f"File not in database: {file_path_obj.name}", file=sys.stderr) continue # Get current relationships cursor = db.db.connection.cursor() cursor.execute(""" SELECT relationships FROM metadata WHERE file_id = ? """, (file_id,)) row = cursor.fetchone() if not row: log(f"No relationships found for: {file_path_obj.name}", file=sys.stderr) continue relationships_str = row[0] if not relationships_str: log(f"No relationships found for: {file_path_obj.name}", file=sys.stderr) continue try: relationships = json.loads(relationships_str) except json.JSONDecodeError: log(f"Invalid relationship data for: {file_path_obj.name}", file=sys.stderr) continue if not isinstance(relationships, dict): relationships = {} # Determine what to delete if delete_all_flag: # Delete all relationships deleted_types = list(relationships.keys()) relationships = {} log(f"Deleted all relationships ({len(deleted_types)} types) from: {file_path_obj.name}", file=sys.stderr) elif rel_type_filter: # Delete specific type if rel_type_filter in relationships: deleted_count_for_type = len(relationships[rel_type_filter]) del relationships[rel_type_filter] log(f"Deleted {deleted_count_for_type} {rel_type_filter} relationship(s) from: {file_path_obj.name}", file=sys.stderr) else: log(f"No {rel_type_filter} relationships found for: {file_path_obj.name}", file=sys.stderr) continue else: log("Specify --all to delete all relationships or -type to delete specific type", file=sys.stderr) return 1 # Save updated relationships cursor.execute(""" INSERT INTO metadata (file_id, relationships) VALUES (?, ?) ON CONFLICT(file_id) DO UPDATE SET relationships = excluded.relationships, time_modified = CURRENT_TIMESTAMP """, (file_id, json.dumps(relationships) if relationships else None)) db.db.connection.commit() _refresh_relationship_view_if_current(None, str(file_path_obj), None, config) deleted_count += 1 except Exception as exc: log(f"Error deleting relationship: {exc}", file=sys.stderr) return 1 log(f"Successfully deleted relationships from {deleted_count} file(s)", file=sys.stderr) return 0 except Exception as exc: log(f"Error in delete-relationship: {exc}", file=sys.stderr) return 1 CMDLET = Cmdlet( name="delete-relationship", summary="Remove relationships from files.", usage="@1 | delete-relationship --all OR delete-relationship -path --all OR @1-3 | delete-relationship -type alt", args=[ CmdletArg("path", type="string", description="Specify the local file path (if not piping a result)."), CmdletArg("all", type="flag", description="Delete all relationships for the file(s)."), CmdletArg("type", type="string", description="Delete specific relationship type ('alt', 'king', 'related'). Default: delete all types."), ], details=[ "- Delete all relationships: pipe files | delete-relationship --all", "- Delete specific type: pipe files | delete-relationship -type alt", "- Delete all from file: delete-relationship -path --all", ], )