Title: Implement minimal core framework with PL injection and update build config
Key features implemented: - Updated package metadata and dependencies in PKG-INFO, setup files - Added main.py entry point for backward compatibility with README launch method - Enhanced CLI with config options, system info command, and proper signal handling - Implemented minimal PluginManager loading only plugin-loader core plugin - Refactored PluginLoader to follow minimal core design, removed sandbox/isolation complexity - Updated auto-dependency plugin with safer PL injection mechanism and disabled pl_injection - Removed legacy plugin files (firewall, frp_proxy, ftp_server, multi_lang_deploy, ops_toolbox, security_gateway) as functionality moved to core plugin system - Improved gitignore with comprehensive ignore patterns The changes implement a minimal core framework design where only the plugin-loader is directly loaded by the core, with all other plugins managed through the PL injection mechanism, significantly simplifying the architecture.
This commit is contained in:
278
store/@{FutureOSS}/plugin-loader-pro/core/manager.py
Normal file
278
store/@{FutureOSS}/plugin-loader-pro/core/manager.py
Normal file
@@ -0,0 +1,278 @@
|
||||
"""插件加载 Pro - 核心管理器"""
|
||||
import sys
|
||||
import json
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional
|
||||
|
||||
from oss.plugin.types import Plugin
|
||||
from .config import ProConfig
|
||||
from .registry import CapabilityRegistry
|
||||
from .proxy import PluginProxy, PermissionError
|
||||
from ..models.plugin_info import PluginInfo
|
||||
from ..circuit.breaker import CircuitBreaker
|
||||
from ..retry.handler import RetryHandler
|
||||
from ..fallback.handler import FallbackHandler
|
||||
from ..recovery.health import HealthChecker
|
||||
from ..recovery.auto_fix import AutoRecovery
|
||||
from ..isolation.timeout import TimeoutController, TimeoutError
|
||||
from ..utils.logger import ProLogger
|
||||
from oss.plugin.capabilities import scan_capabilities
|
||||
|
||||
|
||||
class ProPluginManager:
|
||||
"""Pro 插件管理器"""
|
||||
|
||||
def __init__(self, config: ProConfig):
|
||||
self.config = config
|
||||
self.plugins: dict[str, dict[str, Any]] = {}
|
||||
self.capability_registry = CapabilityRegistry()
|
||||
self._breakers: dict[str, CircuitBreaker] = {}
|
||||
self._health_checker = HealthChecker(
|
||||
config.health_check.interval,
|
||||
config.health_check.timeout,
|
||||
config.health_check.max_failures
|
||||
)
|
||||
self._auto_recovery = AutoRecovery(
|
||||
config.auto_recovery.max_attempts,
|
||||
config.auto_recovery.delay
|
||||
)
|
||||
|
||||
def load_all(self, store_dir: str = "store"):
|
||||
"""加载所有插件"""
|
||||
ProLogger.info("loader", "开始扫描插件...")
|
||||
|
||||
self._load_from_dir(Path(store_dir))
|
||||
|
||||
ProLogger.info("loader", f"共加载 {len(self.plugins)} 个插件")
|
||||
|
||||
def _load_from_dir(self, store_dir: Path):
|
||||
"""从目录加载插件"""
|
||||
if not store_dir.exists():
|
||||
return
|
||||
|
||||
for author_dir in store_dir.iterdir():
|
||||
if not author_dir.is_dir():
|
||||
continue
|
||||
|
||||
for plugin_dir in author_dir.iterdir():
|
||||
if not plugin_dir.is_dir():
|
||||
continue
|
||||
|
||||
main_file = plugin_dir / "main.py"
|
||||
if not main_file.exists():
|
||||
continue
|
||||
|
||||
self._load_single_plugin(plugin_dir)
|
||||
|
||||
def _load_single_plugin(self, plugin_dir: Path) -> Optional[Any]:
|
||||
"""加载单个插件"""
|
||||
main_file = plugin_dir / "main.py"
|
||||
manifest_file = plugin_dir / "manifest.json"
|
||||
|
||||
try:
|
||||
manifest = {}
|
||||
if manifest_file.exists():
|
||||
with open(manifest_file, "r", encoding="utf-8") as f:
|
||||
manifest = json.load(f)
|
||||
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
f"pro_plugin.{plugin_dir.name}", str(main_file)
|
||||
)
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
sys.modules[spec.name] = module
|
||||
spec.loader.exec_module(module)
|
||||
|
||||
if not hasattr(module, "New"):
|
||||
return None
|
||||
|
||||
instance = module.New()
|
||||
|
||||
plugin_name = plugin_dir.name.rstrip("}")
|
||||
permissions = manifest.get("permissions", [])
|
||||
|
||||
if permissions:
|
||||
instance = PluginProxy(
|
||||
plugin_name, instance, permissions, self.plugins
|
||||
)
|
||||
|
||||
info = PluginInfo()
|
||||
meta = manifest.get("metadata", {})
|
||||
info.name = meta.get("name", plugin_name)
|
||||
info.version = meta.get("version", "1.0.0")
|
||||
info.author = meta.get("author", "")
|
||||
info.description = meta.get("description", "")
|
||||
info.dependencies = manifest.get("dependencies", [])
|
||||
info.capabilities = scan_capabilities(plugin_dir)
|
||||
|
||||
for cap in info.capabilities:
|
||||
self.capability_registry.register_provider(
|
||||
cap, plugin_name, instance
|
||||
)
|
||||
|
||||
self._breakers[plugin_name] = CircuitBreaker(
|
||||
self.config.circuit_breaker.failure_threshold,
|
||||
self.config.circuit_breaker.recovery_timeout,
|
||||
self.config.circuit_breaker.half_open_requests
|
||||
)
|
||||
|
||||
self.plugins[plugin_name] = {
|
||||
"instance": instance,
|
||||
"module": module,
|
||||
"info": info,
|
||||
"permissions": permissions,
|
||||
"dir": plugin_dir
|
||||
}
|
||||
|
||||
ProLogger.info("loader", f"已加载: {plugin_name} v{info.version}")
|
||||
return instance
|
||||
|
||||
except Exception as e:
|
||||
ProLogger.error("loader", f"加载失败 {plugin_dir.name}: {e}")
|
||||
return None
|
||||
|
||||
def init_and_start_all(self):
|
||||
"""初始化并启动所有插件"""
|
||||
ProLogger.info("manager", "开始初始化所有插件...")
|
||||
|
||||
self._inject_dependencies()
|
||||
ordered = self._get_ordered_plugins()
|
||||
|
||||
for name in ordered:
|
||||
self._safe_init(name)
|
||||
|
||||
ProLogger.info("manager", "开始启动所有插件...")
|
||||
for name in ordered:
|
||||
self._safe_start(name)
|
||||
|
||||
self._health_checker.start(
|
||||
on_failure_callback=self._on_plugin_failure
|
||||
)
|
||||
|
||||
def _safe_init(self, name: str):
|
||||
"""安全初始化插件"""
|
||||
info = self.plugins[name]
|
||||
instance = info["instance"]
|
||||
breaker = self._breakers[name]
|
||||
|
||||
try:
|
||||
breaker.call(instance.init)
|
||||
info["info"].status = "initialized"
|
||||
ProLogger.info("manager", f"已初始化: {name}")
|
||||
except Exception as e:
|
||||
ProLogger.error("manager", f"初始化失败 {name}: {e}")
|
||||
info["info"].status = "error"
|
||||
info["info"].error_count += 1
|
||||
info["info"].last_error = str(e)
|
||||
|
||||
def _safe_start(self, name: str):
|
||||
"""安全启动插件"""
|
||||
info = self.plugins[name]
|
||||
instance = info["instance"]
|
||||
breaker = self._breakers[name]
|
||||
|
||||
try:
|
||||
breaker.call(instance.start)
|
||||
info["info"].status = "running"
|
||||
self._health_checker.add_plugin(name, instance)
|
||||
ProLogger.info("manager", f"已启动: {name}")
|
||||
except Exception as e:
|
||||
ProLogger.error("manager", f"启动失败 {name}: {e}")
|
||||
info["info"].status = "error"
|
||||
info["info"].error_count += 1
|
||||
info["info"].last_error = str(e)
|
||||
|
||||
def stop_all(self):
|
||||
"""停止所有插件"""
|
||||
self._health_checker.stop()
|
||||
|
||||
for name in reversed(list(self.plugins.keys())):
|
||||
self._safe_stop(name)
|
||||
|
||||
def _safe_stop(self, name: str):
|
||||
"""安全停止插件"""
|
||||
info = self.plugins[name]
|
||||
instance = info["instance"]
|
||||
|
||||
try:
|
||||
instance.stop()
|
||||
info["info"].status = "stopped"
|
||||
ProLogger.info("manager", f"已停止: {name}")
|
||||
except Exception as e:
|
||||
ProLogger.warn("manager", f"停止异常 {name}: {e}")
|
||||
|
||||
def _on_plugin_failure(self, name: str):
|
||||
"""插件失败回调"""
|
||||
ProLogger.error("recovery", f"插件 {name} 健康检查失败")
|
||||
|
||||
if not self.config.auto_recovery.enabled:
|
||||
return
|
||||
|
||||
info = self.plugins.get(name)
|
||||
if not info:
|
||||
return
|
||||
|
||||
plugin_dir = info.get("dir")
|
||||
module = info.get("module")
|
||||
instance = info.get("instance")
|
||||
|
||||
if plugin_dir:
|
||||
result = self._auto_recovery.attempt_recovery(
|
||||
name, plugin_dir, module, instance
|
||||
)
|
||||
if result:
|
||||
info["instance"] = result
|
||||
info["info"].status = "running"
|
||||
self._health_checker.reset_failure_count(name)
|
||||
|
||||
def _inject_dependencies(self):
|
||||
"""注入依赖"""
|
||||
name_map = {}
|
||||
for name in self.plugins:
|
||||
clean = name.rstrip("}")
|
||||
name_map[clean] = name
|
||||
name_map[clean + "}"] = name
|
||||
|
||||
for name, info in self.plugins.items():
|
||||
deps = info["info"].dependencies
|
||||
if not deps:
|
||||
continue
|
||||
|
||||
for dep_name in deps:
|
||||
actual_dep = name_map.get(dep_name) or name_map.get(dep_name + "}")
|
||||
if actual_dep and actual_dep in self.plugins:
|
||||
dep_instance = self.plugins[actual_dep]["instance"]
|
||||
setter = f"set_{dep_name.replace('-', '_')}"
|
||||
|
||||
if hasattr(info["instance"], setter):
|
||||
try:
|
||||
getattr(info["instance"], setter)(dep_instance)
|
||||
ProLogger.info("inject", f"{name} <- {actual_dep}")
|
||||
except Exception as e:
|
||||
ProLogger.error("inject", f"注入失败 {name}.{setter}: {e}")
|
||||
|
||||
def _get_ordered_plugins(self) -> list[str]:
|
||||
"""获取插件顺序"""
|
||||
ordered = []
|
||||
visited = set()
|
||||
|
||||
def visit(name):
|
||||
if name in visited:
|
||||
return
|
||||
visited.add(name)
|
||||
|
||||
info = self.plugins.get(name)
|
||||
if not info:
|
||||
return
|
||||
|
||||
for dep in info["info"].dependencies:
|
||||
clean_dep = dep.rstrip("}")
|
||||
if clean_dep in self.plugins:
|
||||
visit(clean_dep)
|
||||
|
||||
ordered.append(name)
|
||||
|
||||
for name in self.plugins:
|
||||
visit(name)
|
||||
|
||||
return ordered
|
||||
Reference in New Issue
Block a user