6868import logging
6969import os
7070import sys
71+ import typing
7172
7273from safeeyes import utility
73- from safeeyes .model import Break , PluginDependency , RequiredPluginException , TrayAction
74+ from safeeyes .context import Context
75+ from safeeyes .model import (
76+ Config ,
77+ Break ,
78+ PluginDependency ,
79+ RequiredPluginException ,
80+ TrayAction ,
81+ )
7482
7583sys .path .append (os .path .abspath (utility .SYSTEM_PLUGINS_DIR ))
7684sys .path .append (os .path .abspath (utility .USER_PLUGINS_DIR ))
8189class PluginManager :
8290 """Imports the Safe Eyes plugins and calls the methods defined in those plugins."""
8391
84- def __init__ (self ):
92+ __plugins : dict [str , "LoadedPlugin" ]
93+ last_break : typing .Optional [Break ]
94+
95+ def __init__ (self ) -> None :
8596 logging .info ("Load all the plugins" )
8697 self .__plugins = {}
8798 self .last_break = None
8899 self .horizontal_line = "─" * HORIZONTAL_LINE_LENGTH
89100
90- def init (self , context , config ) :
101+ def init (self , context : Context , config : Config ) -> None :
91102 """Initialize all the plugins with init(context, safe_eyes_config,
92103 plugin_config) function.
93104 """
@@ -111,12 +122,43 @@ def init(self, context, config):
111122 # Initialize the plugins
112123 for plugin in self .__plugins .values ():
113124 plugin .init_plugin (context , config )
114- return True
115125
116- def needs_retry (self ):
126+ def reload (self , context : Context , config : Config ) -> None :
127+ """Reinitialize all the plugins with updated config."""
128+ plugin_ids : set [str ] = set ()
129+ # Load the plugins
130+ for plugin in config .get ("plugins" ):
131+ plugin_id = plugin ["id" ]
132+ plugin_ids .add (plugin_id )
133+ if plugin_id in self .__plugins :
134+ self .__plugins [plugin_id ].reload_config (plugin )
135+ else :
136+ try :
137+ loaded_plugin = LoadedPlugin (plugin )
138+ self .__plugins [loaded_plugin .id ] = loaded_plugin
139+ except BaseException as e :
140+ traceback_wanted = (
141+ logging .getLogger ().getEffectiveLevel () == logging .DEBUG
142+ )
143+ if traceback_wanted :
144+ import traceback
145+
146+ traceback .print_exc ()
147+ logging .error ("Error in loading the plugin %s: %s" , plugin ["id" ], e )
148+ continue
149+
150+ removed_plugins = set (self .__plugins .keys ()).difference (plugin_ids )
151+ for plugin_id in removed_plugins :
152+ self .__plugins [plugin_id ].disable ()
153+
154+ # Initialize the plugins
155+ for plugin in self .__plugins .values ():
156+ plugin .init_plugin (context , config )
157+
158+ def needs_retry (self ) -> bool :
117159 return self .get_retryable_error () is not None
118160
119- def get_retryable_error (self ):
161+ def get_retryable_error (self ) -> typing . Optional [ RequiredPluginException ] :
120162 for plugin in self .__plugins .values ():
121163 if plugin .required_plugin and plugin .errored and plugin .enabled :
122164 if (
@@ -129,7 +171,7 @@ def get_retryable_error(self):
129171
130172 return None
131173
132- def retry_errored_plugins (self ):
174+ def retry_errored_plugins (self ) -> None :
133175 for plugin in self .__plugins .values ():
134176 if plugin .required_plugin and plugin .errored and plugin .enabled :
135177 if (
@@ -138,32 +180,29 @@ def retry_errored_plugins(self):
138180 ):
139181 plugin .reload_errored ()
140182
141- def start (self ):
183+ def start (self ) -> None :
142184 """Execute the on_start() function of plugins."""
143185 for plugin in self .__plugins .values ():
144186 plugin .call_plugin_method ("on_start" )
145- return True
146187
147- def stop (self ):
188+ def stop (self ) -> None :
148189 """Execute the on_stop() function of plugins."""
149190 for plugin in self .__plugins .values ():
150191 plugin .call_plugin_method ("on_stop" )
151- return True
152192
153- def exit (self ):
193+ def exit (self ) -> None :
154194 """Execute the on_exit() function of plugins."""
155195 for plugin in self .__plugins .values ():
156196 plugin .call_plugin_method ("on_exit" )
157- return True
158197
159- def pre_break (self , break_obj ):
198+ def pre_break (self , break_obj ) -> bool :
160199 """Execute the on_pre_break(break_obj) function of plugins."""
161200 for plugin in self .__plugins .values ():
162201 if plugin .call_plugin_method_break_obj ("on_pre_break" , 1 , break_obj ):
163202 return False
164203 return True
165204
166- def start_break (self , break_obj ):
205+ def start_break (self , break_obj ) -> bool :
167206 """Execute the start_break(break_obj) function of plugins."""
168207 self .last_break = break_obj
169208 for plugin in self .__plugins .values ():
@@ -172,25 +211,24 @@ def start_break(self, break_obj):
172211
173212 return True
174213
175- def stop_break (self ):
214+ def stop_break (self ) -> None :
176215 """Execute the stop_break() function of plugins."""
177216 for plugin in self .__plugins .values ():
178217 plugin .call_plugin_method ("on_stop_break" )
179218
180- def countdown (self , countdown , seconds ):
219+ def countdown (self , countdown , seconds ) -> None :
181220 """Execute the on_countdown(countdown, seconds) function of plugins."""
182221 for plugin in self .__plugins .values ():
183222 plugin .call_plugin_method ("on_countdown" , 2 , countdown , seconds )
184223
185- def update_next_break (self , break_obj , break_time ):
224+ def update_next_break (self , break_obj , break_time ) -> None :
186225 """Execute the update_next_break(break_time) function of plugins."""
187226 for plugin in self .__plugins .values ():
188227 plugin .call_plugin_method_break_obj (
189228 "update_next_break" , 2 , break_obj , break_time
190229 )
191- return True
192230
193- def get_break_screen_widgets (self , break_obj ):
231+ def get_break_screen_widgets (self , break_obj ) -> str :
194232 """Return the HTML widget generated by the plugins.
195233
196234 The widget is generated by calling the get_widget_title and
@@ -246,14 +284,14 @@ class LoadedPlugin:
246284 # misc data
247285 # FIXME: rename to plugin_config to plugin_json? plugin_config and config are easy
248286 # to confuse
249- config = None
250- plugin_config = None
251- plugin_dir = None
252- module = None
253- last_error = None
254- id = None
255-
256- def __init__ (self , plugin ) :
287+ config : dict
288+ plugin_config : dict
289+ plugin_dir : str
290+ module : typing . Optional [ typing . Any ] = None
291+ last_error : typing . Optional [ typing . Union [ str , PluginDependency ]] = None
292+ id : str
293+
294+ def __init__ (self , plugin : dict ) -> None :
257295 (plugin_config , plugin_dir ) = self ._load_config_json (plugin ["id" ])
258296
259297 self .id = plugin ["id" ]
@@ -285,11 +323,10 @@ def __init__(self, plugin):
285323
286324 self ._import_plugin ()
287325
288- def reload_config (self , plugin ):
289- if self .enabled and not plugin ["enabled" ]:
290- self .enabled = False
291- if not self .errored and utility .has_method (self .module , "disable" ):
292- self .module .disable ()
326+ def reload_config (self , plugin : dict ) -> None :
327+ if not plugin ["enabled" ]:
328+ self .disable ()
329+ return
293330
294331 if not self .enabled and plugin ["enabled" ]:
295332 self .enabled = True
@@ -315,7 +352,18 @@ def reload_config(self, plugin):
315352 # No longer errored, import the module now
316353 self ._import_plugin ()
317354
318- def reload_errored (self ):
355+ def disable (self ) -> None :
356+ if self .enabled :
357+ self .enabled = False
358+ if (
359+ not self .errored
360+ and self .module is not None
361+ and utility .has_method (self .module , "disable" )
362+ ):
363+ self .module .disable ()
364+ logging .info ("Successfully unloaded the plugin '%s'" , self .id )
365+
366+ def reload_errored (self ) -> None :
319367 if not self .errored :
320368 return
321369
@@ -336,10 +384,10 @@ def reload_errored(self):
336384 # No longer errored, import the module now
337385 self ._import_plugin ()
338386
339- def get_name (self ):
387+ def get_name (self ) -> str :
340388 return self .plugin_config ["meta" ]["name" ]
341389
342- def _import_plugin (self ):
390+ def _import_plugin (self ) -> None :
343391 if self .errored :
344392 # do not try to import errored plugin
345393 return
@@ -350,7 +398,7 @@ def _import_plugin(self):
350398 if utility .has_method (self .module , "enable" ):
351399 self .module .enable ()
352400
353- def _load_config_json (self , plugin_id ) :
401+ def _load_config_json (self , plugin_id : str ) -> typing . Tuple [ dict , str ] :
354402 # Look for plugin.py
355403 if os .path .isfile (
356404 os .path .join (utility .SYSTEM_PLUGINS_DIR , plugin_id , "plugin.py" )
@@ -373,16 +421,16 @@ def _load_config_json(self, plugin_id):
373421
374422 return (plugin_config , plugin_dir )
375423
376- def init_plugin (self , context , safeeyes_config ) :
424+ def init_plugin (self , context : Context , safeeyes_config : Config ) -> None :
377425 if self .errored :
378426 return
379427 if self .break_override_allowed or self .enabled :
380- if utility .has_method (self .module , "init" , 3 ):
428+ if self . module is not None and utility .has_method (self .module , "init" , 3 ):
381429 self .module .init (context , safeeyes_config , self .config )
382430
383431 def call_plugin_method_break_obj (
384432 self , method_name : str , num_args , break_obj , * args , ** kwargs
385- ):
433+ ) -> typing . Any :
386434 if self .errored :
387435 return None
388436
@@ -399,7 +447,9 @@ def call_plugin_method_break_obj(
399447
400448 return None
401449
402- def call_plugin_method (self , method_name : str , num_args = 0 , * args , ** kwargs ):
450+ def call_plugin_method (
451+ self , method_name : str , num_args = 0 , * args , ** kwargs
452+ ) -> typing .Any :
403453 if self .errored :
404454 return None
405455
@@ -412,7 +462,7 @@ def call_plugin_method(self, method_name: str, num_args=0, *args, **kwargs):
412462
413463 def _call_plugin_method_internal (
414464 self , method_name : str , num_args = 0 , * args , ** kwargs
415- ):
465+ ) -> typing . Any :
416466 # FIXME: cache if method exists
417467 if utility .has_method (self .module , method_name , num_args ):
418468 return getattr (self .module , method_name )(* args , ** kwargs )
0 commit comments