platypush/platypush/__init__.py

214 lines
5.3 KiB
Python
Raw Normal View History

import functools
import importlib
2017-10-29 05:17:42 +01:00
import os
import logging
import json
import socket
import sys
import websocket
2017-11-03 01:56:27 +01:00
import yaml
2017-10-29 05:17:42 +01:00
from queue import Queue
from threading import Thread
from getopt import getopt
2017-10-29 05:17:42 +01:00
2017-11-03 01:56:27 +01:00
__author__ = 'Fabio Manganiello <info@fabiomanganiello.com>'
#-----------#
2017-11-03 15:06:29 +01:00
config = {}
modules = {}
wrkdir = os.path.dirname(os.path.realpath(__file__))
2017-11-03 02:42:56 +01:00
2017-10-29 05:17:42 +01:00
def _init_plugin(plugin_name, reload=False):
global modules
global config
if plugin_name in modules and not reload:
return modules[plugin_name]
try:
module = importlib.import_module(__package__ + '.plugins.' + plugin_name)
except ModuleNotFoundError as e:
logging.warn('No such plugin: {}'.format(plugin_name))
raise RuntimeError(e)
2017-11-03 04:36:24 +01:00
# e.g. plugins.music.mpd main class: MusicMpdPlugin
2017-11-03 02:42:56 +01:00
cls_name = functools.reduce(
lambda a,b: a.title() + b.title(),
(plugin_name.title().split('.'))
2017-11-03 02:42:56 +01:00
) + 'Plugin'
plugin_conf = config[plugin_name] if plugin_name in config else {}
2017-11-03 15:06:29 +01:00
try:
plugin = getattr(module, cls_name)(plugin_conf)
modules[plugin_name] = plugin
except AttributeError as e:
logging.warn('No such class in {}: {}'.format(
plugin_name, cls_name))
raise RuntimeError(e)
return plugin
2017-11-03 02:42:56 +01:00
2017-11-09 01:43:17 +01:00
def _exec_func(args, retry=True):
action = args.pop('action')
2017-11-05 15:18:46 +01:00
tokens = action.split('.')
module_name = str.join('.', tokens[:-1])
method_name = tokens[-1:][0]
2017-11-03 16:48:48 +01:00
try:
plugin = _init_plugin(module_name)
except RuntimeError as e: # Module/class not found
logging.exception(e)
return
try:
ret = plugin.run(method=method_name, **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:
# Put the action back where it was before retrying
2017-11-09 01:43:17 +01:00
args['action'] = action
logging.info('Reloading plugin {} and retrying'.format(module_name))
_init_plugin(module_name, reload=True)
2017-11-09 01:43:17 +01:00
_exec_func(args, retry=False)
def on_msg(msg):
Thread(target=_exec_func, args=(msg,)).start()
2017-11-03 15:06:29 +01:00
def parse_config_file(config_file=None):
global config
if config_file:
locations = [config_file]
else:
locations = [
# ./config.yaml
os.path.join(wrkdir, 'config.yaml'),
2017-12-11 20:30:57 +01:00
# ~/.config/platypush/config.yaml
os.path.join(os.environ['HOME'], '.config', 'platypush', 'config.yaml'),
# /etc/platypush/config.yaml
os.path.join(os.sep, 'etc', 'platypush', 'config.yaml'),
2017-11-03 15:06:29 +01:00
]
for loc in locations:
try:
with open(loc,'r') as f:
config = yaml.load(f)
except FileNotFoundError as e:
pass
for section in config:
if 'disabled' in config[section] and config[section]['disabled']:
del config[section]
2017-11-03 15:06:29 +01:00
return config
2017-11-03 12:01:20 +01:00
2017-11-03 15:06:29 +01:00
def get_backends(config):
backends = []
for k in config.keys():
if k.startswith('backend.'):
module = importlib.import_module(__package__ + '.' + k)
# e.g. backend.pushbullet main class: PushbulletBackend
cls_name = functools.reduce(
lambda a,b: a.title() + b.title(),
(module.__name__.title().split('.')[2:])
) + 'Backend'
# Ignore the pusher attribute here
if 'pusher' in config[k]: del config[k]['pusher']
try:
b = getattr(module, cls_name)(config[k])
backends.append(b)
except AttributeError as e:
logging.warn('No such class in {}: {}'.format(
module.__name__, cls_name))
raise RuntimeError(e)
return backends
def get_device_id():
global config
return config['device_id']
2017-11-03 15:06:29 +01:00
def main():
2017-11-03 01:56:27 +01:00
DEBUG = False
2017-11-03 15:06:29 +01:00
config_file = None
plugins_dir = os.path.join(wrkdir, 'plugins')
sys.path.insert(0, plugins_dir)
optlist, args = getopt(sys.argv[1:], 'vh')
for opt, arg in optlist:
2017-11-03 01:56:27 +01:00
if opt == '-c':
config_file = arg
if opt == '-v':
DEBUG = True
elif opt == '-h':
print('''
2017-11-03 01:56:27 +01:00
Usage: {} [-v] [-h] [-c <config_file>]
-v Enable debug mode
-h Show this help
2017-11-03 01:56:27 +01:00
-c Path to the configuration file (default: ./config.yaml)
'''.format(sys.argv[0]))
2017-10-30 02:59:35 +01:00
return
2017-11-03 15:06:29 +01:00
config = parse_config_file(config_file)
logging.info('Configuration dump: {}'.format(config))
2017-11-03 01:56:27 +01:00
2017-11-03 15:06:29 +01:00
if 'device_id' not in config:
config['device_id'] = socket.gethostname()
2017-11-03 01:56:27 +01:00
if 'debug' in config:
DEBUG = config['debug']
if DEBUG:
logging.basicConfig(level=logging.DEBUG)
websocket.enableTrace(True)
else:
logging.basicConfig(level=logging.INFO)
mq = Queue()
backends = get_backends(config)
2017-10-29 05:17:42 +01:00
for backend in backends:
backend.mq = mq
backend.start()
while True:
try:
on_msg(mq.get())
except KeyboardInterrupt:
return
2017-10-29 05:17:42 +01:00
if __name__ == '__main__':
main()
2017-10-29 20:45:19 +01:00
# vim:sw=4:ts=4:et: