EU-Utility/core/plugin_api.py

236 lines
8.0 KiB
Python

"""
Plugin API for EU-Utility plugin system.
Manages plugin lifecycle and provides service registration.
"""
import importlib
import os
import sys
from pathlib import Path
from typing import Dict, List, Optional, Type, Any
from core.base_plugin import BasePlugin
from core.clipboard import ClipboardManager, get_clipboard_manager
class PluginAPI:
"""
Plugin API for EU-Utility.
Responsibilities:
- Plugin discovery and loading
- Plugin lifecycle management (start/stop)
- Service registration (clipboard, etc.)
"""
def __init__(self):
self._plugins: Dict[str, BasePlugin] = {}
self._plugin_classes: Dict[str, Type[BasePlugin]] = {}
self._clipboard_manager: Optional[ClipboardManager] = None
self._services_registered = False
# Service Registration
def register_clipboard_service(self, auto_start_monitoring: bool = False) -> ClipboardManager:
"""
Register and initialize the clipboard service.
This makes clipboard functionality available to all plugins.
Must be called before loading plugins that use clipboard features.
Args:
auto_start_monitoring: Whether to start clipboard monitoring automatically
Returns:
The initialized ClipboardManager instance
Example:
api = PluginAPI()
api.register_clipboard_service(auto_start_monitoring=True)
api.load_plugins()
"""
self._clipboard_manager = get_clipboard_manager()
if auto_start_monitoring:
self._clipboard_manager.start_monitoring()
print("[PluginAPI] Clipboard monitoring started")
# Inject clipboard manager into all loaded plugins
for plugin in self._plugins.values():
plugin._set_clipboard_manager(self._clipboard_manager)
print("[PluginAPI] Clipboard service registered")
return self._clipboard_manager
def get_clipboard_manager(self) -> Optional[ClipboardManager]:
"""Get the clipboard manager if registered."""
return self._clipboard_manager
# Plugin Management
def load_plugin(self, plugin_class: Type[BasePlugin]) -> BasePlugin:
"""
Load a single plugin class.
Args:
plugin_class: The plugin class to instantiate
Returns:
The instantiated plugin
"""
plugin = plugin_class()
# Inject clipboard manager if available
if self._clipboard_manager:
plugin._set_clipboard_manager(self._clipboard_manager)
self._plugins[plugin.name] = plugin
self._plugin_classes[plugin.name] = plugin_class
print(f"[PluginAPI] Loaded plugin: {plugin.name} v{plugin.version}")
return plugin
def load_plugins_from_directory(self, directory: str = "plugins") -> List[BasePlugin]:
"""
Discover and load all plugins from a directory.
Args:
directory: Path to the plugins directory
Returns:
List of loaded plugins
"""
plugins_dir = Path(directory)
if not plugins_dir.exists():
print(f"[PluginAPI] Plugins directory not found: {directory}")
return []
# Add plugins directory to path
if str(plugins_dir.absolute()) not in sys.path:
sys.path.insert(0, str(plugins_dir.absolute()))
loaded = []
for file_path in plugins_dir.glob("*.py"):
if file_path.name.startswith("_"):
continue
try:
module_name = file_path.stem
spec = importlib.util.spec_from_file_location(module_name, file_path)
if spec and spec.loader:
module = importlib.util.module_from_spec(spec)
sys.modules[module_name] = module
spec.loader.exec_module(module)
# Find plugin classes in the module
for attr_name in dir(module):
attr = getattr(module, attr_name)
if (isinstance(attr, type) and
issubclass(attr, BasePlugin) and
attr is not BasePlugin and
not getattr(attr, '_abstract', False)):
plugin = self.load_plugin(attr)
loaded.append(plugin)
except Exception as e:
print(f"[PluginAPI] Failed to load plugin from {file_path}: {e}")
return loaded
def unload_plugin(self, name: str) -> None:
"""Unload a plugin by name."""
if name in self._plugins:
plugin = self._plugins[name]
if plugin.is_initialized():
plugin.on_stop()
del self._plugins[name]
del self._plugin_classes[name]
print(f"[PluginAPI] Unloaded plugin: {name}")
def get_plugin(self, name: str) -> Optional[BasePlugin]:
"""Get a loaded plugin by name."""
return self._plugins.get(name)
def get_all_plugins(self) -> List[BasePlugin]:
"""Get all loaded plugins."""
return list(self._plugins.values())
# Lifecycle
def start_all(self) -> None:
"""Start all loaded plugins."""
for plugin in self._plugins.values():
try:
plugin.on_start()
plugin._initialized = True
print(f"[PluginAPI] Started plugin: {plugin.name}")
except Exception as e:
print(f"[PluginAPI] Failed to start plugin {plugin.name}: {e}")
def stop_all(self) -> None:
"""Stop all loaded plugins."""
for plugin in self._plugins.values():
try:
plugin.on_stop()
plugin._initialized = False
print(f"[PluginAPI] Stopped plugin: {plugin.name}")
except Exception as e:
print(f"[PluginAPI] Error stopping plugin {plugin.name}: {e}")
# Stop clipboard monitoring if active
if self._clipboard_manager and self._clipboard_manager.is_monitoring():
self._clipboard_manager.stop_monitoring()
def reload_plugin(self, name: str) -> Optional[BasePlugin]:
"""Reload a plugin by name."""
if name not in self._plugin_classes:
return None
self.unload_plugin(name)
# Reload module
plugin_class = self._plugin_classes[name]
module = sys.modules.get(plugin_class.__module__)
if module:
importlib.reload(module)
plugin_class = getattr(module, plugin_class.__name__)
return self.load_plugin(plugin_class)
# Utilities
def get_plugin_info(self) -> List[Dict[str, Any]]:
"""Get information about all loaded plugins."""
return [
{
'name': p.name,
'description': p.description,
'version': p.version,
'author': p.author,
'initialized': p.is_initialized()
}
for p in self._plugins.values()
]
def broadcast(self, method_name: str, *args, **kwargs) -> Dict[str, Any]:
"""
Call a method on all plugins and collect results.
Args:
method_name: Name of the method to call
*args, **kwargs: Arguments to pass to the method
Returns:
Dictionary mapping plugin names to results
"""
results = {}
for name, plugin in self._plugins.items():
method = getattr(plugin, method_name, None)
if callable(method):
try:
results[name] = method(*args, **kwargs)
except Exception as e:
results[name] = e
return results