""" 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