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