from __future__ import annotations from pathlib import Path from typing import Any, Dict, Optional, Sequence import sys from SYS.logger import log import pipeline as ctx from ._shared import ( Cmdlet, CmdletArg, SharedArgs, normalize_hash, parse_cmdlet_args, normalize_result_input, should_show_help, ) from Store import Store from SYS.utils import sha256_file class Add_Note(Cmdlet): def __init__(self) -> None: super().__init__( name="add-note", summary="Add or set a named note on a file in a store.", usage="add-note -store [-hash ] ", alias=["set-note", "add_note"], arg=[ SharedArgs.STORE, SharedArgs.HASH, CmdletArg("name", type="string", required=True, description="The note name/key to set (e.g. 'comment', 'lyric')."), CmdletArg("text", type="string", required=True, description="Note text/content to store.", variadic=True), ], detail=[ "- Notes are stored via the selected store backend.", "- For lyrics: store LRC text in a note named 'lyric'.", ], exec=self.run, ) # Populate dynamic store choices for autocomplete try: SharedArgs.STORE.choices = SharedArgs.get_store_choices(None) except Exception: pass self.register() def _resolve_hash(self, raw_hash: Optional[str], raw_path: Optional[str], override_hash: Optional[str]) -> Optional[str]: resolved = normalize_hash(override_hash) if override_hash else normalize_hash(raw_hash) if resolved: return resolved if raw_path: try: p = Path(str(raw_path)) stem = p.stem if len(stem) == 64 and all(c in "0123456789abcdef" for c in stem.lower()): return stem.lower() if p.exists() and p.is_file(): return sha256_file(p) except Exception: return None return None def run(self, result: Any, args: Sequence[str], config: Dict[str, Any]) -> int: if should_show_help(args): log(f"Cmdlet: {self.name}\nSummary: {self.summary}\nUsage: {self.usage}") return 0 parsed = parse_cmdlet_args(args, self) store_override = parsed.get("store") hash_override = parsed.get("hash") note_name = str(parsed.get("name") or "").strip() text_parts = parsed.get("text") if not note_name: log("[add_note] Error: Requires ", file=sys.stderr) return 1 if isinstance(text_parts, list): note_text = " ".join([str(p) for p in text_parts]).strip() else: note_text = str(text_parts or "").strip() if not note_text: log("[add_note] Error: Empty note text", file=sys.stderr) return 1 results = normalize_result_input(result) if not results: if store_override and normalize_hash(hash_override): results = [{"store": str(store_override), "hash": normalize_hash(hash_override)}] else: log("[add_note] Error: Requires piped item(s) or -store and -hash", file=sys.stderr) return 1 store_registry = Store(config) updated = 0 for res in results: if not isinstance(res, dict): ctx.emit(res) continue store_name = str(store_override or res.get("store") or "").strip() raw_hash = res.get("hash") raw_path = res.get("path") if not store_name: log("[add_note] Error: Missing -store and item has no store field", file=sys.stderr) return 1 resolved_hash = self._resolve_hash( raw_hash=str(raw_hash) if raw_hash else None, raw_path=str(raw_path) if raw_path else None, override_hash=str(hash_override) if hash_override else None, ) if not resolved_hash: log("[add_note] Warning: Item missing usable hash; skipping", file=sys.stderr) ctx.emit(res) continue try: backend = store_registry[store_name] except Exception as exc: log(f"[add_note] Error: Unknown store '{store_name}': {exc}", file=sys.stderr) return 1 ok = False try: ok = bool(backend.set_note(resolved_hash, note_name, note_text, config=config)) except Exception as exc: log(f"[add_note] Error: Failed to set note: {exc}", file=sys.stderr) ok = False if ok: updated += 1 ctx.emit(res) log(f"[add_note] Updated {updated} item(s)", file=sys.stderr) return 0 if updated > 0 else 1 CMDLET = Add_Note()