✨ feat(launcher, plugin_manager): add lazy loading support for plugins with trigger mapping

This commit is contained in:
sankalp committed 2025-06-29 12:34:09 +05:45
1 parent 3ec4c6229d
commit 24313ecdb6
4 files changed
+126 -1277

No files matched your search

+79 -34
View File
@@ -8,22 +8,23 @@ from modules.launcher.plugin_base import PluginBase
class PluginManager:
"""
Manages launcher plugins.
Manages launcher plugins with lazy loading support.
"""
def __init__(self):
self.plugins: Dict[str, PluginBase] = {}
self.plugin_classes: Dict[str, Type[PluginBase]] = {}
self.active_plugins: List[str] = []
self.plugins: Dict[str, PluginBase] = {} # Actually loaded plugin instances
self.plugin_classes: Dict[str, Type[PluginBase]] = {} # Available plugin classes
self.active_plugins: List[str] = [] # List of loaded plugin names
self.trigger_map: Dict[str, str] = {} # Maps triggers to plugin names
# Load built-in plugins
# Load built-in plugins (discover classes only)
self._load_builtin_plugins()
# Load external plugins
# Load external plugins (discover classes only)
self._load_external_plugins()
# Activate default plugins
self._activate_default_plugins()
# Build trigger mapping without activating plugins
self._build_trigger_map()
def _load_builtin_plugins(self):
"""Load built-in plugins from the plugins directory."""
@@ -74,35 +75,42 @@ class PluginManager:
except Exception as e:
print(f"Failed to load plugin {plugin_name}: {e}")
def _activate_default_plugins(self):
"""Activate default plugins."""
default_plugins = [
"applications",
"calculator",
"system",
"clipboard",
"power",
"caffeine",
"screencapture",
"emoji",
"wallpaper",
"websearch",
"kanban",
"calendar",
"reminders",
"network",
"bluetooth",
"otp",
"password",
"bookmarks",
"process",
]
def _build_trigger_map(self):
"""Build a mapping of triggers to plugin names without loading plugins."""
for plugin_name, plugin_class in self.plugin_classes.items():
try:
# Get triggers from class without instantiating
triggers = self._get_class_triggers(plugin_class)
for trigger in triggers:
self.trigger_map[trigger.lower()] = plugin_name
except Exception as e:
print(f"Failed to get triggers for plugin {plugin_name}: {e}")
for plugin_name in default_plugins:
self.activate_plugin(plugin_name)
def _get_class_triggers(self, plugin_class: Type[PluginBase]) -> List[str]:
"""Get triggers from a plugin class without full initialization."""
# Check if the plugin class has a class-level triggers attribute
if hasattr(plugin_class, 'TRIGGERS'):
return plugin_class.TRIGGERS
# Fallback: create a minimal instance to get triggers
try:
temp_instance = plugin_class()
# Try to get triggers without full initialization if possible
if hasattr(temp_instance, '_default_triggers'):
triggers = temp_instance._default_triggers
else:
# Last resort: minimal initialization
temp_instance.initialize()
triggers = temp_instance.get_triggers()
temp_instance.cleanup()
return triggers
except Exception as e:
print(f"Failed to get triggers for {plugin_class.__name__}: {e}")
# Fallback: return empty list if we can't get triggers
return []
def activate_plugin(self, plugin_name: str) -> bool:
"""Activate a plugin by name."""
"""Activate a plugin by name (lazy loading)."""
if plugin_name in self.plugins:
# Already activated
return True
@@ -122,6 +130,7 @@ class PluginManager:
self.plugins[plugin_name] = plugin_instance
self.active_plugins.append(plugin_name)
print(f"Lazy loaded plugin: {plugin_name}")
return True
except Exception as e:
@@ -155,6 +164,42 @@ class PluginManager:
self.plugins[name] for name in self.active_plugins if name in self.plugins
]
def get_plugin_for_trigger(self, trigger: str) -> PluginBase:
"""Get plugin instance for a trigger, loading it if necessary."""
trigger_lower = trigger.lower()
# Check if we have a plugin for this trigger
plugin_name = self.trigger_map.get(trigger_lower)
if not plugin_name:
return None
# Load the plugin if not already loaded
if plugin_name not in self.plugins:
if not self.activate_plugin(plugin_name):
return None
return self.plugins.get(plugin_name)
def find_trigger_plugin(self, query: str) -> tuple:
"""Find plugin and trigger for a query without loading the plugin."""
query_lower = query.lower().strip()
# Sort triggers by length (longest first) to match more specific triggers first
sorted_triggers = sorted(self.trigger_map.items(), key=lambda x: len(x[0]), reverse=True)
for trigger, plugin_name in sorted_triggers:
trigger_lower = trigger.lower()
# Exact match with trigger
if query_lower == trigger_lower:
return plugin_name, trigger
# Match trigger followed by space
if query_lower.startswith(trigger_lower + " "):
return plugin_name, trigger
return None, ""
def get_plugin_names(self) -> List[str]:
"""Get list of available plugin names."""
return list(self.plugin_classes.keys())