f
This commit is contained in:
@@ -907,10 +907,13 @@ class HydrusNetwork:
|
|||||||
file_ids: Sequence[int] | None = None,
|
file_ids: Sequence[int] | None = None,
|
||||||
hashes: Sequence[str] | None = None,
|
hashes: Sequence[str] | None = None,
|
||||||
include_service_keys_to_tags: bool = True,
|
include_service_keys_to_tags: bool = True,
|
||||||
|
include_tag_services: bool = False,
|
||||||
|
include_file_services: bool = False,
|
||||||
include_file_url: bool = False,
|
include_file_url: bool = False,
|
||||||
include_duration: bool = True,
|
include_duration: bool = True,
|
||||||
include_size: bool = True,
|
include_size: bool = True,
|
||||||
include_mime: bool = False,
|
include_mime: bool = False,
|
||||||
|
include_is_trashed: bool = False,
|
||||||
include_notes: bool = False,
|
include_notes: bool = False,
|
||||||
) -> dict[str,
|
) -> dict[str,
|
||||||
Any]:
|
Any]:
|
||||||
@@ -929,6 +932,16 @@ class HydrusNetwork:
|
|||||||
include_service_keys_to_tags,
|
include_service_keys_to_tags,
|
||||||
lambda v: "true" if v else None,
|
lambda v: "true" if v else None,
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
"include_tag_services",
|
||||||
|
include_tag_services,
|
||||||
|
lambda v: "true" if v else None,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"include_file_services",
|
||||||
|
include_file_services,
|
||||||
|
lambda v: "true" if v else None,
|
||||||
|
),
|
||||||
("include_file_url",
|
("include_file_url",
|
||||||
include_file_url, lambda v: "true" if v else None),
|
include_file_url, lambda v: "true" if v else None),
|
||||||
("include_duration",
|
("include_duration",
|
||||||
@@ -937,6 +950,11 @@ class HydrusNetwork:
|
|||||||
include_size, lambda v: "true" if v else None),
|
include_size, lambda v: "true" if v else None),
|
||||||
("include_mime",
|
("include_mime",
|
||||||
include_mime, lambda v: "true" if v else None),
|
include_mime, lambda v: "true" if v else None),
|
||||||
|
(
|
||||||
|
"include_is_trashed",
|
||||||
|
include_is_trashed,
|
||||||
|
lambda v: "true" if v else None,
|
||||||
|
),
|
||||||
("include_notes",
|
("include_notes",
|
||||||
include_notes, lambda v: "true" if v else None),
|
include_notes, lambda v: "true" if v else None),
|
||||||
]
|
]
|
||||||
@@ -1831,6 +1849,48 @@ def get_tag_service_key(client: HydrusNetwork,
|
|||||||
if not isinstance(services, dict):
|
if not isinstance(services, dict):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def _normalize_name(value: Any) -> str:
|
||||||
|
if isinstance(value, bytes):
|
||||||
|
try:
|
||||||
|
return value.decode("utf-8", errors="ignore").strip().lower()
|
||||||
|
except Exception:
|
||||||
|
return ""
|
||||||
|
try:
|
||||||
|
return str(value or "").strip().lower()
|
||||||
|
except Exception:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def _normalize_service_key(value: Any) -> Optional[str]:
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
if isinstance(value, bytes):
|
||||||
|
# Hydrus service keys are raw bytes; API expects hex.
|
||||||
|
try:
|
||||||
|
hex_text = value.hex()
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
return hex_text if (len(hex_text) == 64 and all(ch in "0123456789abcdef" for ch in hex_text)) else None
|
||||||
|
if isinstance(value, str):
|
||||||
|
text = value.strip().lower()
|
||||||
|
if text.startswith("0x"):
|
||||||
|
text = text[2:]
|
||||||
|
if len(text) == 64 and all(ch in "0123456789abcdef" for ch in text):
|
||||||
|
return text
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
text = str(value).strip().lower()
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
if text.startswith("0x"):
|
||||||
|
text = text[2:]
|
||||||
|
if len(text) == 64 and all(ch in "0123456789abcdef" for ch in text):
|
||||||
|
return text
|
||||||
|
return None
|
||||||
|
|
||||||
|
target_name = _normalize_name(fallback_name)
|
||||||
|
if not target_name:
|
||||||
|
target_name = "my tags"
|
||||||
|
|
||||||
# Hydrus returns services grouped by type; walk all lists and match on name
|
# Hydrus returns services grouped by type; walk all lists and match on name
|
||||||
for group in services.values():
|
for group in services.values():
|
||||||
if not isinstance(group, list):
|
if not isinstance(group, list):
|
||||||
@@ -1838,10 +1898,13 @@ def get_tag_service_key(client: HydrusNetwork,
|
|||||||
for item in group:
|
for item in group:
|
||||||
if not isinstance(item, dict):
|
if not isinstance(item, dict):
|
||||||
continue
|
continue
|
||||||
name = str(item.get("name") or "").strip().lower()
|
name = _normalize_name(item.get("name"))
|
||||||
key = item.get("service_key") or item.get("key")
|
if name != target_name:
|
||||||
if name == fallback_name.lower() and key:
|
continue
|
||||||
return str(key)
|
key_raw = item.get("service_key") or item.get("key")
|
||||||
|
normalized_key = _normalize_service_key(key_raw)
|
||||||
|
if normalized_key:
|
||||||
|
return normalized_key
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|||||||
@@ -2234,6 +2234,25 @@ class PipelineExecutor:
|
|||||||
|
|
||||||
# After inserting/appending an auto-stage, continue processing so later
|
# After inserting/appending an auto-stage, continue processing so later
|
||||||
# selection-expansion logic can still run (e.g., for example selectors).
|
# selection-expansion logic can still run (e.g., for example selectors).
|
||||||
|
if (not stages) and selection_indices and len(selection_indices) == 1:
|
||||||
|
# Selection-only invocation (e.g. user types @1 with no pipe).
|
||||||
|
# Show the item details panel so selection feels actionable.
|
||||||
|
try:
|
||||||
|
selected_item = filtered[0] if filtered else None
|
||||||
|
if selected_item is not None and not isinstance(selected_item, dict):
|
||||||
|
to_dict = getattr(selected_item, "to_dict", None)
|
||||||
|
if callable(to_dict):
|
||||||
|
selected_item = to_dict()
|
||||||
|
if isinstance(selected_item, dict):
|
||||||
|
from SYS.rich_display import render_item_details_panel
|
||||||
|
|
||||||
|
render_item_details_panel(selected_item)
|
||||||
|
try:
|
||||||
|
ctx.set_last_result_items_only([selected_item])
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
except Exception:
|
||||||
|
logger.exception("Failed to render selection-only item details")
|
||||||
return True, piped_result
|
return True, piped_result
|
||||||
else:
|
else:
|
||||||
debug(f"@N: No items to select from (items_list empty)")
|
debug(f"@N: No items to select from (items_list empty)")
|
||||||
|
|||||||
@@ -364,6 +364,18 @@ class HydrusNetwork(Store):
|
|||||||
except Exception:
|
except Exception:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _has_current_file_service(meta: Dict[str, Any]) -> bool:
|
||||||
|
services = meta.get("file_services")
|
||||||
|
if not isinstance(services, dict):
|
||||||
|
return False
|
||||||
|
current = services.get("current")
|
||||||
|
if isinstance(current, dict):
|
||||||
|
return any(bool(v) for v in current.values())
|
||||||
|
if isinstance(current, list):
|
||||||
|
return len(current) > 0
|
||||||
|
return False
|
||||||
|
|
||||||
def add_file(self, file_path: Path, **kwargs: Any) -> str:
|
def add_file(self, file_path: Path, **kwargs: Any) -> str:
|
||||||
"""Upload file to Hydrus with full metadata support.
|
"""Upload file to Hydrus with full metadata support.
|
||||||
|
|
||||||
@@ -411,25 +423,50 @@ class HydrusNetwork(Store):
|
|||||||
if client is None:
|
if client is None:
|
||||||
raise Exception("Hydrus client unavailable")
|
raise Exception("Hydrus client unavailable")
|
||||||
|
|
||||||
# Check if file already exists in Hydrus
|
# Check if file already exists in Hydrus.
|
||||||
|
# IMPORTANT: some Hydrus deployments can return a metadata record (file_id)
|
||||||
|
# even when the file is not in any current file service (e.g. trashed/missing).
|
||||||
|
# Only treat as a real duplicate if it is in a current file service.
|
||||||
file_exists = False
|
file_exists = False
|
||||||
try:
|
try:
|
||||||
metadata = client.fetch_file_metadata(
|
metadata = client.fetch_file_metadata(
|
||||||
hashes=[file_hash],
|
hashes=[file_hash],
|
||||||
include_service_keys_to_tags=False,
|
include_service_keys_to_tags=False,
|
||||||
|
include_file_services=True,
|
||||||
|
include_is_trashed=True,
|
||||||
include_file_url=True,
|
include_file_url=True,
|
||||||
include_duration=False,
|
include_duration=False,
|
||||||
include_size=False,
|
include_size=True,
|
||||||
include_mime=False,
|
include_mime=True,
|
||||||
)
|
)
|
||||||
if metadata and isinstance(metadata, dict):
|
if metadata and isinstance(metadata, dict):
|
||||||
metas = metadata.get("metadata", [])
|
metas = metadata.get("metadata", [])
|
||||||
if isinstance(metas, list) and metas:
|
if isinstance(metas, list) and metas:
|
||||||
# Hydrus returns placeholder rows for unknown hashes.
|
# Hydrus returns placeholder rows for unknown hashes.
|
||||||
# Only treat as a real duplicate if it has a concrete file_id.
|
# Only treat as a real duplicate if it has a concrete file_id AND
|
||||||
|
# appears in a current file service.
|
||||||
for meta in metas:
|
for meta in metas:
|
||||||
if isinstance(meta,
|
if not isinstance(meta, dict):
|
||||||
dict) and meta.get("file_id") is not None:
|
continue
|
||||||
|
if meta.get("file_id") is None:
|
||||||
|
continue
|
||||||
|
# Preferred: use file_services.current.
|
||||||
|
if isinstance(meta.get("file_services"), dict):
|
||||||
|
if self._has_current_file_service(meta):
|
||||||
|
file_exists = True
|
||||||
|
break
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Fallback: if Hydrus doesn't return file_services, only treat as
|
||||||
|
# existing when the metadata looks like a real file (non-zero size).
|
||||||
|
size_val = meta.get("size")
|
||||||
|
if size_val is None:
|
||||||
|
size_val = meta.get("size_bytes")
|
||||||
|
try:
|
||||||
|
size_int = int(size_val) if size_val is not None else 0
|
||||||
|
except Exception:
|
||||||
|
size_int = 0
|
||||||
|
if size_int > 0:
|
||||||
file_exists = True
|
file_exists = True
|
||||||
break
|
break
|
||||||
if file_exists:
|
if file_exists:
|
||||||
@@ -440,13 +477,54 @@ class HydrusNetwork(Store):
|
|||||||
debug(f"{self._log_prefix()} metadata fetch failed: {exc}")
|
debug(f"{self._log_prefix()} metadata fetch failed: {exc}")
|
||||||
|
|
||||||
# If Hydrus reports an existing file, it may be in trash. Best-effort restore it to 'my files'.
|
# If Hydrus reports an existing file, it may be in trash. Best-effort restore it to 'my files'.
|
||||||
# This keeps behavior aligned with user expectation: "use API only" and ensure it lands in my files.
|
# Then re-check that it is actually in a current file service; if not, we'll proceed to upload.
|
||||||
if file_exists:
|
if file_exists:
|
||||||
try:
|
try:
|
||||||
client.undelete_files([file_hash])
|
client.undelete_files([file_hash])
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
metadata2 = client.fetch_file_metadata(
|
||||||
|
hashes=[file_hash],
|
||||||
|
include_service_keys_to_tags=False,
|
||||||
|
include_file_services=True,
|
||||||
|
include_is_trashed=True,
|
||||||
|
include_file_url=False,
|
||||||
|
include_duration=False,
|
||||||
|
include_size=False,
|
||||||
|
include_mime=False,
|
||||||
|
)
|
||||||
|
metas2 = metadata2.get("metadata", []) if isinstance(metadata2, dict) else []
|
||||||
|
if isinstance(metas2, list) and metas2:
|
||||||
|
still_current = False
|
||||||
|
for meta in metas2:
|
||||||
|
if not isinstance(meta, dict):
|
||||||
|
continue
|
||||||
|
if meta.get("file_id") is None:
|
||||||
|
continue
|
||||||
|
if isinstance(meta.get("file_services"), dict):
|
||||||
|
if self._has_current_file_service(meta):
|
||||||
|
still_current = True
|
||||||
|
break
|
||||||
|
continue
|
||||||
|
|
||||||
|
size_val = meta.get("size")
|
||||||
|
if size_val is None:
|
||||||
|
size_val = meta.get("size_bytes")
|
||||||
|
try:
|
||||||
|
size_int = int(size_val) if size_val is not None else 0
|
||||||
|
except Exception:
|
||||||
|
size_int = 0
|
||||||
|
if size_int > 0:
|
||||||
|
still_current = True
|
||||||
|
break
|
||||||
|
if not still_current:
|
||||||
|
file_exists = False
|
||||||
|
except Exception:
|
||||||
|
# If re-check fails, keep prior behavior (avoid forcing uploads in unknown states)
|
||||||
|
pass
|
||||||
|
|
||||||
# Upload file if not already present
|
# Upload file if not already present
|
||||||
if not file_exists:
|
if not file_exists:
|
||||||
debug(
|
debug(
|
||||||
@@ -1199,48 +1277,15 @@ class HydrusNetwork(Store):
|
|||||||
|
|
||||||
file_id = meta.get("file_id")
|
file_id = meta.get("file_id")
|
||||||
hash_hex = meta.get("hash")
|
hash_hex = meta.get("hash")
|
||||||
size = meta.get("size", 0)
|
size_val = meta.get("size")
|
||||||
|
if size_val is None:
|
||||||
|
size_val = meta.get("size_bytes")
|
||||||
|
try:
|
||||||
|
size = int(size_val) if size_val is not None else 0
|
||||||
|
except Exception:
|
||||||
|
size = 0
|
||||||
|
|
||||||
tags_set = meta.get("tags",
|
title, all_tags = self._extract_title_and_tags(meta, file_id)
|
||||||
{})
|
|
||||||
all_tags: list[str] = []
|
|
||||||
title = f"Hydrus File {file_id}"
|
|
||||||
if isinstance(tags_set, dict):
|
|
||||||
|
|
||||||
def _collect(tag_list: Any) -> None:
|
|
||||||
nonlocal title
|
|
||||||
if not isinstance(tag_list, list):
|
|
||||||
return
|
|
||||||
for tag in tag_list:
|
|
||||||
tag_text = str(tag) if tag else ""
|
|
||||||
if not tag_text:
|
|
||||||
continue
|
|
||||||
tag_l = tag_text.strip().lower()
|
|
||||||
if not tag_l:
|
|
||||||
continue
|
|
||||||
all_tags.append(tag_l)
|
|
||||||
if (tag_l.startswith("title:") and title
|
|
||||||
== f"Hydrus File {file_id}"):
|
|
||||||
title = tag_l.split(":", 1)[1].strip()
|
|
||||||
|
|
||||||
for _service_name, service_tags in tags_set.items():
|
|
||||||
if not isinstance(service_tags, dict):
|
|
||||||
continue
|
|
||||||
storage_tags = service_tags.get(
|
|
||||||
"storage_tags",
|
|
||||||
{}
|
|
||||||
)
|
|
||||||
if isinstance(storage_tags, dict):
|
|
||||||
for tag_list in storage_tags.values():
|
|
||||||
_collect(tag_list)
|
|
||||||
display_tags = service_tags.get(
|
|
||||||
"display_tags",
|
|
||||||
[]
|
|
||||||
)
|
|
||||||
_collect(display_tags)
|
|
||||||
|
|
||||||
# Unique tags
|
|
||||||
all_tags = sorted(list(set(all_tags)))
|
|
||||||
|
|
||||||
# Use known URLs (source URLs) from Hydrus if available (matches get-url cmdlet)
|
# Use known URLs (source URLs) from Hydrus if available (matches get-url cmdlet)
|
||||||
item_url = meta.get("known_urls") or meta.get("urls") or meta.get("url") or []
|
item_url = meta.get("known_urls") or meta.get("urls") or meta.get("url") or []
|
||||||
@@ -1340,55 +1385,15 @@ class HydrusNetwork(Store):
|
|||||||
|
|
||||||
file_id = meta.get("file_id")
|
file_id = meta.get("file_id")
|
||||||
hash_hex = meta.get("hash")
|
hash_hex = meta.get("hash")
|
||||||
size = meta.get("size", 0)
|
size_val = meta.get("size")
|
||||||
|
if size_val is None:
|
||||||
|
size_val = meta.get("size_bytes")
|
||||||
|
try:
|
||||||
|
size = int(size_val) if size_val is not None else 0
|
||||||
|
except Exception:
|
||||||
|
size = 0
|
||||||
|
|
||||||
# Get tags for this file and extract title
|
title, all_tags = self._extract_title_and_tags(meta, file_id)
|
||||||
tags_set = meta.get("tags",
|
|
||||||
{})
|
|
||||||
all_tags = []
|
|
||||||
title = f"Hydrus File {file_id}" # Default fallback
|
|
||||||
all_tags_str = "" # For substring matching
|
|
||||||
|
|
||||||
# debug(f"[HydrusBackend.search] Processing file_id={file_id}, tags type={type(tags_set)}")
|
|
||||||
|
|
||||||
if isinstance(tags_set, dict):
|
|
||||||
# Collect both storage_tags and display_tags to capture siblings/parents and ensure title: is seen
|
|
||||||
def _collect(tag_list: Any) -> None:
|
|
||||||
nonlocal title, all_tags_str
|
|
||||||
if not isinstance(tag_list, list):
|
|
||||||
return
|
|
||||||
for tag in tag_list:
|
|
||||||
tag_text = str(tag) if tag else ""
|
|
||||||
if not tag_text:
|
|
||||||
continue
|
|
||||||
tag_l = tag_text.strip().lower()
|
|
||||||
if not tag_l:
|
|
||||||
continue
|
|
||||||
all_tags.append(tag_l)
|
|
||||||
all_tags_str += " " + tag_l
|
|
||||||
if tag_l.startswith("title:"
|
|
||||||
) and title == f"Hydrus File {file_id}":
|
|
||||||
title = tag_l.split(":", 1)[1].strip()
|
|
||||||
|
|
||||||
for _service_name, service_tags in tags_set.items():
|
|
||||||
if not isinstance(service_tags, dict):
|
|
||||||
continue
|
|
||||||
|
|
||||||
storage_tags = service_tags.get("storage_tags",
|
|
||||||
{})
|
|
||||||
if isinstance(storage_tags, dict):
|
|
||||||
for tag_list in storage_tags.values():
|
|
||||||
_collect(tag_list)
|
|
||||||
|
|
||||||
display_tags = service_tags.get("display_tags", [])
|
|
||||||
_collect(display_tags)
|
|
||||||
|
|
||||||
# Also consider top-level flattened tags payload if provided (Hydrus API sometimes includes it)
|
|
||||||
top_level_tags = meta.get("tags_flat", []) or meta.get("tags", [])
|
|
||||||
_collect(top_level_tags)
|
|
||||||
|
|
||||||
# Unique tags
|
|
||||||
all_tags = sorted(list(set(all_tags)))
|
|
||||||
|
|
||||||
# Prefer Hydrus-provided extension (e.g. ".webm"); fall back to MIME map.
|
# Prefer Hydrus-provided extension (e.g. ".webm"); fall back to MIME map.
|
||||||
mime_type = meta.get("mime")
|
mime_type = meta.get("mime")
|
||||||
@@ -1694,21 +1699,19 @@ class HydrusNetwork(Store):
|
|||||||
|
|
||||||
# Extract title from tags
|
# Extract title from tags
|
||||||
title = f"Hydrus_{file_hash[:12]}"
|
title = f"Hydrus_{file_hash[:12]}"
|
||||||
tags_payload = meta.get("tags",
|
extracted_tags = self._extract_tags_from_hydrus_meta(
|
||||||
{})
|
meta,
|
||||||
if isinstance(tags_payload, dict):
|
service_key=None,
|
||||||
for service_data in tags_payload.values():
|
service_name="my tags",
|
||||||
if isinstance(service_data, dict):
|
)
|
||||||
display_tags = service_data.get("display_tags",
|
for raw_tag in extracted_tags:
|
||||||
{})
|
tag_text = str(raw_tag or "").strip()
|
||||||
if isinstance(display_tags, dict):
|
if not tag_text:
|
||||||
current_tags = display_tags.get("0", [])
|
continue
|
||||||
if isinstance(current_tags, list):
|
if tag_text.lower().startswith("title:"):
|
||||||
for tag in current_tags:
|
value = tag_text.split(":", 1)[1].strip()
|
||||||
if str(tag).lower().startswith("title:"):
|
if value:
|
||||||
title = tag.split(":", 1)[1].strip()
|
title = value
|
||||||
break
|
|
||||||
if title != f"Hydrus_{file_hash[:12]}":
|
|
||||||
break
|
break
|
||||||
|
|
||||||
# Hydrus may return mime as an int enum, or sometimes a human label.
|
# Hydrus may return mime as an int enum, or sometimes a human label.
|
||||||
@@ -1777,9 +1780,9 @@ class HydrusNetwork(Store):
|
|||||||
if size_val is None:
|
if size_val is None:
|
||||||
size_val = meta.get("size_bytes")
|
size_val = meta.get("size_bytes")
|
||||||
try:
|
try:
|
||||||
size_int: int | None = int(size_val) if size_val is not None else None
|
size_int: int | None = int(size_val) if size_val is not None else 0
|
||||||
except Exception:
|
except Exception:
|
||||||
size_int = None
|
size_int = 0
|
||||||
|
|
||||||
dur_val = meta.get("duration")
|
dur_val = meta.get("duration")
|
||||||
if dur_val is None:
|
if dur_val is None:
|
||||||
@@ -2157,7 +2160,7 @@ class HydrusNetwork(Store):
|
|||||||
try:
|
try:
|
||||||
if service_key:
|
if service_key:
|
||||||
# Mutate tags for many hashes in a single request
|
# Mutate tags for many hashes in a single request
|
||||||
client.mutate_tags_by_key(hashes=hashes, service_key=service_key, add_tags=list(tag_tuple))
|
client.mutate_tags_by_key(hash=hashes, service_key=service_key, add_tags=list(tag_tuple))
|
||||||
any_success = True
|
any_success = True
|
||||||
continue
|
continue
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
@@ -2295,28 +2298,128 @@ class HydrusNetwork(Store):
|
|||||||
if not isinstance(tags_payload, dict):
|
if not isinstance(tags_payload, dict):
|
||||||
return []
|
return []
|
||||||
|
|
||||||
svc_data = None
|
desired_service_name = str(service_name or "").strip().lower()
|
||||||
if service_key:
|
desired_service_key = str(service_key).strip() if service_key is not None else ""
|
||||||
svc_data = tags_payload.get(service_key)
|
|
||||||
if not isinstance(svc_data, dict):
|
|
||||||
return []
|
|
||||||
|
|
||||||
# Prefer display_tags (Hydrus computes siblings/parents)
|
def _append_tag(out: List[str], value: Any) -> None:
|
||||||
display = svc_data.get("display_tags")
|
text = ""
|
||||||
if isinstance(display, list) and display:
|
if isinstance(value, bytes):
|
||||||
return [
|
try:
|
||||||
str(t) for t in display
|
text = value.decode("utf-8", errors="ignore")
|
||||||
if isinstance(t, (str, bytes)) and str(t).strip()
|
except Exception:
|
||||||
]
|
text = str(value)
|
||||||
|
elif isinstance(value, str):
|
||||||
|
text = value
|
||||||
|
if not text:
|
||||||
|
return
|
||||||
|
cleaned = text.strip()
|
||||||
|
if cleaned:
|
||||||
|
out.append(cleaned)
|
||||||
|
|
||||||
# Fallback to storage_tags status '0' (current)
|
def _collect_current(container: Any, out: List[str]) -> None:
|
||||||
storage = svc_data.get("storage_tags")
|
if isinstance(container, list):
|
||||||
if isinstance(storage, dict):
|
for tag in container:
|
||||||
current_list = storage.get("0") or storage.get(0)
|
_append_tag(out, tag)
|
||||||
if isinstance(current_list, list):
|
return
|
||||||
return [
|
if isinstance(container, dict):
|
||||||
str(t) for t in current_list
|
current = container.get("0")
|
||||||
if isinstance(t, (str, bytes)) and str(t).strip()
|
if current is None:
|
||||||
]
|
current = container.get(0)
|
||||||
|
if isinstance(current, list):
|
||||||
|
for tag in current:
|
||||||
|
_append_tag(out, tag)
|
||||||
|
|
||||||
return []
|
def _collect_service_data(service_data: Any, out: List[str]) -> None:
|
||||||
|
if not isinstance(service_data, dict):
|
||||||
|
return
|
||||||
|
|
||||||
|
display = (
|
||||||
|
service_data.get("display_tags")
|
||||||
|
or service_data.get("display_friendly_tags")
|
||||||
|
or service_data.get("display")
|
||||||
|
)
|
||||||
|
_collect_current(display, out)
|
||||||
|
|
||||||
|
storage = (
|
||||||
|
service_data.get("storage_tags")
|
||||||
|
or service_data.get("statuses_to_tags")
|
||||||
|
or service_data.get("tags")
|
||||||
|
)
|
||||||
|
_collect_current(storage, out)
|
||||||
|
|
||||||
|
collected: List[str] = []
|
||||||
|
|
||||||
|
if desired_service_key:
|
||||||
|
_collect_service_data(tags_payload.get(desired_service_key), collected)
|
||||||
|
|
||||||
|
if not collected and desired_service_name:
|
||||||
|
for maybe_service in tags_payload.values():
|
||||||
|
if not isinstance(maybe_service, dict):
|
||||||
|
continue
|
||||||
|
svc_name = str(
|
||||||
|
maybe_service.get("service_name")
|
||||||
|
or maybe_service.get("name")
|
||||||
|
or ""
|
||||||
|
).strip().lower()
|
||||||
|
if svc_name and svc_name == desired_service_name:
|
||||||
|
_collect_service_data(maybe_service, collected)
|
||||||
|
|
||||||
|
names_map = tags_payload.get("service_keys_to_names")
|
||||||
|
statuses_map = tags_payload.get("service_keys_to_statuses_to_tags")
|
||||||
|
if isinstance(statuses_map, dict):
|
||||||
|
keys_to_collect: List[str] = []
|
||||||
|
if desired_service_key:
|
||||||
|
keys_to_collect.append(desired_service_key)
|
||||||
|
if desired_service_name and isinstance(names_map, dict):
|
||||||
|
for raw_key, raw_name in names_map.items():
|
||||||
|
if str(raw_name or "").strip().lower() == desired_service_name:
|
||||||
|
keys_to_collect.append(str(raw_key))
|
||||||
|
keys_filter = {k for k in keys_to_collect if k}
|
||||||
|
|
||||||
|
for raw_key, status_payload in statuses_map.items():
|
||||||
|
raw_key_text = str(raw_key)
|
||||||
|
if keys_filter and raw_key_text not in keys_filter:
|
||||||
|
continue
|
||||||
|
_collect_current(status_payload, collected)
|
||||||
|
|
||||||
|
if not collected:
|
||||||
|
for maybe_service in tags_payload.values():
|
||||||
|
_collect_service_data(maybe_service, collected)
|
||||||
|
|
||||||
|
top_level_tags = meta.get("tags_flat")
|
||||||
|
if isinstance(top_level_tags, list):
|
||||||
|
_collect_current(top_level_tags, collected)
|
||||||
|
|
||||||
|
deduped: List[str] = []
|
||||||
|
seen: set[str] = set()
|
||||||
|
for tag in collected:
|
||||||
|
key = str(tag).strip().lower()
|
||||||
|
if not key or key in seen:
|
||||||
|
continue
|
||||||
|
seen.add(key)
|
||||||
|
deduped.append(tag)
|
||||||
|
return deduped
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _extract_title_and_tags(meta: Dict[str, Any], file_id: Any) -> Tuple[str, List[str]]:
|
||||||
|
title = f"Hydrus File {file_id}"
|
||||||
|
tags = HydrusNetwork._extract_tags_from_hydrus_meta(
|
||||||
|
meta,
|
||||||
|
service_key=None,
|
||||||
|
service_name="my tags",
|
||||||
|
)
|
||||||
|
|
||||||
|
normalized_tags: List[str] = []
|
||||||
|
seen: set[str] = set()
|
||||||
|
for raw_tag in tags:
|
||||||
|
text = str(raw_tag or "").strip().lower()
|
||||||
|
if not text or text in seen:
|
||||||
|
continue
|
||||||
|
seen.add(text)
|
||||||
|
normalized_tags.append(text)
|
||||||
|
if text.startswith("title:") and title == f"Hydrus File {file_id}":
|
||||||
|
value = text.split(":", 1)[1].strip()
|
||||||
|
if value:
|
||||||
|
title = value
|
||||||
|
|
||||||
|
return title, normalized_tags
|
||||||
|
|||||||
@@ -585,11 +585,11 @@ def parse_cmdlet_args(args: Sequence[str],
|
|||||||
result = parse_cmdlet_args(["value1", "-count", "5"], cmdlet)
|
result = parse_cmdlet_args(["value1", "-count", "5"], cmdlet)
|
||||||
# result = {"path": "value1", "count": "5"}
|
# result = {"path": "value1", "count": "5"}
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
from SYS.cmdlet_spec import parse_cmdlet_args as _parse_cmdlet_args_fast
|
from SYS.cmdlet_spec import parse_cmdlet_args as _parse_cmdlet_args_fast
|
||||||
|
|
||||||
return _parse_cmdlet_args_fast(args, cmdlet_spec)
|
return _parse_cmdlet_args_fast(args, cmdlet_spec)
|
||||||
except Exception:
|
except Exception:
|
||||||
# Fall back to local implementation below to preserve behavior if the
|
# Fall back to local implementation below to preserve behavior if the
|
||||||
# lightweight parser is unavailable.
|
# lightweight parser is unavailable.
|
||||||
pass
|
pass
|
||||||
|
|||||||
Reference in New Issue
Block a user