From bbb8bd90204a8bd42a31322d2203ea458fc1e550 Mon Sep 17 00:00:00 2001 From: Fabio Manganiello Date: Fri, 3 Nov 2017 04:08:47 +0100 Subject: [PATCH] Major rewrite for more modularity and maintanability --- lib/plugins/__init__.py | 26 +++++++++++ lib/plugins/music/__init__.py | 28 ++++++++++++ lib/plugins/music/mpd/__init__.py | 23 ++++++++++ lib/plugins/music/mpd/config.yaml | 6 +++ lib/plugins/shell/__init__.py | 2 +- notifier.py | 75 +++++++++++++++++++++---------- 6 files changed, 136 insertions(+), 24 deletions(-) create mode 100644 lib/plugins/music/__init__.py create mode 100644 lib/plugins/music/mpd/__init__.py create mode 100644 lib/plugins/music/mpd/config.yaml diff --git a/lib/plugins/__init__.py b/lib/plugins/__init__.py index ae15b74f6..1025a851b 100644 --- a/lib/plugins/__init__.py +++ b/lib/plugins/__init__.py @@ -1,4 +1,30 @@ +import os +import sys +import yaml + class Plugin(object): + def __init__(self): + for cls in reversed(self.__class__.mro()): + if cls is not object: + try: + cls._init(self) + except AttributeError as e: + pass + + + def _init(self): + module_dir = os.path.dirname(sys.modules[self.__module__].__file__) + config_file = module_dir + os.sep + 'config.yaml' + + config = {} + + try: + with open(config_file, 'r') as f: + self.config = yaml.load(f) + except FileNotFoundError as e: + pass + + def run(self, args): raise NotImplementedError() diff --git a/lib/plugins/music/__init__.py b/lib/plugins/music/__init__.py new file mode 100644 index 000000000..27d37437d --- /dev/null +++ b/lib/plugins/music/__init__.py @@ -0,0 +1,28 @@ +from .. import Plugin + +class MusicPlugin(Plugin): + def run(self, args): + if 'play' in args and self.status()['state'] != 'play': + self.play() + elif 'pause' in args and self.status()['state'] != 'pause': + self.pause() + elif 'stop' in args: + self.stop() + + return self.status() + + def play(self): + raise NotImplementedError() + + def pause(self): + raise NotImplementedError() + + def stop(self): + raise NotImplementedError() + + def status(self): + raise NotImplementedError() + + +# vim:sw=4:ts=4:et: + diff --git a/lib/plugins/music/mpd/__init__.py b/lib/plugins/music/mpd/__init__.py new file mode 100644 index 000000000..60c5b3fb1 --- /dev/null +++ b/lib/plugins/music/mpd/__init__.py @@ -0,0 +1,23 @@ +import mpd + +from .. import MusicPlugin + +class MusicMpdPlugin(MusicPlugin): + def _init(self): + self.client = mpd.MPDClient(use_unicode=True) + self.client.connect(self.config['host'], self.config['port']) + + def play(self): + self.client.play() + + def pause(self): + self.client.pause() + + def stop(self): + self.client.stop() + + def status(self): + return self.client.status() + +# vim:sw=4:ts=4:et: + diff --git a/lib/plugins/music/mpd/config.yaml b/lib/plugins/music/mpd/config.yaml new file mode 100644 index 000000000..0c51fba5b --- /dev/null +++ b/lib/plugins/music/mpd/config.yaml @@ -0,0 +1,6 @@ +host: localhost +port: 6600 + +_deps: + - python-mpd2 + diff --git a/lib/plugins/shell/__init__.py b/lib/plugins/shell/__init__.py index e53b9a231..8143208c2 100644 --- a/lib/plugins/shell/__init__.py +++ b/lib/plugins/shell/__init__.py @@ -18,7 +18,7 @@ class ShellPlugin(Plugin): except subprocess.CalledProcessError as e: error = e.output - return output, error + return [output, error] # vim:sw=4:ts=4:et: diff --git a/notifier.py b/notifier.py index e1dae2ac5..c622a4958 100644 --- a/notifier.py +++ b/notifier.py @@ -50,38 +50,67 @@ def on_error(ws, error): logging.error(error) -def _exec_func(body): - module_name = 'plugins.{}'.format(body['plugin']) - if module_name in modules: - module = modules[module_name] - else: +def _init_plugin(plugin, reload=False): + module_name = 'plugins.{}'.format(plugin) + if module_name not in modules or reload: try: - module = importlib.import_module(module_name) - modules[module_name] = module + modules[module_name] = importlib.import_module(module_name) except ModuleNotFoundError as e: - logging.warn('No such plugin: {}'.format(body['plugin'])) - return + logging.warn('No such plugin: {}'.format(plugin)) + raise RuntimeError(e) - logging.info('Received push addressed to me: {}'.format(body)) - - args = body['args'] if 'args' in body else {} cls_name = functools.reduce( lambda a,b: a.title() + b.title(), - (body['plugin'].title().split('.')) + (plugin.title().split('.')) ) + 'Plugin' - if cls_name in plugins: - instance = plugins[cls_name] - else: - cls = getattr(module, cls_name) - instance = cls() - plugins[cls_name] = cls + if cls_name not in plugins or reload: + try: + plugins[cls_name] = getattr(modules[module_name], cls_name)() + except AttributeError as e: + logging.warn('No such class in {}: {}'.format( + module_name, cls_name)) + raise RuntimeError(e) - out, err = instance.run(args) + return plugins[cls_name] - logging.info('Command output: {}'.format(out)) - if err: - logging.warn('Command error: {}'.format(err)) + +def _exec_func(body, retry=True): + try: + logging.info('Received push addressed to me: {}'.format(body)) + args = body['args'] if 'args' in body else {} + if 'plugin' not in body: + logging.warn('No plugin specified') + return + + plugin_name = body['plugin'] + + try: + plugin = _init_plugin(plugin_name) + except RuntimeError as e: # Module/class not found + return + + ret = plugin.run(args) + out = None + err = None + + if isinstance(ret, list): + out = ret[0] + err = ret[1] if len(ret) > 1 else None + elif ret is not None: + out = ret + + if out: + logging.info('Command output: {}'.format(out)) + + if err: + logging.warn('Command error: {}'.format(err)) + except Exception as e: + logging.exception(e) + if retry: + logging.info('Reloading plugin {} and retrying'.format(plugin_name)) + _init_plugin(plugin_name, reload=True) + _exec_func(body, retry=False) def _on_push(ws, data):