Mercurial > libervia-backend
view frontends/src/jp/cmd_pubsub.py @ 2308:0b21d87c91cf
jp (pubsub/hook): added create/delete/list hook command to handle new Pubsub hook feature
author | Goffi <goffi@goffi.org> |
---|---|
date | Wed, 05 Jul 2017 15:05:49 +0200 |
parents | bd4d8c73b1d3 |
children | 7b448ac50a69 |
line wrap: on
line source
#!/usr/bin/env python2 # -*- coding: utf-8 -*- # jp: a SàT command line tool # Copyright (C) 2009-2016 Jérôme Poisson (goffi@goffi.org) # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. import base from sat.core.i18n import _ from sat_frontends.jp.constants import Const as C from sat_frontends.jp import common from functools import partial from sat.tools.common import uri from sat_frontends.tools import jid import os.path __commands__ = ["Pubsub"] PUBSUB_TMP_DIR = u"pubsub" # TODO: need to split this class in several modules, plugin should handle subcommands class NodeInfo(base.CommandBase): def __init__(self, host): base.CommandBase.__init__(self, host, 'info', use_output=C.OUTPUT_DICT, use_pubsub_node_req=True, help=_(u'retrieve node configuration')) self.need_loop=True def add_parser_options(self): self.parser.add_argument("-k", "--key", type=base.unicode_decoder, action='append', dest='keys', help=_(u"data key to filter")) def removePrefix(self, key): return key[7:] if key.startswith(u"pubsub#") else key def filterKey(self, key): return any((key == k or key == u'pubsub#' + k) for k in self.args.keys) def psNodeConfigurationGetCb(self, config_dict): key_filter = (lambda k: True) if not self.args.keys else self.filterKey config_dict = {self.removePrefix(k):v for k,v in config_dict.iteritems() if key_filter(k)} self.output(config_dict) self.host.quit() def psNodeConfigurationGetEb(self, failure_): self.disp(u"can't get node configuration: {reason}".format( reason=failure_), error=True) self.host.quit(C.EXIT_BRIDGE_ERRBACK) def start(self): common.checkURI(self.args) self.host.bridge.psNodeConfigurationGet( self.args.service, self.args.node, self.profile, callback=self.psNodeConfigurationGetCb, errback=self.psNodeConfigurationGetEb) class NodeCreate(base.CommandBase): def __init__(self, host): base.CommandBase.__init__(self, host, 'create', use_output=C.OUTPUT_DICT, use_pubsub_node_req=True, use_verbose=True, help=_(u'create a node')) self.need_loop=True def add_parser_options(self): self.parser.add_argument("-f", "--field", type=base.unicode_decoder, action='append', nargs=2, dest='fields', default={}, metavar=(u"KEY", u"VALUE"), help=_(u"configuration field to set")) self.parser.add_argument("-F", "--full-prefix", action="store_true", help=_(u"don't prepend \"pubsub#\" prefix to field names")) def psNodeCreateCb(self, node_id): if self.host.verbosity: announce = _(u'node created successfully: ') else: announce = u'' self.disp(announce + node_id) self.host.quit() def psNodeCreateEb(self, failure_): self.disp(u"can't create: {reason}".format( reason=failure_), error=True) self.host.quit(C.EXIT_BRIDGE_ERRBACK) def start(self): if not self.args.full_prefix: options = {u'pubsub#' + k: v for k,v in self.args.fields} else: options = dict(self.args.fields) self.host.bridge.psNodeCreate( self.args.service, self.args.node, options, self.profile, callback=self.psNodeCreateCb, errback=partial(self.errback, msg=_(u"can't create node: {}"), exit_code=C.EXIT_BRIDGE_ERRBACK)) class NodeDelete(base.CommandBase): def __init__(self, host): base.CommandBase.__init__(self, host, 'delete', use_pubsub_node_req=True, help=_(u'delete a node')) self.need_loop=True def add_parser_options(self): self.parser.add_argument('-f', '--force', action='store_true', help=_(u'delete node without confirmation')) def psNodeDeleteCb(self): self.disp(_(u'node deleted successfully')) self.host.quit() def start(self): if not self.args.force: if not self.args.service: message = _(u"Are you sure to delete pep node [{node_id}] ?").format( node_id=self.args.node) else: message = _(u"Are you sure to delete node [{node_id}] on service [{service}] ?").format( node_id=self.args.node, service=self.args.service) res = raw_input("{} (y/N)? ".format(message)) if res not in ("y", "Y"): self.disp(_(u"node deletion cancelled")) self.host.quit(2) self.host.bridge.psNodeDelete( self.args.service, self.args.node, self.profile, callback=self.psNodeDeleteCb, errback=partial(self.errback, msg=_(u"can't delete node: {}"), exit_code=C.EXIT_BRIDGE_ERRBACK)) class NodeSet(base.CommandBase): def __init__(self, host): base.CommandBase.__init__(self, host, 'set', use_output=C.OUTPUT_DICT, use_pubsub_node_req=True, use_verbose=True, help=_(u'set node configuration')) self.need_loop=True def add_parser_options(self): self.parser.add_argument("-f", "--field", type=base.unicode_decoder, action='append', nargs=2, dest='fields', required=True, metavar=(u"KEY", u"VALUE"), help=_(u"configuration field to set (required)")) def psNodeConfigurationSetCb(self): self.disp(_(u'node configuration successful'), 1) self.host.quit() def psNodeConfigurationSetEb(self, failure_): self.disp(u"can't set node configuration: {reason}".format( reason=failure_), error=True) self.host.quit(C.EXIT_BRIDGE_ERRBACK) def getKeyName(self, k): if not k.startswith(u'pubsub#'): return u'pubsub#' + k else: return k def start(self): common.checkURI(self.args) self.host.bridge.psNodeConfigurationSet( self.args.service, self.args.node, {self.getKeyName(k): v for k,v in self.args.fields}, self.profile, callback=self.psNodeConfigurationSetCb, errback=self.psNodeConfigurationSetEb) class NodeAffiliationsGet(base.CommandBase): def __init__(self, host): base.CommandBase.__init__(self, host, 'get', use_output=C.OUTPUT_DICT, use_pubsub_node_req=True, help=_(u'retrieve node affiliations (for node owner)')) self.need_loop=True def add_parser_options(self): pass def psNodeAffiliationsGetCb(self, affiliations): self.output(affiliations) self.host.quit() def psNodeAffiliationsGetEb(self, failure_): self.disp(u"can't get node affiliations: {reason}".format( reason=failure_), error=True) self.host.quit(C.EXIT_BRIDGE_ERRBACK) def start(self): common.checkURI(self.args) self.host.bridge.psNodeAffiliationsGet( self.args.service, self.args.node, self.profile, callback=self.psNodeAffiliationsGetCb, errback=self.psNodeAffiliationsGetEb) class NodeAffiliationsSet(base.CommandBase): def __init__(self, host): base.CommandBase.__init__(self, host, 'set', use_pubsub_node_req=True, use_verbose=True, help=_(u'set affiliations (for node owner)')) self.need_loop=True def add_parser_options(self): # XXX: we use optional argument syntax for a required one because list of list of 2 elements # (uses to construct dicts) don't work with positional arguments self.parser.add_argument("-a", "--affiliation", dest="affiliations", metavar=('JID', 'AFFILIATION'), required=True, type=base.unicode_decoder, action="append", nargs=2, help=_(u"entity/affiliation couple(s)")) def psNodeAffiliationsSetCb(self): self.disp(_(u"affiliations have been set"), 1) self.host.quit() def psNodeAffiliationsSetEb(self, failure_): self.disp(u"can't set node affiliations: {reason}".format( reason=failure_), error=True) self.host.quit(C.EXIT_BRIDGE_ERRBACK) def start(self): common.checkURI(self.args) affiliations = dict(self.args.affiliations) self.host.bridge.psNodeAffiliationsSet( self.args.service, self.args.node, affiliations, self.profile, callback=self.psNodeAffiliationsSetCb, errback=self.psNodeAffiliationsSetEb) class NodeAffiliations(base.CommandBase): subcommands = (NodeAffiliationsGet, NodeAffiliationsSet) def __init__(self, host): super(NodeAffiliations, self).__init__(host, 'affiliations', use_profile=False, help=_(u'set or retrieve node affiliations')) class Node(base.CommandBase): subcommands = (NodeInfo, NodeCreate, NodeDelete, NodeSet, NodeAffiliations) def __init__(self, host): super(Node, self).__init__(host, 'node', use_profile=False, help=_('node handling')) class Get(base.CommandBase): def __init__(self, host): base.CommandBase.__init__(self, host, 'get', use_output=C.OUTPUT_LIST_XML, use_pubsub_node_req=True, help=_(u'get pubsub item(s)')) self.need_loop=True def add_parser_options(self): self.parser.add_argument("-i", "--item", type=base.unicode_decoder, action='append', default=[], dest='items', help=_(u"item(s) id(s) to get (default: request all items)")) self.parser.add_argument("-S", "--sub-id", type=base.unicode_decoder, default=u'', help=_(u"subscription id")) self.parser.add_argument("-m", "--max", type=int, default=10, help=_(u"maximum number of items to get ({} to get all items)".format(C.NO_LIMIT))) # TODO: a key(s) argument to select keys to display # TODO: add MAM filters def psItemsGetCb(self, ps_result): self.output(ps_result[0]) self.host.quit(C.EXIT_OK) def psItemsGetEb(self, failure_): self.disp(u"can't get pubsub items: {reason}".format( reason=failure_), error=True) self.host.quit(C.EXIT_BRIDGE_ERRBACK) def start(self): common.checkURI(self.args) self.host.bridge.psItemsGet( self.args.service, self.args.node, self.args.max, self.args.items, self.args.sub_id, {}, self.profile, callback=self.psItemsGetCb, errback=self.psItemsGetEb) class Delete(base.CommandBase): def __init__(self, host): base.CommandBase.__init__(self, host, 'delete', use_pubsub_node_req=True, help=_(u'delete an item')) self.need_loop=True def add_parser_options(self): self.parser.add_argument("item", nargs='?', type=base.unicode_decoder, help=_(u"item to delete")) self.parser.add_argument("-f", "--force", action='store_true', help=_(u"delete without confirmation")) self.parser.add_argument("-n", "--notify", action='store_true', help=_(u"notify deletion")) def psItemsDeleteCb(self): self.disp(_(u'item {item_id} has been deleted').format(item_id=self.args.item)) self.host.quit(C.EXIT_OK) def start(self): common.checkURI(self.args) if not self.args.item: self.parser.error(_(u"You need to specify an item to delete")) if not self.args.force: message = _(u"Are you sure to delete item {item_id} ?").format(item_id=self.args.item) res = raw_input("{} (y/N)? ".format(message)) if res not in ("y", "Y"): self.disp(_(u"Item deletion cancelled")) self.host.quit(2) self.host.bridge.psRetractItem( self.args.service, self.args.node, self.args.item, self.args.notify, self.profile, callback=self.psItemsDeleteCb, errback=partial(self.errback, msg=_(u"can't delete item: {}"), exit_code=C.EXIT_BRIDGE_ERRBACK)) class Edit(base.CommandBase, common.BaseEdit): def __init__(self, host): base.CommandBase.__init__(self, host, 'edit', use_verbose=True, use_pubsub=True, help=_(u'edit an existing or new pubsub item')) common.BaseEdit.__init__(self, self.host, PUBSUB_TMP_DIR) def add_parser_options(self): self.parser.add_argument("item", type=base.unicode_decoder, nargs='?', default=u'new', help=_(u"URL of the item to edit, or keyword")) common.BaseEdit.add_parser_options(self) def edit(self, content_file_path, content_file_obj): # we launch editor self.runEditor("pubsub_editor_args", content_file_path, content_file_obj) def publish(self, content): published_id = self.host.bridge.psItemSend(self.pubsub_service, self.pubsub_node, content, self.pubsub_item or '', {}, self.profile) if published_id: self.disp(u"Item published at {pub_id}".format(pub_id=published_id)) else: self.disp(u"Item published") def getItemData(self, service, node, item): try: from lxml import etree except ImportError: self.disp(u"lxml module must be installed to use edit, please install it with \"pip install lxml\"", error=True) self.host.quit(1) items = [item] if item is not None else [] item_raw = self.host.bridge.psItemsGet(service, node, 1, items, "", {}, self.profile)[0][0] parser = etree.XMLParser(remove_blank_text=True) item_elt = etree.fromstring(item_raw, parser) item_id = item_elt.get('id') try: payload = item_elt[0] except IndexError: self.disp(_(u'Item has not payload'), 1) return u'' return etree.tostring(payload, encoding="unicode", pretty_print=True), item_id def start(self): self.pubsub_service, self.pubsub_node, self.pubsub_item, content_file_path, content_file_obj = self.getItemPath(self.args.item) self.edit(content_file_path, content_file_obj) class Affiliations(base.CommandBase): def __init__(self, host): base.CommandBase.__init__(self, host, 'affiliations', use_output=C.OUTPUT_DICT, use_pubsub=True, help=_(u'retrieve all affiliations on a service')) self.need_loop=True def add_parser_options(self): pass def psAffiliationsGetCb(self, affiliations): self.output(affiliations) self.host.quit() def psAffiliationsGetEb(self, failure_): self.disp(u"can't get node affiliations: {reason}".format( reason=failure_), error=True) self.host.quit(C.EXIT_BRIDGE_ERRBACK) def start(self): self.host.bridge.psAffiliationsGet( self.args.service, self.args.node, self.profile, callback=self.psAffiliationsGetCb, errback=self.psAffiliationsGetEb) class Uri(base.CommandBase): def __init__(self, host): base.CommandBase.__init__(self, host, 'uri', use_profile=False, use_pubsub_node_req=True, help=_(u'build URI')) self.need_loop=True def add_parser_options(self): self.parser.add_argument("-i", "--item", type=base.unicode_decoder, help=_(u"item to link")) self.parser.add_argument("-p", "--profile", type=base.unicode_decoder, default=C.PROF_KEY_DEFAULT, help=_(u"profile (used when no server is specified)")) def display_uri(self, jid_): uri_args = {} if not self.args.service: self.args.service = jid.JID(jid_).bare for key in ('node', 'service', 'item'): value = getattr(self.args, key) if key == 'service': key = 'path' if value: uri_args[key] = value self.disp(uri.buildXMPPUri(u'pubsub', **uri_args)) self.host.quit() def start(self): if not self.args.service: self.host.bridge.asyncGetParamA( u'JabberID', u'Connection', profile_key=self.args.profile, callback=self.display_uri, errback=partial(self.errback, msg=_(u"can't retrieve jid: {}"), exit_code=C.EXIT_BRIDGE_ERRBACK)) else: self.display_uri(None) class HookCreate(base.CommandBase): def __init__(self, host): base.CommandBase.__init__(self, host, 'create', use_pubsub_node_req=True, help=_(u'create a Pubsub hook')) self.need_loop=True def add_parser_options(self): self.parser.add_argument('-t', '--type', default=u'python', choices=('python', 'python_file', 'python_code'), help=_(u"hook type")) self.parser.add_argument('-P', '--persistent', action='store_true', help=_(u"make hook persistent across restarts")) self.parser.add_argument("hook_arg", type=base.unicode_decoder, help=_(u"argument of the hook (depend of the type)")) @staticmethod def checkArgs(self): if self.args.type == u'python_file': self.args.hook_arg = os.path.abspath(self.args.hook_arg) if not os.path.isfile(self.args.hook_arg): self.parser.error(_(u"{path} is not a file").format(path=self.args.hook_arg)) def start(self): common.checkURI(self.args) self.checkArgs(self) self.host.bridge.psHookAdd( self.args.service, self.args.node, self.args.type, self.args.hook_arg, self.args.persistent, self.profile, callback=self.host.quit, errback=partial(self.errback, msg=_(u"can't create hook: {}"), exit_code=C.EXIT_BRIDGE_ERRBACK)) class HookDelete(base.CommandBase): def __init__(self, host): base.CommandBase.__init__(self, host, 'delete', use_pubsub_node_req=True, help=_(u'delete a Pubsub hook')) self.need_loop=True def add_parser_options(self): self.parser.add_argument('-t', '--type', default=u'', choices=('', 'python', 'python_file', 'python_code'), help=_(u"hook type to remove, empty to remove all (DEFAULT: remove all)")) self.parser.add_argument('-a', '--arg', dest='hook_arg', type=base.unicode_decoder, default=u'', help=_(u"argument of the hook to remove, empty to remove all (DEFAULT: remove all)")) def psHookRemoveCb(self, nb_deleted): self.disp(_(u'{nb_deleted} hook(s) have been deleted').format( nb_deleted = nb_deleted)) self.host.quit() def start(self): common.checkURI(self.args) HookCreate.checkArgs(self) self.host.bridge.psHookRemove( self.args.service, self.args.node, self.args.type, self.args.hook_arg, self.profile, callback=self.psHookRemoveCb, errback=partial(self.errback, msg=_(u"can't delete hook: {}"), exit_code=C.EXIT_BRIDGE_ERRBACK)) class HookList(base.CommandBase): def __init__(self, host): base.CommandBase.__init__(self, host, 'list', use_output=C.OUTPUT_LIST_DICT, help=_(u'list hooks of a profile')) self.need_loop = True def add_parser_options(self): pass def psHookListCb(self, data): if not data: self.disp(_(u'No hook found.')) self.output(data) self.host.quit() def start(self): self.host.bridge.psHookList( self.profile, callback=self.psHookListCb, errback=partial(self.errback, msg=_(u"can't list hooks: {}"), exit_code=C.EXIT_BRIDGE_ERRBACK)) class Hook(base.CommandBase): subcommands = (HookCreate, HookDelete, HookList) def __init__(self, host): super(Hook, self).__init__(host, 'hook', use_profile=False, help=_('trigger action on Pubsub notifications')) class Pubsub(base.CommandBase): subcommands = (Get, Delete, Edit, Node, Affiliations, Hook, Uri) def __init__(self, host): super(Pubsub, self).__init__(host, 'pubsub', use_profile=False, help=_('PubSub nodes/items management'))