实现了渠道插件的全生命周期管理能力,包括: 1. 插件清单的加载、解析与存储 2. 依赖校验与拓扑排序安装/卸载 3. 插件发现与安全沙箱执行环境 4. 插件注册中心与目录管理能力
88 lines
3.0 KiB
Python
88 lines
3.0 KiB
Python
from __future__ import annotations
|
|
|
|
import logging
|
|
from typing import Any
|
|
|
|
from .discovery import ChannelPluginDiscovery
|
|
from .install import install_channel_plugin
|
|
from .manifest import ChannelPluginManifest, load_manifests_from_directory
|
|
from .registry import ChannelPluginRegistry
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class ChannelPluginCatalog:
|
|
"""渠道插件目录 — 管理可用插件的注册、安装与查询。"""
|
|
|
|
def __init__(self, manifest_dir: str | None = None):
|
|
self._manifest_dir = manifest_dir
|
|
self._manifests: dict[str, ChannelPluginManifest] = {}
|
|
self._installed: set[str] = set()
|
|
|
|
def load_manifests(self) -> None:
|
|
if not self._manifest_dir:
|
|
return
|
|
manifests = load_manifests_from_directory(self._manifest_dir)
|
|
for manifest in manifests:
|
|
self._manifests[manifest.id] = manifest
|
|
logger.info("Loaded %d plugin manifests", len(self._manifests))
|
|
|
|
def get_manifest(self, plugin_id: str) -> ChannelPluginManifest | None:
|
|
return self._manifests.get(plugin_id)
|
|
|
|
def list_manifests(self) -> list[ChannelPluginManifest]:
|
|
return list(self._manifests.values())
|
|
|
|
def list_available(self) -> list[str]:
|
|
return list(self._manifests.keys())
|
|
|
|
def list_installed(self) -> list[str]:
|
|
return list(self._installed)
|
|
|
|
def is_installed(self, plugin_id: str) -> bool:
|
|
return plugin_id in self._installed
|
|
|
|
def install(self, plugin_id: str) -> bool:
|
|
manifest = self._manifests.get(plugin_id)
|
|
if not manifest:
|
|
logger.warning("Cannot install plugin '%s': manifest not found", plugin_id)
|
|
return False
|
|
try:
|
|
install_channel_plugin(manifest)
|
|
self._installed.add(plugin_id)
|
|
logger.info("Installed channel plugin: %s", plugin_id)
|
|
return True
|
|
except Exception:
|
|
logger.exception("Failed to install plugin '%s'", plugin_id)
|
|
return False
|
|
|
|
def uninstall(self, plugin_id: str) -> bool:
|
|
if plugin_id not in self._installed:
|
|
return False
|
|
self._installed.discard(plugin_id)
|
|
|
|
manifest = self._manifests.get(plugin_id)
|
|
target_dir = manifest.target_path if manifest else None
|
|
dependencies = manifest.dependencies if manifest else None
|
|
|
|
from .install import uninstall_channel_plugin
|
|
|
|
uninstall_channel_plugin(
|
|
plugin_id=plugin_id,
|
|
target_dir=target_dir,
|
|
dependencies=dependencies,
|
|
)
|
|
return True
|
|
|
|
def discover_builtin(self, package_name: str = "yuxi.channels") -> list[Any]:
|
|
discovered = ChannelPluginDiscovery.discover_builtin(package_name)
|
|
for plugin in discovered:
|
|
self._installed.add(getattr(plugin, "id"))
|
|
return discovered
|
|
|
|
def discover_from_path(self, plugin_path: str) -> list[Any]:
|
|
discovered = ChannelPluginDiscovery.discover_from_path(plugin_path)
|
|
for plugin in discovered:
|
|
self._installed.add(getattr(plugin, "id"))
|
|
return discovered
|