import argparse import os import re import sys from platypush.bus import Bus from platypush.config import Config from platypush.message.request import Request from platypush.message.response import Response from platypush.utils import init_backends, set_timeout, clear_timeout class Pusher(object): """ Main class to send messages and events to a node """ """ Configuration file path """ config_file = None """ Default backend name """ backend = None """ Pusher local bus. The response will be processed here """ bus = None """ Configured backends as a name => object map """ backends = {} """ Default response_wait timeout """ default_response_wait_timeout = 5 def __init__(self, config_file=None, backend=None, on_response=None): """ Constructor. Params: config_file -- Path to the configuration file - default: ~/.config/platypush/config.yaml or /etc/platypush/config.yaml) backend -- Name of the backend where pusher will send the request and wait for the response (kafka or pushbullet). Default: whatever is specified with pusher=true in your configuration file on_response -- Method that will be invoked upon response receipt. Takes a platypush.message.response.Response as arg. Default: print the response and exit. """ # Initialize the configuration self.config_file = config_file Config.init(config_file) self.on_response = on_response or self.default_on_response() self.backend = backend or Config.get_default_pusher_backend() self.bus = Bus() @classmethod def parse_build_args(cls, args): """ Parse the recognized options from a list of cmdline arguments """ parser = argparse.ArgumentParser() parser.add_argument('--config', '-c', dest='config', required=False, default=None, help="Configuration file path (default: " + "~/.config/platypush/config.yaml or " + "/etc/platypush/config.yaml") parser.add_argument('--target', '-t', dest='target', required=True, help="Destination of the command") parser.add_argument('--action', '-a', dest='action', required=True, help="Action to execute, as package.method") parser.add_argument('--backend', '-b', dest='backend', required=False, default=None, help="Backend to deliver the message " + "[pushbullet|kafka] (default: whatever " + "specified in your config with pusher=True)") parser.add_argument('--timeout', '-T', dest='timeout', required=False, default=cls.default_response_wait_timeout, help="The application " + "will wait for a response for this number of seconds " + "(default: " + str(cls.default_response_wait_timeout) + " seconds. " "A zero value means that the application " + " will exit without waiting for a response)") opts, args = parser.parse_known_args(args) if len(args) % 2 != 0: raise RuntimeError('Odd number of key-value options passed: {}'.format(args)) opts.args = {} for i in range(0, len(args), 2): opts.args[re.sub('^-+', '', args[i])] = args[i+1] return opts def get_backend(self, name): # Lazy init if not self.backends: self.backends = init_backends(bus=self.bus) if name not in self.backends: raise RuntimeError('No such backend configured: {}'.format(name)) return self.backends[name] def on_timeout(self): """ Default response timeout handle: raise RuntimeError """ def _f(): raise RuntimeError('Response timed out') return _f def default_on_response(self): def _f(response): print('Received response: {}'.format(response)) os._exit(0) return _f def response_wait(self, request, timeout): # Install the timeout handler set_timeout(seconds=timeout, on_timeout=self.on_timeout()) # Loop on the bus until you get a response for your request ID response_received = False while not response_received: msg = self.bus.get() response_received = ( isinstance(msg, Response) and hasattr(msg, 'id') and msg.id == request.id) if timeout: clear_timeout() self.on_response(msg) def push(self, target, action, backend=None, config_file=None, timeout=default_response_wait_timeout, **kwargs): """ Sends a message on a backend and optionally waits for an answer. Params: target -- Target node action -- Action to be executed in the form plugin.path.method (e.g. shell.exec or music.mpd.play) backend -- Name of the backend that will process the request and get the response (e.g. 'pushbullet' or 'kafka') (default: whichever backend marked as pusher=true in your config.yaml) timeout -- Response receive timeout in seconds - Pusher Default: 5 seconds - If timeout == 0 or None: Pusher exits without waiting for a response config_file -- Path to the configuration file to be used (default: ~/.config/platypush/config.yaml or /etc/platypush/config.yaml) **kwargs -- Optional key-valued arguments for the action method (e.g. cmd='echo ping' or groups="['Living Room']") """ def _timeout_hndl(signum, frame): """ Default response timeout handle: raise RuntimeError and exit """ if not backend: backend = self.backend req = Request.build({ 'target' : target, 'action' : action, 'args' : kwargs, }) b = self.get_backend(backend) b.start() b.send_request(req) if timeout: self.response_wait(request=req, timeout=timeout) def main(args=sys.argv[1:]): opts = Pusher.parse_build_args(args) pusher = Pusher(config_file=opts.config, backend=opts.backend) pusher.push(opts.target, action=opts.action, timeout=opts.timeout, **opts.args) if __name__ == '__main__': main() # vim:sw=4:ts=4:et: