h
This commit is contained in:
@@ -1153,6 +1153,16 @@ class PipelineLiveProgress:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Auto-stop Live rendering once all pipes are complete so the progress
|
||||
# UI clears itself even if callers forget to stop it explicitly.
|
||||
try:
|
||||
if self._live is not None and self._pipe_labels:
|
||||
total_pipes = len(self._pipe_labels)
|
||||
if total_pipes > 0 and completed >= total_pipes:
|
||||
self.stop()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def begin_pipe_steps(self, pipe_index: int, *, total_steps: int) -> None:
|
||||
"""Initialize step tracking for a pipe.
|
||||
|
||||
|
||||
@@ -2821,6 +2821,7 @@ class PipelineExecutor:
|
||||
|
||||
pipe_idx = pipe_index_by_stage.get(stage_index)
|
||||
|
||||
overlay_table: Any | None = None
|
||||
session = WorkerStages.begin_stage(
|
||||
worker_manager,
|
||||
cmd_name=cmd_name,
|
||||
@@ -2856,24 +2857,17 @@ class PipelineExecutor:
|
||||
# Pipeline overlay tables (e.g., get-url detail views) need to be
|
||||
# rendered when running inside a pipeline because the CLI path
|
||||
# normally handles rendering. The overlay is only useful when
|
||||
# we're at the terminal stage of the pipeline.
|
||||
# we're at the terminal stage of the pipeline. Save the table so
|
||||
# it can be printed after the pipe finishes.
|
||||
overlay_table = None
|
||||
if stage_index + 1 >= len(stages):
|
||||
display_table = None
|
||||
try:
|
||||
display_table = (
|
||||
overlay_table = (
|
||||
ctx.get_display_table()
|
||||
if hasattr(ctx, "get_display_table") else None
|
||||
)
|
||||
except Exception:
|
||||
display_table = None
|
||||
if display_table is not None:
|
||||
try:
|
||||
from SYS.rich_display import stdout_console
|
||||
|
||||
stdout_console().print()
|
||||
stdout_console().print(display_table)
|
||||
except Exception:
|
||||
pass
|
||||
overlay_table = None
|
||||
|
||||
# Update piped_result for next stage from emitted items
|
||||
stage_emits = list(stage_ctx.emits)
|
||||
@@ -2884,6 +2878,14 @@ class PipelineExecutor:
|
||||
finally:
|
||||
if progress_ui is not None and pipe_idx is not None:
|
||||
progress_ui.finish_pipe(pipe_idx)
|
||||
if overlay_table is not None:
|
||||
try:
|
||||
from SYS.rich_display import stdout_console
|
||||
|
||||
stdout_console().print()
|
||||
stdout_console().print(overlay_table)
|
||||
except Exception:
|
||||
pass
|
||||
if session:
|
||||
try:
|
||||
session.close()
|
||||
|
||||
@@ -554,6 +554,14 @@ class Add_File(Cmdlet):
|
||||
media_path, file_hash, temp_dir_to_cleanup = self._resolve_source(
|
||||
item, path_arg, pipe_obj, config, store_instance=storage_registry
|
||||
)
|
||||
if not media_path and provider_name:
|
||||
media_path, file_hash, temp_dir_to_cleanup = Add_File._download_provider_source(
|
||||
pipe_obj, config, storage_registry
|
||||
)
|
||||
if media_path:
|
||||
debug(
|
||||
f"[add-file] Provider source downloaded: {media_path}"
|
||||
)
|
||||
debug(
|
||||
f"[add-file] RESOLVED source: path={media_path}, hash={file_hash if file_hash else 'N/A'}..."
|
||||
)
|
||||
@@ -1071,6 +1079,28 @@ class Add_File(Cmdlet):
|
||||
pass
|
||||
return None, None
|
||||
|
||||
@staticmethod
|
||||
def _resolve_backend_by_name(store: Any, backend_name: str) -> Optional[Any]:
|
||||
if not store or not backend_name:
|
||||
return None
|
||||
try:
|
||||
return store[backend_name]
|
||||
except Exception:
|
||||
pass
|
||||
target = str(backend_name or "").strip().lower()
|
||||
if not target:
|
||||
return None
|
||||
try:
|
||||
for candidate in store.list_backends():
|
||||
if isinstance(candidate, str) and candidate.strip().lower() == target:
|
||||
try:
|
||||
return store[candidate]
|
||||
except Exception:
|
||||
continue
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _resolve_source(
|
||||
result: Any,
|
||||
@@ -1111,15 +1141,12 @@ class Add_File(Cmdlet):
|
||||
if not store:
|
||||
store = Store(config)
|
||||
|
||||
if r_store in store.list_backends():
|
||||
backend = store[r_store]
|
||||
# Try direct access (Path)
|
||||
backend = Add_File._resolve_backend_by_name(store, r_store)
|
||||
if backend is not None:
|
||||
mp = backend.get_file(r_hash)
|
||||
if isinstance(mp, Path) and mp.exists():
|
||||
pipe_obj.path = str(mp)
|
||||
return mp, str(r_hash), None
|
||||
|
||||
# Try download to temp
|
||||
if isinstance(mp, str) and mp.strip():
|
||||
dl_path, tmp_dir = Add_File._maybe_download_backend_file(
|
||||
backend, str(r_hash), pipe_obj
|
||||
@@ -1162,6 +1189,41 @@ class Add_File(Cmdlet):
|
||||
log("File path could not be resolved")
|
||||
return None, None, None
|
||||
|
||||
@staticmethod
|
||||
def _download_provider_source(
|
||||
pipe_obj: models.PipeObject,
|
||||
config: Dict[str, Any],
|
||||
store_instance: Optional[Any],
|
||||
) -> Tuple[Optional[Path], Optional[str], Optional[Path]]:
|
||||
r_hash = str(getattr(pipe_obj, "hash", None) or getattr(pipe_obj, "file_hash", None) or "").strip()
|
||||
r_store = str(getattr(pipe_obj, "store", None) or "").strip()
|
||||
if not (r_hash and r_store):
|
||||
return None, None, None
|
||||
|
||||
try:
|
||||
store = store_instance or Store(config)
|
||||
except Exception:
|
||||
store = None
|
||||
backend = Add_File._resolve_backend_by_name(store, r_store) if store is not None else None
|
||||
if backend is None:
|
||||
return None, None, None
|
||||
|
||||
try:
|
||||
source = backend.get_file(r_hash.lower())
|
||||
if isinstance(source, Path) and source.exists():
|
||||
pipe_obj.path = str(source)
|
||||
return source, str(r_hash), None
|
||||
if isinstance(source, str) and source.strip():
|
||||
dl_path, tmp_dir = Add_File._maybe_download_backend_file(
|
||||
backend, str(r_hash), pipe_obj
|
||||
)
|
||||
if dl_path and dl_path.exists():
|
||||
return dl_path, str(r_hash), tmp_dir
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return None, None, None
|
||||
|
||||
@staticmethod
|
||||
def _scan_directory_for_files(directory: Path, compute_hash: bool = True) -> List[Dict[str, Any]]:
|
||||
"""Scan a directory for supported media files and return list of file info dicts.
|
||||
|
||||
@@ -20,6 +20,7 @@ from . import _shared as sh
|
||||
from SYS.logger import log, debug
|
||||
from Store import Store
|
||||
from SYS.config import resolve_output_dir
|
||||
from API.HTTP import _download_direct_file
|
||||
|
||||
|
||||
class Get_File(sh.Cmdlet):
|
||||
@@ -148,33 +149,33 @@ class Get_File(sh.Cmdlet):
|
||||
|
||||
debug(f"[get-file] backend.get_file returned: {source_path}")
|
||||
|
||||
# Check if backend returned a URL (HydrusNetwork case)
|
||||
if isinstance(source_path,
|
||||
str) and (source_path.startswith("http://")
|
||||
or source_path.startswith("https://")):
|
||||
# Hydrus backend returns a URL; open it only for this explicit user action.
|
||||
download_url = None
|
||||
if isinstance(source_path, str):
|
||||
if source_path.startswith("http://") or source_path.startswith("https://"):
|
||||
download_url = source_path
|
||||
else:
|
||||
source_path = Path(source_path)
|
||||
|
||||
if download_url and output_path is None:
|
||||
# Hydrus backend returns a URL; open it only when no output path
|
||||
try:
|
||||
webbrowser.open(source_path)
|
||||
webbrowser.open(download_url)
|
||||
except Exception as exc:
|
||||
log(f"Error opening browser: {exc}", file=sys.stderr)
|
||||
else:
|
||||
debug(f"Opened in browser: {source_path}", file=sys.stderr)
|
||||
debug(f"Opened in browser: {download_url}", file=sys.stderr)
|
||||
|
||||
# Emit result for pipeline
|
||||
ctx.emit(
|
||||
{
|
||||
"hash": file_hash,
|
||||
"store": store_name,
|
||||
"url": source_path,
|
||||
"url": download_url,
|
||||
"title": resolve_display_title() or "Opened",
|
||||
}
|
||||
)
|
||||
return 0
|
||||
|
||||
# Otherwise treat as file path (local/folder backends)
|
||||
if isinstance(source_path, str):
|
||||
source_path = Path(source_path)
|
||||
|
||||
if download_url is None:
|
||||
if not source_path or not source_path.exists():
|
||||
log(f"Error: Backend could not retrieve file for hash {file_hash}")
|
||||
return 1
|
||||
@@ -206,8 +207,18 @@ class Get_File(sh.Cmdlet):
|
||||
ext = "." + ext
|
||||
filename += ext
|
||||
|
||||
dest_path: Path
|
||||
if download_url:
|
||||
downloaded = _download_direct_file(
|
||||
download_url,
|
||||
output_dir,
|
||||
quiet=True,
|
||||
suggested_filename=filename,
|
||||
)
|
||||
dest_path = downloaded.path
|
||||
debug(f"[get-file] Downloaded remote file to {dest_path}", file=sys.stderr)
|
||||
else:
|
||||
dest_path = self._unique_path(output_dir / filename)
|
||||
|
||||
# Copy file to destination
|
||||
debug(f"[get-file] Copying {source_path} -> {dest_path}", file=sys.stderr)
|
||||
shutil.copy2(source_path, dest_path)
|
||||
|
||||
@@ -59,7 +59,11 @@ class _WorkerLogger:
|
||||
|
||||
def update_worker_status(self, worker_id: str, status: str) -> None:
|
||||
try:
|
||||
update_worker(worker_id, status=status)
|
||||
normalized = (status or "").lower()
|
||||
kwargs: dict[str, str] = {"status": status}
|
||||
if normalized in {"completed", "error", "cancelled"}:
|
||||
kwargs["result"] = normalized
|
||||
update_worker(worker_id, **kwargs)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
Reference in New Issue
Block a user