Skip to content

Commit

Permalink
PluginManager: retry loading trayicon plugin when service is missing
Browse files Browse the repository at this point in the history
  • Loading branch information
deltragon committed Aug 21, 2024
1 parent f9dcb56 commit 6edb343
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 16 deletions.
1 change: 1 addition & 0 deletions safeeyes/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,7 @@ def build(cls, name, icon_path, icon_id, action):
class PluginDependency:
message: str
link: str|None = None
retryable: bool = False

class RequiredPluginException(Exception):
def __init__(self, plugin_id, plugin_name: str, message: str|PluginDependency):
Expand Down
60 changes: 55 additions & 5 deletions safeeyes/plugin_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,13 @@
import sys

from safeeyes import utility
from safeeyes.model import RequiredPluginException
from safeeyes.model import PluginDependency, RequiredPluginException

sys.path.append(os.path.abspath(utility.SYSTEM_PLUGINS_DIR))
sys.path.append(os.path.abspath(utility.USER_PLUGINS_DIR))

HORIZONTAL_LINE_LENGTH = 64


class PluginManager:
"""
Imports the Safe Eyes plugins and calls the methods defined in those plugins.
Expand Down Expand Up @@ -99,6 +98,27 @@ def init(self, context, config):
plugin.init_plugin(context, config)
return True

def needs_retry(self):
return self.get_retryable_error() is not None

def get_retryable_error(self):
for plugin in self.__plugins.values():
if plugin.required_plugin and plugin.errored and plugin.enabled:
if isinstance(plugin.last_error, PluginDependency) and plugin.last_error.retryable:
return RequiredPluginException(
plugin.id,
plugin.get_name(),
plugin.last_error
)

return None

def retry_errored_plugins(self):
for plugin in self.__plugins.values():
if plugin.required_plugin and plugin.errored and plugin.enabled:
if isinstance(plugin.last_error, PluginDependency) and plugin.last_error.retryable:
plugin.reload_errored()

def start(self):
"""
Execute the on_start() function of plugins.
Expand Down Expand Up @@ -235,7 +255,8 @@ def __init__(self, plugin):

if message:
self.errored = True
if self.required_plugin:
self.last_error = message
if self.required_plugin and not (isinstance(message, PluginDependency) and message.retryable):
raise RequiredPluginException(
plugin['id'],
plugin_config['meta']['name'],
Expand All @@ -262,9 +283,9 @@ def reload_config(self, plugin):
if self.enabled or self.break_override_allowed:
plugin_path = os.path.join(self.plugin_dir, self.id)
message = utility.check_plugin_dependencies(
plugin['id'],
self.id,
self.plugin_config,
plugin.get('settings', {}),
self.config,
plugin_path
)

Expand All @@ -279,6 +300,35 @@ def reload_config(self, plugin):
# No longer errored, import the module now
self._import_plugin()


def reload_errored(self):
if not self.errored:
return

if self.enabled or self.break_override_allowed:
plugin_path = os.path.join(self.plugin_dir, self.id)
message = utility.check_plugin_dependencies(
self.id,
self.plugin_config,
self.config,
plugin_path
)

if message:
self.errored = True
self.last_error = message
elif self.errored:
self.errored = False
self.last_error = None

if not self.errored and self.module is None:
# No longer errored, import the module now
self._import_plugin()


def get_name(self):
return self.plugin_config['meta']['name']

def _import_plugin(self):
if self.errored:
# do not try to import errored plugin
Expand Down
3 changes: 2 additions & 1 deletion safeeyes/plugins/trayicon/dependency_checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ def validate(plugin_config, plugin_settings):
else:
return PluginDependency(
message=_("Please install service providing tray icons for your desktop environment."),
link="https://github.com/slgobinath/SafeEyes/wiki/How-to-install-backend-for-Safe-Eyes-tray-icon"
link="https://github.com/slgobinath/SafeEyes/wiki/How-to-install-backend-for-Safe-Eyes-tray-icon",
retryable=True
)

command = None
Expand Down
50 changes: 40 additions & 10 deletions safeeyes/safeeyes.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
from safeeyes.ui.settings_dialog import SettingsDialog

gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gio
from gi.repository import Gtk, Gio, GLib

SAFE_EYES_VERSION = "2.2.2"

Expand All @@ -48,6 +48,7 @@ class SafeEyes(Gtk.Application):
"""

required_plugin_dialog_active = False
retry_errored_plugins_count = 0

def __init__(self, system_locale, config, cli_args):
super().__init__(
Expand Down Expand Up @@ -119,8 +120,7 @@ def do_startup(self):
try:
self.plugins_manager.init(self.context, self.config)
except RequiredPluginException as e:
self.show_required_plugin_dialog(e.get_plugin_id(), e.get_plugin_name(), e.get_message())
self.required_plugin_dialog_active = True
self.show_required_plugin_dialog(e)

self.hold()

Expand All @@ -129,7 +129,7 @@ def do_startup(self):
if self.config.get('use_rpc_server', True):
self.__start_rpc_server()

if not self.required_plugin_dialog_active and self.safe_eyes_core.has_breaks():
if not self.plugins_manager.needs_retry() and not self.required_plugin_dialog_active and self.safe_eyes_core.has_breaks():
self.active = True
self.context['state'] = State.START
self.plugins_manager.start() # Call the start method of all plugins
Expand All @@ -139,6 +139,9 @@ def do_startup(self):
def do_activate(self):
logging.info('Application activated')

if self.plugins_manager.needs_retry():
GLib.timeout_add_seconds(1, self._retry_errored_plugins)

if self.cli_args.about:
self.show_about()
elif self.cli_args.disable:
Expand All @@ -150,6 +153,32 @@ def do_activate(self):
elif self.cli_args.take_break:
self.take_break()


def _retry_errored_plugins(self):
if not self.plugins_manager.needs_retry():
return

logging.info(f"Retry loading errored plugin")
self.plugins_manager.retry_errored_plugins()

error = self.plugins_manager.get_retryable_error()

if error is None:
# success
self.restart(self.config, set_active=True)
return

# errored again
if self.retry_errored_plugins_count >= 3:
self.show_required_plugin_dialog(error)
return

timeout = pow(2, self.retry_errored_plugins_count)
self.retry_errored_plugins_count += 1

GLib.timeout_add_seconds(timeout, self._retry_errored_plugins)


def show_settings(self):
"""
Listen to tray icon Settings action and send the signal to Settings dialog.
Expand All @@ -161,12 +190,14 @@ def show_settings(self):
self.config.clone(), self.save_settings)
settings_dialog.show()

def show_required_plugin_dialog(self, plugin_id, plugin_name, message):
def show_required_plugin_dialog(self, error: RequiredPluginException):
self.required_plugin_dialog_active = True

logging.info("Show RequiredPlugin dialog")
dialog = RequiredPluginDialog(
plugin_id,
plugin_name,
message,
error.get_plugin_id(),
error.get_plugin_name(),
error.get_message(),
self.quit,
lambda: self.disable_plugin(plugin_id)
)
Expand Down Expand Up @@ -306,8 +337,7 @@ def restart(self, config, set_active=False):
try:
self.plugins_manager.init(self.context, self.config)
except RequiredPluginException as e:
self.show_required_plugin_dialog(e.get_plugin_id(), e.get_plugin_name(), e.get_message())
self.required_plugin_dialog_active = True
self.show_required_plugin_dialog(e)
return

if set_active:
Expand Down

0 comments on commit 6edb343

Please sign in to comment.