platypush/platypush/__init__.py

229 lines
6.1 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 traceback
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
from .message.response import Response
2017-10-29 05:17:42 +01:00
2017-11-03 01:56:27 +01:00
__author__ = 'Fabio Manganiello <info@fabiomanganiello.com>'
2017-12-16 05:00:29 +01:00
__version__ = '0.3.3'
2017-11-03 01:56:27 +01:00
#-----------#
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-12-16 07:01:25 +01:00
def _exec_func(args, retry=True):
backend = args.pop('backend') if 'backend' in args else None
origin = args.pop('origin') if 'origin' in args else None
2017-11-09 01:43:17 +01:00
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:
response = plugin.run(method=method_name, **args)
if response and response.is_error():
logging.warn('Response processed with errors: {}'.format(response))
else:
logging.info('Processed response: {}'.format(response))
except Exception as e:
response = Response(output=None, errors=[e, traceback.format_exc()])
logging.exception(e)
if retry:
# Put the popped args back where they were before retrying
2017-12-16 07:01:25 +01:00
args['action'] = action; args['origin'] = origin; args['backend'] = backend
logging.info('Reloading plugin {} and retrying'.format(module_name))
_init_plugin(module_name, reload=True)
2017-12-16 07:01:25 +01:00
_exec_func(args, retry=False)
finally:
if backend: backend.send_response(origin, response)
2017-12-16 07:01:25 +01:00
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]
if 'logging' not in config:
config['logging'] = logging.INFO
else:
config['logging'] = getattr(logging, config['logging'].upper())
if 'device_id' not in config:
config['device_id'] = socket.gethostname()
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])
name = '.'.join((k.split('.'))[1:])
backends[name] = b
except AttributeError as e:
logging.warn('No such class in {}: {}'.format(
module.__name__, cls_name))
raise RuntimeError(e)
return backends
def get_default_pusher_backend(config):
backends = ['.'.join((k.split('.'))[1:])
for k in config.keys() if k.startswith('backend.')
and 'pusher' in config[k] and config[k]['pusher'] is True]
return backends[0] if backends else None
def get_logging_level():
global config
return config['logging']
def get_device_id():
global config
return config['device_id'] if 'device_id' in config else None
2017-11-03 15:06:29 +01:00
def main():
print('Starting platypush v.{}'.format(__version__))
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)
if debug: config['logging'] = logging.DEBUG
2017-11-03 01:56:27 +01:00
logging.basicConfig(level=get_logging_level(), stream=sys.stdout)
logging.debug('Configuration dump: {}'.format(config))
mq = Queue()
backends = get_backends(config)
2017-10-29 05:17:42 +01:00
for backend in backends.values():
backend.mq = mq
backend.start()
while True:
try:
2017-12-16 07:01:25 +01:00
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: