updating and refining plugin system refactor

This commit is contained in:
2026-04-28 22:20:54 -07:00
parent 8685fbb723
commit 323c24f4f4
33 changed files with 4287 additions and 3312 deletions
+248 -76
View File
@@ -16,18 +16,6 @@ from scp import SCPClient
from ProviderCore.base import Provider, SearchResult, parse_inline_query_arguments
def _pick_provider_config(config: Any) -> Dict[str, Any]:
if not isinstance(config, dict):
return {}
provider = config.get("provider")
if not isinstance(provider, dict):
return {}
entry = provider.get("scp")
if isinstance(entry, dict):
return entry
return {}
def _coerce_bool(value: Any, default: bool = False) -> bool:
if isinstance(value, bool):
return value
@@ -166,20 +154,55 @@ class SCP(Provider):
def __init__(self, config: Optional[Dict[str, Any]] = None):
super().__init__(config)
conf = _pick_provider_config(self.config)
self._host = str(conf.get("host") or "").strip()
self._port = _coerce_int(conf.get("port"), 22)
self._username = str(conf.get("username") or conf.get("user") or "").strip()
self._password = str(conf.get("password") or "").strip()
self._key_path = str(conf.get("key_path") or conf.get("identity_file") or "").strip()
self._timeout = max(1, _coerce_int(conf.get("timeout"), 20))
self._search_depth = max(0, _coerce_int(conf.get("search_depth"), 1))
self._allow_agent = _coerce_bool(conf.get("allow_agent"), True)
self._look_for_keys = _coerce_bool(conf.get("look_for_keys"), True)
self._base_path = self._normalize_remote_path(conf.get("base_path") or "/", default="/")
_instance_name, conf = self.resolve_plugin_instance()
defaults = self._settings_from_config(conf)
self._host = str(defaults.get("host") or "").strip()
self._port = int(defaults.get("port") or 22)
self._username = str(defaults.get("username") or "").strip()
self._password = str(defaults.get("password") or "").strip()
self._key_path = str(defaults.get("key_path") or "").strip()
self._timeout = max(1, int(defaults.get("timeout") or 20))
self._search_depth = max(0, int(defaults.get("search_depth") or 1))
self._allow_agent = bool(defaults.get("allow_agent"))
self._look_for_keys = bool(defaults.get("look_for_keys"))
self._base_path = self._normalize_remote_path(defaults.get("base_path") or "/", default="/")
def _settings_from_config(self, conf: Optional[Dict[str, Any]], *, instance_name: Optional[str] = None) -> Dict[str, Any]:
entry = dict(conf or {})
return {
"instance": str(instance_name or entry.get("_instance_name") or "").strip() or None,
"host": str(entry.get("host") or "").strip(),
"port": _coerce_int(entry.get("port"), 22),
"username": str(entry.get("username") or entry.get("user") or "").strip(),
"password": str(entry.get("password") or "").strip(),
"key_path": str(entry.get("key_path") or entry.get("identity_file") or "").strip(),
"timeout": max(1, _coerce_int(entry.get("timeout"), 20)),
"search_depth": max(0, _coerce_int(entry.get("search_depth"), 1)),
"allow_agent": _coerce_bool(entry.get("allow_agent"), True),
"look_for_keys": _coerce_bool(entry.get("look_for_keys"), True),
"base_path": self._normalize_remote_path(entry.get("base_path") or "/", default="/"),
}
def _resolve_settings(
self,
*,
filters: Optional[Dict[str, Any]] = None,
instance_name: Optional[str] = None,
require_explicit: bool = False,
) -> Dict[str, Any]:
requested = self.requested_instance_name(filters, instance=instance_name)
resolved_name, conf = self.resolve_plugin_instance(
requested,
require_explicit=require_explicit or bool(requested),
)
settings = self._settings_from_config(conf, instance_name=resolved_name)
if settings.get("instance") is None and requested:
settings["instance"] = requested
return settings
def validate(self) -> bool:
return bool(self._host and self._username)
settings = self._resolve_settings()
return bool(settings.get("host") and settings.get("username"))
def config_helper_text(self) -> str:
return "Test the SSH/SCP connection before searching. You can also generate an RSA key pair from here."
@@ -210,6 +233,10 @@ class SCP(Provider):
text, inline = parse_inline_query_arguments(query)
filters: Dict[str, Any] = {}
instance_name = str(inline.get("instance") or inline.get("store") or "").strip()
if instance_name:
filters["instance"] = instance_name
if inline.get("path"):
filters["path"] = inline.get("path")
if inline.get("depth"):
@@ -220,17 +247,21 @@ class SCP(Provider):
return text, filters
def get_table_title(self, query: str, filters: Optional[Dict[str, Any]] = None) -> str:
active_path = self._normalize_remote_path((filters or {}).get("path") or self._base_path, default=self._base_path)
settings = self._resolve_settings(filters=filters)
active_path = self._normalize_remote_path((filters or {}).get("path") or settings.get("base_path") or "/", default=str(settings.get("base_path") or "/"))
instance_name = str(settings.get("instance") or "").strip()
text = str(query or "").strip()
if not text or text == "*":
return f"SCP: {active_path}"
return f"SCP: {text} @ {active_path}"
return f"SCP{f'[{instance_name}]' if instance_name else ''}: {active_path}"
return f"SCP{f'[{instance_name}]' if instance_name else ''}: {text} @ {active_path}"
def get_table_metadata(self, query: str, filters: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
settings = self._resolve_settings(filters=filters)
return {
"plugin": self.name,
"host": self._host,
"path": self._normalize_remote_path((filters or {}).get("path") or self._base_path, default=self._base_path),
"instance": settings.get("instance"),
"host": settings.get("host"),
"path": self._normalize_remote_path((filters or {}).get("path") or settings.get("base_path") or "/", default=str(settings.get("base_path") or "/")),
"query": str(query or "").strip(),
}
@@ -243,15 +274,21 @@ class SCP(Provider):
) -> List[SearchResult]:
_ = kwargs
active_filters = dict(filters or {})
start_path = self._normalize_remote_path(active_filters.get("path") or self._base_path, default=self._base_path)
search_depth = max(0, _coerce_int(active_filters.get("depth"), self._search_depth))
settings = self._resolve_settings(filters=active_filters, require_explicit=True)
if not settings.get("host") or not settings.get("username"):
requested = self.requested_instance_name(active_filters)
if requested:
raise RuntimeError(f"SCP instance '{requested}' is unavailable")
return []
start_path = self._normalize_remote_path(active_filters.get("path") or settings.get("base_path") or "/", default=str(settings.get("base_path") or "/"))
search_depth = max(0, _coerce_int(active_filters.get("depth"), int(settings.get("search_depth") or self._search_depth)))
type_filter = str(active_filters.get("type") or "any").strip().lower()
needle = str(query or "").strip()
max_results = max(0, int(limit or 0))
if max_results <= 0:
return []
ssh = self._connect_ssh()
ssh = self._connect_ssh(settings)
sftp = None
try:
try:
@@ -266,6 +303,7 @@ class SCP(Provider):
limit=max_results,
search_depth=search_depth,
type_filter=type_filter,
settings=settings,
)
return self._search_directory(
@@ -275,6 +313,7 @@ class SCP(Provider):
limit=max_results,
search_depth=search_depth,
type_filter=type_filter,
settings=settings,
)
finally:
self._close_client(sftp)
@@ -293,19 +332,23 @@ class SCP(Provider):
target_path = ""
target_title = ""
instance_name = ""
for item in selected_items or []:
metadata = self._item_metadata(item)
if not metadata.get("is_dir"):
continue
target_path = self._normalize_remote_path(metadata.get("scp_path") or metadata.get("selection_path"), default=self._base_path)
settings = self._resolve_settings(instance_name=str(metadata.get("instance") or "").strip() or None, require_explicit=bool(metadata.get("instance")))
target_path = self._normalize_remote_path(metadata.get("scp_path") or metadata.get("selection_path"), default=str(settings.get("base_path") or "/"))
target_title = str(metadata.get("title") or metadata.get("name") or "").strip()
instance_name = str(settings.get("instance") or metadata.get("instance") or "").strip()
if target_path:
break
if not target_path:
return False
ssh = self._connect_ssh()
settings = self._resolve_settings(instance_name=instance_name or None, require_explicit=bool(instance_name))
ssh = self._connect_ssh(settings)
sftp = None
try:
try:
@@ -320,6 +363,7 @@ class SCP(Provider):
limit=500,
search_depth=0,
type_filter="any",
settings=settings,
)
else:
rows = self._search_directory(
@@ -329,6 +373,7 @@ class SCP(Provider):
limit=500,
search_depth=0,
type_filter="any",
settings=settings,
)
finally:
self._close_client(sftp)
@@ -341,18 +386,23 @@ class SCP(Provider):
return True
title = target_title or target_path
table = Table(f"SCP: {title}")._perseverance(True)
table = Table(f"SCP{f'[{instance_name}]' if instance_name else ''}: {title}")._perseverance(True)
table.set_table("scp")
try:
table.set_table_metadata({
"provider": "scp",
"host": self._host,
"instance": instance_name or None,
"host": settings.get("host"),
"path": target_path,
"view": "directory",
})
except Exception:
pass
table.set_source_command("search-file", ["-plugin", "scp", f"path:{target_path}", "*"])
source_args = ["-plugin", "scp"]
if instance_name:
source_args.extend(["-instance", instance_name])
source_args.extend([f"path:{target_path}", "*"])
table.set_source_command("search-file", source_args)
payloads: List[Dict[str, Any]] = []
for row in rows:
@@ -360,7 +410,7 @@ class SCP(Provider):
payloads.append(row.to_dict())
try:
ctx.set_last_result_table(table, payloads, subject={"plugin": "scp", "path": target_path})
ctx.set_last_result_table(table, payloads, subject={"plugin": "scp", "instance": instance_name or None, "path": target_path})
ctx.set_current_stage_table(table)
except Exception:
pass
@@ -373,6 +423,77 @@ class SCP(Provider):
return True
def show_selection_details(
self,
selected_items: List[Any],
*,
ctx: Any,
stage_is_last: bool = True,
source_command: str = "",
table_type: str = "",
table_metadata: Optional[Dict[str, Any]] = None,
**_kwargs: Any,
) -> bool:
_ = table_type
item, _payload, _meta = self.resolve_selection_detail_subject(
selected_items,
stage_is_last=stage_is_last,
source_command=source_command,
require_media_kind="file",
)
if item is None:
return False
metadata = self._item_metadata(item)
if bool(metadata.get("is_dir")):
return False
title = str(metadata.get("title") or metadata.get("name") or metadata.get("path") or "").strip() or "SCP Item"
instance_name = str(metadata.get("instance") or (table_metadata or {}).get("instance") or "").strip()
scp_url = str(metadata.get("scp_url") or metadata.get("selection_url") or metadata.get("path") or "").strip()
remote_path = str(metadata.get("scp_path") or "").strip()
host = str(metadata.get("host") or "").strip()
modified = str(metadata.get("modified") or "").strip()
try:
from SYS.detail_view_helpers import prepare_detail_metadata, render_selection_detail_view
except Exception:
return super().show_selection_details(
selected_items,
ctx=ctx,
stage_is_last=stage_is_last,
source_command=source_command,
table_type=table_type,
table_metadata=table_metadata,
)
detail_metadata = prepare_detail_metadata(
item,
title=title,
store=instance_name or self.name,
path=scp_url or remote_path or None,
tags=metadata.get("tag") or metadata.get("tags"),
extra_fields={
"Plugin": self.name,
"Host": host or None,
"Instance": instance_name or None,
"Remote Path": remote_path or None,
"Directory": str(metadata.get("detail") or "").strip() or None,
"Modified": modified or None,
"Scp Url": scp_url or None,
},
)
return render_selection_detail_view(
ctx=ctx,
item=item,
title=f"SCP Item: {title}",
metadata=detail_metadata,
table_name=self.name,
detail_order=["Title", "Store", "Host", "Instance", "Remote Path", "Directory", "Modified", "Path", "Ext", "SCP URL", "Plugin"],
value_case="preserve",
)
def download(self, result: SearchResult, output_dir: Path) -> Optional[Path]:
metadata = getattr(result, "full_metadata", None)
if isinstance(metadata, dict) and metadata.get("is_dir"):
@@ -380,10 +501,15 @@ class SCP(Provider):
target = str(getattr(result, "path", "") or "").strip()
if not target:
return None
return self.download_url(target, output_dir, title=getattr(result, "title", None))
instance_name = str(metadata.get("instance") or "").strip() if isinstance(metadata, dict) else ""
return self.download_url(target, output_dir, title=getattr(result, "title", None), instance=instance_name or None)
def download_url(self, url: str, output_dir: Path, **kwargs: Any) -> Optional[Path]:
settings = self._connection_settings_for_url(url)
parsed = kwargs.get("parsed") if isinstance(kwargs.get("parsed"), dict) else {}
settings = self._connection_settings_for_url(
url,
instance_name=str(kwargs.get("instance") or parsed.get("instance") or "").strip() or None,
)
remote_path = settings["path"]
if not remote_path or remote_path == "/":
return None
@@ -431,7 +557,12 @@ class SCP(Provider):
return None, None, None
temp_dir = Path(tempfile.mkdtemp(prefix="scp-add-file-"))
downloaded = self.download_url(download_url, temp_dir, title=metadata.get("title"))
downloaded = self.download_url(
download_url,
temp_dir,
title=metadata.get("title"),
instance=metadata.get("instance"),
)
if downloaded is None:
try:
temp_dir.rmdir()
@@ -451,11 +582,24 @@ class SCP(Provider):
if not local_path.exists() or not local_path.is_file():
raise FileNotFoundError(f"File not found: {local_path}")
remote_dir = self._normalize_remote_path(kwargs.get("remote_path") or kwargs.get("path") or self._base_path, default=self._base_path)
settings = self._resolve_settings(
instance_name=str(kwargs.get("instance") or kwargs.get("store") or "").strip() or None,
require_explicit=bool(kwargs.get("instance") or kwargs.get("store")),
)
if not settings.get("host") or not settings.get("username"):
requested = str(kwargs.get("instance") or kwargs.get("store") or "").strip()
if requested:
raise RuntimeError(f"SCP instance '{requested}' is unavailable")
raise RuntimeError("No configured SCP instance is available")
remote_dir = self._normalize_remote_path(
kwargs.get("remote_path") or kwargs.get("path") or settings.get("base_path") or "/",
default=str(settings.get("base_path") or "/"),
)
remote_name = posixpath.basename(str(kwargs.get("remote_name") or local_path.name).replace("\\", "/")) or local_path.name
remote_path = self._join_remote_path(remote_dir, remote_name)
ssh = self._connect_ssh()
ssh = self._connect_ssh(settings)
sftp = None
scp_client = None
try:
@@ -466,7 +610,7 @@ class SCP(Provider):
raise
self._ensure_directory_via_ssh(ssh, remote_dir)
else:
self._ensure_directory(sftp, remote_dir)
self._ensure_directory(sftp, remote_dir, base_path=str(settings.get("base_path") or "/"))
scp_client = self._open_scp(ssh)
scp_client.put(str(local_path), remote_path=remote_path)
finally:
@@ -474,19 +618,20 @@ class SCP(Provider):
self._close_client(sftp)
self._close_client(ssh)
return self._build_url(remote_path)
return self._build_url(remote_path, settings=settings)
def _run_test_connection(self) -> Dict[str, Any]:
if not self._host:
settings = self._resolve_settings()
if not settings.get("host"):
return {"ok": False, "message": "Set 'host' before testing the SCP connection."}
if not self._username:
if not settings.get("username"):
return {"ok": False, "message": "Set 'username' before testing the SCP connection."}
ssh = None
sftp = None
try:
ssh = self._connect_ssh()
base_path = self._base_path or "/"
ssh = self._connect_ssh(settings)
base_path = str(settings.get("base_path") or "/")
transport_detail = "SFTP available"
try:
sftp = self._open_sftp(ssh)
@@ -502,10 +647,11 @@ class SCP(Provider):
except Exception:
is_dir = False
detail = f" and confirmed {base_path}" if is_dir else ""
auth_mode = f"key {self._key_path}" if self._key_path else "password/agent auth"
key_path = str(settings.get("key_path") or "").strip()
auth_mode = f"key {key_path}" if key_path else "password/agent auth"
return {
"ok": True,
"message": f"Connected to SCP {self._host}:{self._port} as {self._username} via {auth_mode}. {transport_detail}{detail}.",
"message": f"Connected to SCP {settings.get('host')}:{settings.get('port')} as {settings.get('username')} via {auth_mode}. {transport_detail}{detail}.",
}
except Exception as exc:
return {"ok": False, "message": f"SCP connection failed: {exc}"}
@@ -514,7 +660,9 @@ class SCP(Provider):
self._close_client(ssh)
def _generate_ssh_keypair(self) -> Dict[str, Any]:
target = Path(self._key_path).expanduser() if self._key_path else (Path.home() / ".ssh" / "medeia_scp_rsa")
settings = self._resolve_settings()
key_path = str(settings.get("key_path") or "").strip()
target = Path(key_path).expanduser() if key_path else (Path.home() / ".ssh" / "medeia_scp_rsa")
try:
target.parent.mkdir(parents=True, exist_ok=True)
except Exception as exc:
@@ -530,7 +678,7 @@ class SCP(Provider):
try:
key = paramiko.RSAKey.generate(bits=4096)
key.write_private_key_file(str(target))
comment = f"{self._username or 'medeia'}@{self._host or 'scp'}"
comment = f"{settings.get('username') or 'medeia'}@{settings.get('host') or 'scp'}"
public_path.write_text(f"{key.get_name()} {key.get_base64()} {comment}\n", encoding="utf-8")
try:
target.chmod(0o600)
@@ -654,34 +802,40 @@ class SCP(Provider):
self,
remote_path: Any,
*,
settings: Optional[Dict[str, Any]] = None,
host: Optional[str] = None,
port: Optional[int] = None,
scheme: str = "scp",
) -> str:
resolved = dict(settings or {})
path_text = self._normalize_remote_path(remote_path, default="/")
host_text = str(host or self._host).strip()
port_value = int(port or self._port)
host_text = str(host or resolved.get("host") or self._host).strip()
port_value = int(port or resolved.get("port") or self._port)
port_suffix = f":{port_value}" if port_value and port_value != 22 else ""
return f"{scheme}://{host_text}{port_suffix}{quote(path_text, safe='/-._~!$&\'()*+,;=:@')}"
def _connection_settings_for_url(self, url: str) -> Dict[str, Any]:
def _connection_settings_for_url(self, url: str, *, instance_name: Optional[str] = None) -> Dict[str, Any]:
settings = self._resolve_settings(instance_name=instance_name, require_explicit=bool(instance_name))
parsed = urlparse(str(url or "").strip())
scheme = (parsed.scheme or "scp").strip().lower()
host = parsed.hostname or self._host
port = parsed.port or self._port
username = parsed.username or self._username
password = parsed.password or self._password
path_text = self._normalize_remote_path(unquote(parsed.path or "/"), default="/")
host = parsed.hostname or settings.get("host") or self._host
port = parsed.port or settings.get("port") or self._port
username = parsed.username or settings.get("username") or self._username
password = parsed.password or settings.get("password") or self._password
path_text = self._normalize_remote_path(unquote(parsed.path or "/"), default=str(settings.get("base_path") or "/"))
return {
"instance": settings.get("instance"),
"scheme": scheme,
"host": host,
"port": port,
"username": username,
"password": password,
"key_path": self._key_path,
"allow_agent": self._allow_agent,
"look_for_keys": self._look_for_keys,
"key_path": settings.get("key_path") or self._key_path,
"allow_agent": settings.get("allow_agent", self._allow_agent),
"look_for_keys": settings.get("look_for_keys", self._look_for_keys),
"path": path_text,
"timeout": settings.get("timeout", self._timeout),
"base_path": settings.get("base_path", self._base_path),
}
def _search_directory(
@@ -693,12 +847,13 @@ class SCP(Provider):
limit: int,
search_depth: int,
type_filter: str,
settings: Dict[str, Any],
) -> List[SearchResult]:
results: List[SearchResult] = []
visited: set[str] = set()
def walk(current_path: str, depth_left: int) -> None:
normalized = self._normalize_remote_path(current_path, default=self._base_path)
normalized = self._normalize_remote_path(current_path, default=str(settings.get("base_path") or self._base_path))
if normalized in visited or len(results) >= limit:
return
visited.add(normalized)
@@ -707,7 +862,7 @@ class SCP(Provider):
if len(results) >= limit:
return
if self._matches_entry(entry, needle=needle, type_filter=type_filter):
results.append(self._build_result(entry))
results.append(self._build_result(entry, settings=settings))
if entry.get("is_dir") and depth_left > 0:
walk(str(entry.get("scp_path") or normalized), depth_left - 1)
@@ -723,6 +878,7 @@ class SCP(Provider):
limit: int,
search_depth: int,
type_filter: str,
settings: Dict[str, Any],
) -> List[SearchResult]:
entries = self._list_directory_via_ssh(ssh, start_path, depth=search_depth)
results: List[SearchResult] = []
@@ -730,7 +886,7 @@ class SCP(Provider):
if len(results) >= limit:
break
if self._matches_entry(entry, needle=needle, type_filter=type_filter):
results.append(self._build_result(entry))
results.append(self._build_result(entry, settings=settings))
return results
def _matches_entry(self, entry: Dict[str, Any], *, needle: str, type_filter: str) -> bool:
@@ -756,16 +912,18 @@ class SCP(Provider):
return False
return True
def _build_result(self, entry: Dict[str, Any]) -> SearchResult:
def _build_result(self, entry: Dict[str, Any], *, settings: Dict[str, Any]) -> SearchResult:
scp_path = str(entry.get("scp_path") or "/")
scp_url = self._build_url(scp_path)
scp_url = self._build_url(scp_path, settings=settings)
is_dir = bool(entry.get("is_dir"))
size_value = entry.get("size")
modified = str(entry.get("modified") or "")
parent = posixpath.dirname(scp_path.rstrip("/")) or "/"
instance_name = str(settings.get("instance") or "").strip()
metadata = {
"provider": "scp",
"host": self._host,
"instance": instance_name or None,
"host": settings.get("host"),
"scp_path": scp_path,
"scp_url": scp_url,
"selection_url": scp_url,
@@ -777,6 +935,13 @@ class SCP(Provider):
if modified:
metadata["modified"] = modified
selection_args = ["-url", scp_url]
selection_action = ["download-file", "-plugin", "scp"]
if instance_name:
selection_args = ["-instance", instance_name, *selection_args]
selection_action.extend(["-instance", instance_name])
selection_action.extend(["-url", scp_url])
return SearchResult(
table="scp",
title=str(entry.get("name") or scp_path),
@@ -793,8 +958,8 @@ class SCP(Provider):
("Size", "" if size_value is None else str(size_value)),
("Modified", modified),
],
selection_args=None if is_dir else ["-url", scp_url],
selection_action=None if is_dir else ["download-file", "-plugin", "scp", "-url", scp_url],
selection_args=None if is_dir else selection_args,
selection_action=None if is_dir else selection_action,
full_metadata=metadata,
)
@@ -867,8 +1032,8 @@ class SCP(Provider):
)
return entries
def _ensure_directory(self, sftp: Any, remote_path: str) -> None:
normalized = self._normalize_remote_path(remote_path, default=self._base_path)
def _ensure_directory(self, sftp: Any, remote_path: str, *, base_path: str) -> None:
normalized = self._normalize_remote_path(remote_path, default=base_path)
if normalized == "/":
return
partial = ""
@@ -923,11 +1088,18 @@ class SCP(Provider):
if path_text.startswith(("scp://", "sftp://")):
scp_path = self._normalize_remote_path(path_text, default=self._base_path)
if scp_path:
metadata["scp_path"] = self._normalize_remote_path(scp_path, default=self._base_path)
base_path = str(metadata.get("base_path") or self._base_path)
metadata["scp_path"] = self._normalize_remote_path(scp_path, default=base_path)
metadata.setdefault("selection_path", metadata["scp_path"])
if metadata.get("scp_path") and not metadata.get("scp_url"):
metadata["scp_url"] = self._build_url(metadata["scp_path"])
metadata["scp_url"] = self._build_url(
metadata["scp_path"],
settings={
"host": metadata.get("host") or self._host,
"instance": metadata.get("instance"),
},
)
if metadata.get("scp_url") and not metadata.get("selection_url"):
metadata["selection_url"] = metadata["scp_url"]