413 lines
16 KiB
Python
413 lines
16 KiB
Python
#!/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
|
||
|
||
__commands__ = ["Pubsub"]
|
||
|
||
PUBSUB_TMP_DIR = u"pubsub"
|
||
|
||
|
||
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):
|
||
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):
|
||
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):
|
||
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):
|
||
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):
|
||
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 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)
|
||
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)
|
||
|
||
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 Pubsub(base.CommandBase):
|
||
subcommands = (Get, Edit, Node, Affiliations, Uri)
|
||
|
||
def __init__(self, host):
|
||
super(Pubsub, self).__init__(host, 'pubsub', use_profile=False, help=_('PubSub nodes/items management'))
|