continuing refactor

This commit is contained in:
2026-05-03 21:20:05 -07:00
parent 77cab1bd27
commit 5534812426
50 changed files with 1004 additions and 428 deletions
+84
View File
@@ -150,6 +150,20 @@ class PluginInfo:
exposed = True
return exposed and _class_supports_method(self.plugin_class, "upload", Provider.upload)
@property
def is_multi_instance(self) -> bool:
"""True if the plugin declares MULTI_INSTANCE = True."""
return bool(getattr(self.plugin_class, "MULTI_INSTANCE", False))
@property
def supported_cmdlets(self) -> frozenset:
"""Frozenset of cmdlet names this plugin declares support for."""
raw = getattr(self.plugin_class, "SUPPORTED_CMDLETS", frozenset())
try:
return frozenset(str(c) for c in raw)
except Exception:
return frozenset()
class PluginRegistry:
"""Handles discovery, registration, and lookup of built-in and external plugins."""
@@ -433,6 +447,42 @@ class PluginRegistry:
def has_name(self, name: str) -> bool:
return self.get(name) is not None
def get_plugins_for_cmdlet(self, cmdlet_name: str) -> List[PluginInfo]:
"""Return all plugins that declare support for the given cmdlet name."""
self.discover()
target = str(cmdlet_name or "").strip().lower()
return [
info for info in self._infos.values()
if target in info.supported_cmdlets
]
def list_storage_plugin_instances(
self,
config: Optional[Dict[str, Any]] = None,
) -> Dict[str, List[str]]:
"""Return {plugin_name: [instance_name, ...]} for all MULTI_INSTANCE storage plugins.
Instance names come from the plugin's resolved config (plugin/provider/store sections).
Plugins with no configured instances are omitted.
"""
self.discover()
result: Dict[str, List[str]] = {}
for info in self._infos.values():
if not info.is_multi_instance:
continue
if not info.supported_cmdlets.intersection(
{"add-file", "get-file", "get-tag", "add-tag"}
):
continue
try:
instance = info.plugin_class(config or {})
instances = instance.configured_instances()
if instances:
result[info.canonical_name] = instances
except Exception:
pass
return result
def _sync_subclasses(self) -> None:
"""Walk all plugin subclasses in memory and register them."""
def _walk(cls: Type[Provider]) -> None:
@@ -691,6 +741,39 @@ def list_plugin_names_with_capability(capability: str) -> List[str]:
)
def list_configured_plugin_names_with_capability(
capability: str,
config: Optional[Dict[str, Any]] = None,
) -> List[str]:
"""Return plugin names that support `capability` AND have configuration present.
For MULTI_INSTANCE plugins (e.g. hydrusnetwork, ftp) the plugin must have at
least one configured instance. For single-instance plugins the plugin's section
must exist under config["plugin"] or config["provider"].
"""
cfg = config or {}
plugin_section: Dict[str, Any] = cfg.get("plugin") or {} # type: ignore[assignment]
provider_section: Dict[str, Any] = cfg.get("provider") or {} # type: ignore[assignment]
result: List[str] = []
for info in REGISTRY.iter_plugins():
if not _info_supports_capability(info, capability):
continue
name = info.canonical_name
if info.is_multi_instance:
try:
instances = info.plugin_class(cfg).configured_instances()
if instances:
result.append(name)
except Exception:
pass
else:
pname = name.lower()
if isinstance(plugin_section.get(pname), dict) or isinstance(provider_section.get(pname), dict):
result.append(name)
return sorted(result)
def match_plugin_name_for_url(url: str) -> Optional[str]:
raw_url = str(url or "").strip()
raw_url_lower = raw_url.lower()
@@ -907,6 +990,7 @@ __all__ = [
"get_plugin_with_capability",
"list_plugins_with_capability",
"list_plugin_names_with_capability",
"list_configured_plugin_names_with_capability",
"match_plugin_name_for_url",
"get_plugin_for_url",
"list_selection_url_prefixes",