ff
This commit is contained in:
@@ -584,8 +584,20 @@ def set_last_result_table(
|
||||
items: Optional[List[Any]] = None,
|
||||
subject: Optional[Any] = None
|
||||
) -> None:
|
||||
"""
|
||||
Store the last result table and items for @ selection syntax.
|
||||
"""Store the last result table and items for @ selection syntax.
|
||||
|
||||
Persists result table and items across command invocations, enabling
|
||||
subsequent commands to reference and operate on previous results using @N syntax.
|
||||
|
||||
Example:
|
||||
search-file hash:<...> # Returns table with 3 results
|
||||
@1 | get-metadata # Gets metadata for result #1
|
||||
@2 | add-tag foo # Adds tag to result #2
|
||||
|
||||
Args:
|
||||
result_table: Table object with results (can be None to clear)
|
||||
items: List of item objects corresponding to table rows
|
||||
subject: Optional context object (first item or full list)
|
||||
"""
|
||||
state = _get_pipeline_state()
|
||||
|
||||
@@ -662,6 +674,16 @@ def set_last_result_table_overlay(
|
||||
|
||||
Used by action cmdlets (get-metadata, get-tag, get-url) to display detail
|
||||
panels or filtered results without disrupting the primary search-result history.
|
||||
|
||||
Difference from set_last_result_table():
|
||||
- Overlay tables are transient (in-process memory only)
|
||||
- Don't persist across command invocations
|
||||
- Used for "live" displays that shouldn't be part of @N selection
|
||||
|
||||
Args:
|
||||
result_table: Table object with transient results
|
||||
items: List of item objects (not persisted)
|
||||
subject: Optional context object
|
||||
"""
|
||||
state = _get_pipeline_state()
|
||||
state.display_table = result_table
|
||||
@@ -833,8 +855,17 @@ def get_last_result_table() -> Optional[Any]:
|
||||
|
||||
|
||||
def get_last_result_items() -> List[Any]:
|
||||
"""
|
||||
Get the items available for @N selection.
|
||||
"""Get the items available for @N selection in current pipeline context.
|
||||
|
||||
Returns items in priority order:
|
||||
1. Display items (from get-tag, get-metadata, etc.) if display table is selectable
|
||||
2. Last result items (from search-file, etc.) if last result table is selectable
|
||||
3. Empty list if no selectable tables available
|
||||
|
||||
Used to resolve @1, @2, etc. in commands.
|
||||
|
||||
Returns:
|
||||
List of items that can be selected via @N syntax
|
||||
"""
|
||||
state = _get_pipeline_state()
|
||||
# Prioritize items from display commands (get-tag, delete-tag, etc.)
|
||||
|
||||
@@ -136,7 +136,19 @@ def _get_first_dict_value(data: Dict[str, Any], keys: List[str]) -> Any:
|
||||
|
||||
|
||||
def _as_dict(item: Any) -> Optional[Dict[str, Any]]:
|
||||
if isinstance(item, dict):
|
||||
"""Convert any object to dictionary representation.
|
||||
|
||||
Handles:
|
||||
- Dict objects (returned as-is)
|
||||
- Objects with __dict__ attribute (converted to dict)
|
||||
- None or conversion failures (returns None)
|
||||
|
||||
Args:
|
||||
item: Object to convert (dict, dataclass, object, etc.)
|
||||
|
||||
Returns:
|
||||
Dictionary representation or None if conversion fails
|
||||
"""
|
||||
return item
|
||||
try:
|
||||
if hasattr(item, "__dict__"):
|
||||
@@ -148,7 +160,17 @@ def _as_dict(item: Any) -> Optional[Dict[str, Any]]:
|
||||
|
||||
|
||||
def extract_store_value(item: Any) -> str:
|
||||
data = _as_dict(item) or {}
|
||||
"""Extract storage backend name from item.
|
||||
|
||||
Searches item for store identifier using multiple field names:
|
||||
store, table, source, storage (legacy).
|
||||
|
||||
Args:
|
||||
item: Object or dict with store information
|
||||
|
||||
Returns:
|
||||
Store name as string (e.g., "hydrus", "local", "") if not found
|
||||
"""
|
||||
store = _get_first_dict_value(
|
||||
data,
|
||||
["store",
|
||||
@@ -1959,7 +1981,33 @@ def format_result(result: Any, title: str = "") -> str:
|
||||
def extract_item_metadata(item: Any) -> Dict[str, Any]:
|
||||
"""Extract a comprehensive set of metadata from an item for the ItemDetailView.
|
||||
|
||||
Now supports SYS.result_table_api.ResultModel as a first-class input.
|
||||
Converts items (ResultModel, dicts, objects) into normalized metadata dict.
|
||||
Extracts all relevant fields for display: Title, Hash, Store, Path, Ext, Size,
|
||||
Duration, URL, Relations, Tags.
|
||||
|
||||
Optimization:
|
||||
- Calls _as_dict() only once and reuses throughout
|
||||
- Handles both ResultModel objects and legacy dicts/objects
|
||||
|
||||
Example output:
|
||||
{
|
||||
"Title": "video.mp4",
|
||||
"Hash": "abc123def456...",
|
||||
"Store": "hydrus",
|
||||
"Path": "/mnt/media/video.mp4",
|
||||
"Ext": "mp4",
|
||||
"Size": "1.2 GB",
|
||||
"Duration": "1h23m",
|
||||
"Url": "https://example.com/video.mp4",
|
||||
"Relations": <null>,
|
||||
"Tags": "movie, comedy"
|
||||
}
|
||||
|
||||
Args:
|
||||
item: Object to extract metadata from (ResultModel, dict, or any object)
|
||||
|
||||
Returns:
|
||||
Dictionary with standardized metadata fields (empty dict if None input)
|
||||
"""
|
||||
if item is None:
|
||||
return {}
|
||||
@@ -1996,13 +2044,15 @@ def extract_item_metadata(item: Any) -> Dict[str, Any]:
|
||||
return out
|
||||
|
||||
# Fallback to existing extraction logic for legacy objects/dicts
|
||||
# Convert once and reuse throughout to avoid repeated _as_dict() calls
|
||||
data = _as_dict(item) or {}
|
||||
|
||||
# Use existing extractors from match-standard result table columns
|
||||
title = extract_title_value(item)
|
||||
if title:
|
||||
out["Title"] = title
|
||||
else:
|
||||
# Fallback for raw dicts
|
||||
data = _as_dict(item) or {}
|
||||
t = data.get("title") or data.get("name") or data.get("TITLE")
|
||||
if t: out["Title"] = t
|
||||
|
||||
@@ -2013,7 +2063,6 @@ def extract_item_metadata(item: Any) -> Dict[str, Any]:
|
||||
if store: out["Store"] = store
|
||||
|
||||
# Path/Target
|
||||
data = _as_dict(item) or {}
|
||||
path = data.get("path") or data.get("target") or data.get("filename")
|
||||
if path: out["Path"] = path
|
||||
|
||||
@@ -2066,6 +2115,23 @@ class ItemDetailView(Table):
|
||||
|
||||
This is used for 'get-tag', 'get-url' and similar cmdlets where we want to contextually show
|
||||
what is being operated on (the main item) along with the selection list.
|
||||
|
||||
Display structure:
|
||||
┌─ Item Details Panel ─────────────────────────────┐
|
||||
│ Title: video.mp4 │
|
||||
│ Hash: abc123def456789... │
|
||||
│ Store: hydrus │
|
||||
│ Path: /media/video.mp4 │
|
||||
│ Ext: mp4 │
|
||||
│ Url: https://example.com/video.mp4 │
|
||||
└────────────────────────────────────────────────────┘
|
||||
|
||||
# TAGS Value
|
||||
1 .jpg
|
||||
2 .png
|
||||
3 .webp
|
||||
|
||||
Used by action cmdlets that operate on an item and show its related details.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
|
||||
26
SYS/utils.py
26
SYS/utils.py
@@ -552,6 +552,32 @@ def add_direct_link_to_result(
|
||||
setattr(result, "original_link", original_link)
|
||||
|
||||
|
||||
def extract_hydrus_hash_from_url(url: str) -> str | None:
|
||||
"""Extract SHA256 hash from Hydrus API URL.
|
||||
|
||||
Handles URLs like:
|
||||
- http://localhost:45869/get_files/file?hash=abc123...
|
||||
- URLs with &hash=abc123...
|
||||
|
||||
Args:
|
||||
url: URL string to extract hash from
|
||||
|
||||
Returns:
|
||||
Hash hex string (lowercase, 64 chars) if valid SHA256, None otherwise
|
||||
"""
|
||||
try:
|
||||
import re
|
||||
match = re.search(r"[?&]hash=([0-9a-fA-F]+)", str(url or ""))
|
||||
if match:
|
||||
hash_hex = match.group(1).strip().lower()
|
||||
# Validate SHA256 (exactly 64 hex chars)
|
||||
if re.fullmatch(r"[0-9a-f]{64}", hash_hex):
|
||||
return hash_hex
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# URL Policy Resolution - Consolidated from url_parser.py
|
||||
# ============================================================================
|
||||
|
||||
Reference in New Issue
Block a user