This commit is contained in:
nose
2025-12-12 21:55:38 -08:00
parent e2ffcab030
commit 85750247cc
78 changed files with 5726 additions and 6239 deletions

View File

@@ -943,6 +943,79 @@ class Folder(Store):
debug(f"delete_url failed for local file: {exc}")
return False
def get_note(self, file_identifier: str, **kwargs: Any) -> Dict[str, str]:
"""Get notes for a local file by hash."""
from API.folder import API_folder_store
try:
if not self._location:
return {}
file_hash = str(file_identifier or "").strip().lower()
if not _normalize_hash(file_hash):
return {}
with API_folder_store(Path(self._location)) as db:
getter = getattr(db, "get_notes", None)
if callable(getter):
notes = getter(file_hash)
return notes if isinstance(notes, dict) else {}
# Fallback: default-only
note = db.get_note(file_hash)
return {"default": str(note or "")} if note else {}
except Exception as exc:
debug(f"get_note failed for local file: {exc}")
return {}
def set_note(self, file_identifier: str, name: str, text: str, **kwargs: Any) -> bool:
"""Set a named note for a local file by hash."""
from API.folder import API_folder_store
try:
if not self._location:
return False
file_hash = str(file_identifier or "").strip().lower()
if not _normalize_hash(file_hash):
return False
file_path = self.get_file(file_hash, **kwargs)
if not file_path or not isinstance(file_path, Path) or not file_path.exists():
return False
with API_folder_store(Path(self._location)) as db:
setter = getattr(db, "set_note", None)
if callable(setter):
setter(file_path, str(name), str(text))
return True
db.save_note(file_path, str(text))
return True
except Exception as exc:
debug(f"set_note failed for local file: {exc}")
return False
def delete_note(self, file_identifier: str, name: str, **kwargs: Any) -> bool:
"""Delete a named note for a local file by hash."""
from API.folder import API_folder_store
try:
if not self._location:
return False
file_hash = str(file_identifier or "").strip().lower()
if not _normalize_hash(file_hash):
return False
with API_folder_store(Path(self._location)) as db:
deleter = getattr(db, "delete_note", None)
if callable(deleter):
deleter(file_hash, str(name))
return True
# Default-only fallback
if str(name).strip().lower() == "default":
deleter2 = getattr(db, "save_note", None)
if callable(deleter2):
file_path = self.get_file(file_hash, **kwargs)
if file_path and isinstance(file_path, Path) and file_path.exists():
deleter2(file_path, "")
return True
return False
except Exception as exc:
debug(f"delete_note failed for local file: {exc}")
return False
def delete_file(self, file_identifier: str, **kwargs: Any) -> bool:
"""Delete a file from the folder store.

View File

@@ -437,7 +437,10 @@ class HydrusNetwork(Store):
try:
from API import HydrusNetwork as hydrus_wrapper
file_hash = str(file_identifier)
file_hash = str(file_identifier or "").strip().lower()
if len(file_hash) != 64 or not all(ch in "0123456789abcdef" for ch in file_hash):
debug(f"get_tags: invalid file hash '{file_identifier}'")
return [], "unknown"
# Get Hydrus client and service info
client = self._client
@@ -483,12 +486,17 @@ class HydrusNetwork(Store):
if client is None:
debug("add_tag: Hydrus client unavailable")
return False
file_hash = str(file_identifier or "").strip().lower()
if len(file_hash) != 64 or not all(ch in "0123456789abcdef" for ch in file_hash):
debug(f"add_tag: invalid file hash '{file_identifier}'")
return False
service_name = kwargs.get("service_name") or "my tags"
# Ensure tags is a list
tag_list = list(tags) if isinstance(tags, (list, tuple)) else [str(tags)]
if not tag_list:
return False
client.add_tag(file_identifier, tag_list, service_name)
client.add_tag(file_hash, tag_list, service_name)
return True
except Exception as exc:
debug(f"Hydrus add_tag failed: {exc}")
@@ -502,11 +510,16 @@ class HydrusNetwork(Store):
if client is None:
debug("delete_tag: Hydrus client unavailable")
return False
file_hash = str(file_identifier or "").strip().lower()
if len(file_hash) != 64 or not all(ch in "0123456789abcdef" for ch in file_hash):
debug(f"delete_tag: invalid file hash '{file_identifier}'")
return False
service_name = kwargs.get("service_name") or "my tags"
tag_list = list(tags) if isinstance(tags, (list, tuple)) else [str(tags)]
if not tag_list:
return False
client.delete_tag(file_identifier, tag_list, service_name)
client.delete_tag(file_hash, tag_list, service_name)
return True
except Exception as exc:
debug(f"Hydrus delete_tag failed: {exc}")
@@ -520,7 +533,12 @@ class HydrusNetwork(Store):
if client is None:
debug("get_url: Hydrus client unavailable")
return []
payload = client.fetch_file_metadata(hashes=[str(file_identifier)], include_file_url=True)
file_hash = str(file_identifier or "").strip().lower()
if len(file_hash) != 64 or not all(ch in "0123456789abcdef" for ch in file_hash):
return []
payload = client.fetch_file_metadata(hashes=[file_hash], include_file_url=True)
items = payload.get("metadata") if isinstance(payload, dict) else None
if not isinstance(items, list) or not items:
return []
@@ -561,6 +579,80 @@ class HydrusNetwork(Store):
debug(f"Hydrus delete_url failed: {exc}")
return False
def get_note(self, file_identifier: str, **kwargs: Any) -> Dict[str, str]:
"""Get notes for a Hydrus file (default note service only)."""
try:
client = self._client
if client is None:
debug("get_note: Hydrus client unavailable")
return {}
file_hash = str(file_identifier or "").strip().lower()
if len(file_hash) != 64 or not all(ch in "0123456789abcdef" for ch in file_hash):
return {}
payload = client.fetch_file_metadata(hashes=[file_hash], include_notes=True)
items = payload.get("metadata") if isinstance(payload, dict) else None
if not isinstance(items, list) or not items:
return {}
meta = items[0] if isinstance(items[0], dict) else None
if not isinstance(meta, dict):
return {}
notes_payload = meta.get("notes")
if isinstance(notes_payload, dict):
return {str(k): str(v or "") for k, v in notes_payload.items() if str(k).strip()}
return {}
except Exception as exc:
debug(f"Hydrus get_note failed: {exc}")
return {}
def set_note(self, file_identifier: str, name: str, text: str, **kwargs: Any) -> bool:
"""Set a named note for a Hydrus file (default note service only)."""
try:
client = self._client
if client is None:
debug("set_note: Hydrus client unavailable")
return False
file_hash = str(file_identifier or "").strip().lower()
if len(file_hash) != 64 or not all(ch in "0123456789abcdef" for ch in file_hash):
return False
note_name = str(name or "").strip()
if not note_name:
return False
note_text = str(text or "")
client.set_notes(file_hash, {note_name: note_text})
return True
except Exception as exc:
debug(f"Hydrus set_note failed: {exc}")
return False
def delete_note(self, file_identifier: str, name: str, **kwargs: Any) -> bool:
"""Delete a named note for a Hydrus file (default note service only)."""
try:
client = self._client
if client is None:
debug("delete_note: Hydrus client unavailable")
return False
file_hash = str(file_identifier or "").strip().lower()
if len(file_hash) != 64 or not all(ch in "0123456789abcdef" for ch in file_hash):
return False
note_name = str(name or "").strip()
if not note_name:
return False
client.delete_notes(file_hash, [note_name])
return True
except Exception as exc:
debug(f"Hydrus delete_note failed: {exc}")
return False
@staticmethod
def _extract_tags_from_hydrus_meta(
meta: Dict[str, Any],

View File

@@ -53,3 +53,21 @@ class Store(ABC):
@abstractmethod
def delete_url(self, file_identifier: str, url: List[str], **kwargs: Any) -> bool:
raise NotImplementedError
@abstractmethod
def get_note(self, file_identifier: str, **kwargs: Any) -> Dict[str, str]:
"""Get notes for a file.
Returns a mapping of note name/key -> note text.
"""
raise NotImplementedError
@abstractmethod
def set_note(self, file_identifier: str, name: str, text: str, **kwargs: Any) -> bool:
"""Add or replace a named note for a file."""
raise NotImplementedError
@abstractmethod
def delete_note(self, file_identifier: str, name: str, **kwargs: Any) -> bool:
"""Delete a named note for a file."""
raise NotImplementedError