Mercurial > libervia-backend
comparison sat_frontends/jp/cmd_pubsub.py @ 2562:26edcf3a30eb
core, setup: huge cleaning:
- moved directories from src and frontends/src to sat and sat_frontends, which is the recommanded naming convention
- move twisted directory to root
- removed all hacks from setup.py, and added missing dependencies, it is now clean
- use https URL for website in setup.py
- removed "Environment :: X11 Applications :: GTK", as wix is deprecated and removed
- renamed sat.sh to sat and fixed its installation
- added python_requires to specify Python version needed
- replaced glib2reactor which use deprecated code by gtk3reactor
sat can now be installed directly from virtualenv without using --system-site-packages anymore \o/
author | Goffi <goffi@goffi.org> |
---|---|
date | Mon, 02 Apr 2018 19:44:50 +0200 |
parents | frontends/src/jp/cmd_pubsub.py@1d754bc14381 |
children | 56f94936df1e |
comparison
equal
deleted
inserted
replaced
2561:bd30dc3ffe5a | 2562:26edcf3a30eb |
---|---|
1 #!/usr/bin/env python2 | |
2 # -*- coding: utf-8 -*- | |
3 | |
4 # jp: a SàT command line tool | |
5 # Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org) | |
6 | |
7 # This program is free software: you can redistribute it and/or modify | |
8 # it under the terms of the GNU Affero General Public License as published by | |
9 # the Free Software Foundation, either version 3 of the License, or | |
10 # (at your option) any later version. | |
11 | |
12 # This program is distributed in the hope that it will be useful, | |
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
15 # GNU Affero General Public License for more details. | |
16 | |
17 # You should have received a copy of the GNU Affero General Public License | |
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
19 | |
20 | |
21 import base | |
22 from sat.core.i18n import _ | |
23 from sat.core import exceptions | |
24 from sat_frontends.jp.constants import Const as C | |
25 from sat_frontends.jp import common | |
26 from sat_frontends.jp import arg_tools | |
27 from functools import partial | |
28 from sat.tools.common import uri | |
29 from sat.tools.common.ansi import ANSI as A | |
30 from sat_frontends.tools import jid, strings | |
31 import argparse | |
32 import os.path | |
33 import re | |
34 import subprocess | |
35 import sys | |
36 | |
37 __commands__ = ["Pubsub"] | |
38 | |
39 PUBSUB_TMP_DIR = u"pubsub" | |
40 PUBSUB_SCHEMA_TMP_DIR = PUBSUB_TMP_DIR + "_schema" | |
41 ALLOWED_SUBSCRIPTIONS_OWNER = ('subscribed', 'pending', 'none') | |
42 | |
43 # TODO: need to split this class in several modules, plugin should handle subcommands | |
44 | |
45 | |
46 class NodeInfo(base.CommandBase): | |
47 | |
48 def __init__(self, host): | |
49 base.CommandBase.__init__(self, host, 'info', use_output=C.OUTPUT_DICT, use_pubsub=True, pubsub_flags={C.NODE}, help=_(u'retrieve node configuration')) | |
50 self.need_loop=True | |
51 | |
52 def add_parser_options(self): | |
53 self.parser.add_argument("-k", "--key", type=base.unicode_decoder, action='append', dest='keys', | |
54 help=_(u"data key to filter")) | |
55 | |
56 def removePrefix(self, key): | |
57 return key[7:] if key.startswith(u"pubsub#") else key | |
58 | |
59 def filterKey(self, key): | |
60 return any((key == k or key == u'pubsub#' + k) for k in self.args.keys) | |
61 | |
62 def psNodeConfigurationGetCb(self, config_dict): | |
63 key_filter = (lambda k: True) if not self.args.keys else self.filterKey | |
64 config_dict = {self.removePrefix(k):v for k,v in config_dict.iteritems() if key_filter(k)} | |
65 self.output(config_dict) | |
66 self.host.quit() | |
67 | |
68 def psNodeConfigurationGetEb(self, failure_): | |
69 self.disp(u"can't get node configuration: {reason}".format( | |
70 reason=failure_), error=True) | |
71 self.host.quit(C.EXIT_BRIDGE_ERRBACK) | |
72 | |
73 def start(self): | |
74 self.host.bridge.psNodeConfigurationGet( | |
75 self.args.service, | |
76 self.args.node, | |
77 self.profile, | |
78 callback=self.psNodeConfigurationGetCb, | |
79 errback=self.psNodeConfigurationGetEb) | |
80 | |
81 | |
82 class NodeCreate(base.CommandBase): | |
83 | |
84 def __init__(self, host): | |
85 base.CommandBase.__init__(self, host, 'create', use_output=C.OUTPUT_DICT, use_pubsub=True, pubsub_flags={C.NODE}, use_verbose=True, help=_(u'create a node')) | |
86 self.need_loop=True | |
87 | |
88 def add_parser_options(self): | |
89 self.parser.add_argument("-f", "--field", type=base.unicode_decoder, action='append', nargs=2, dest='fields', | |
90 default=[], metavar=(u"KEY", u"VALUE"), help=_(u"configuration field to set")) | |
91 self.parser.add_argument("-F", "--full-prefix", action="store_true", help=_(u"don't prepend \"pubsub#\" prefix to field names")) | |
92 | |
93 def psNodeCreateCb(self, node_id): | |
94 if self.host.verbosity: | |
95 announce = _(u'node created successfully: ') | |
96 else: | |
97 announce = u'' | |
98 self.disp(announce + node_id) | |
99 self.host.quit() | |
100 | |
101 def psNodeCreateEb(self, failure_): | |
102 self.disp(u"can't create: {reason}".format( | |
103 reason=failure_), error=True) | |
104 self.host.quit(C.EXIT_BRIDGE_ERRBACK) | |
105 | |
106 def start(self): | |
107 if not self.args.full_prefix: | |
108 options = {u'pubsub#' + k: v for k,v in self.args.fields} | |
109 else: | |
110 options = dict(self.args.fields) | |
111 self.host.bridge.psNodeCreate( | |
112 self.args.service, | |
113 self.args.node, | |
114 options, | |
115 self.profile, | |
116 callback=self.psNodeCreateCb, | |
117 errback=partial(self.errback, | |
118 msg=_(u"can't create node: {}"), | |
119 exit_code=C.EXIT_BRIDGE_ERRBACK)) | |
120 | |
121 | |
122 class NodeDelete(base.CommandBase): | |
123 | |
124 def __init__(self, host): | |
125 base.CommandBase.__init__(self, host, 'delete', use_pubsub=True, pubsub_flags={C.NODE}, help=_(u'delete a node')) | |
126 self.need_loop=True | |
127 | |
128 def add_parser_options(self): | |
129 self.parser.add_argument('-f', '--force', action='store_true', help=_(u'delete node without confirmation')) | |
130 | |
131 def psNodeDeleteCb(self): | |
132 self.disp(_(u'node [{node}] deleted successfully').format(node=self.args.node)) | |
133 self.host.quit() | |
134 | |
135 def start(self): | |
136 if not self.args.force: | |
137 if not self.args.service: | |
138 message = _(u"Are you sure to delete pep node [{node_id}] ?").format( | |
139 node_id=self.args.node) | |
140 else: | |
141 message = _(u"Are you sure to delete node [{node_id}] on service [{service}] ?").format( | |
142 node_id=self.args.node, service=self.args.service) | |
143 self.host.confirmOrQuit(message, _(u"node deletion cancelled")) | |
144 | |
145 self.host.bridge.psNodeDelete( | |
146 self.args.service, | |
147 self.args.node, | |
148 self.profile, | |
149 callback=self.psNodeDeleteCb, | |
150 errback=partial(self.errback, | |
151 msg=_(u"can't delete node: {}"), | |
152 exit_code=C.EXIT_BRIDGE_ERRBACK)) | |
153 | |
154 | |
155 class NodeSet(base.CommandBase): | |
156 | |
157 def __init__(self, host): | |
158 base.CommandBase.__init__(self, host, 'set', use_output=C.OUTPUT_DICT, use_pubsub=True, pubsub_flags={C.NODE}, use_verbose=True, help=_(u'set node configuration')) | |
159 self.need_loop=True | |
160 | |
161 def add_parser_options(self): | |
162 self.parser.add_argument("-f", "--field", type=base.unicode_decoder, action='append', nargs=2, dest='fields', | |
163 required=True, metavar=(u"KEY", u"VALUE"), help=_(u"configuration field to set (required)")) | |
164 | |
165 def psNodeConfigurationSetCb(self): | |
166 self.disp(_(u'node configuration successful'), 1) | |
167 self.host.quit() | |
168 | |
169 def psNodeConfigurationSetEb(self, failure_): | |
170 self.disp(u"can't set node configuration: {reason}".format( | |
171 reason=failure_), error=True) | |
172 self.host.quit(C.EXIT_BRIDGE_ERRBACK) | |
173 | |
174 def getKeyName(self, k): | |
175 if not k.startswith(u'pubsub#'): | |
176 return u'pubsub#' + k | |
177 else: | |
178 return k | |
179 | |
180 def start(self): | |
181 self.host.bridge.psNodeConfigurationSet( | |
182 self.args.service, | |
183 self.args.node, | |
184 {self.getKeyName(k): v for k,v in self.args.fields}, | |
185 self.profile, | |
186 callback=self.psNodeConfigurationSetCb, | |
187 errback=self.psNodeConfigurationSetEb) | |
188 | |
189 | |
190 class NodeAffiliationsGet(base.CommandBase): | |
191 | |
192 def __init__(self, host): | |
193 base.CommandBase.__init__(self, host, 'get', use_output=C.OUTPUT_DICT, use_pubsub=True, pubsub_flags={C.NODE}, help=_(u'retrieve node affiliations (for node owner)')) | |
194 self.need_loop=True | |
195 | |
196 def add_parser_options(self): | |
197 pass | |
198 | |
199 def psNodeAffiliationsGetCb(self, affiliations): | |
200 self.output(affiliations) | |
201 self.host.quit() | |
202 | |
203 def psNodeAffiliationsGetEb(self, failure_): | |
204 self.disp(u"can't get node affiliations: {reason}".format( | |
205 reason=failure_), error=True) | |
206 self.host.quit(C.EXIT_BRIDGE_ERRBACK) | |
207 | |
208 def start(self): | |
209 self.host.bridge.psNodeAffiliationsGet( | |
210 self.args.service, | |
211 self.args.node, | |
212 self.profile, | |
213 callback=self.psNodeAffiliationsGetCb, | |
214 errback=self.psNodeAffiliationsGetEb) | |
215 | |
216 | |
217 class NodeAffiliationsSet(base.CommandBase): | |
218 | |
219 def __init__(self, host): | |
220 base.CommandBase.__init__(self, host, 'set', use_pubsub=True, pubsub_flags={C.NODE}, use_verbose=True, help=_(u'set affiliations (for node owner)')) | |
221 self.need_loop=True | |
222 | |
223 def add_parser_options(self): | |
224 # XXX: we use optional argument syntax for a required one because list of list of 2 elements | |
225 # (uses to construct dicts) don't work with positional arguments | |
226 self.parser.add_argument("-a", | |
227 "--affiliation", | |
228 dest="affiliations", | |
229 metavar=('JID', 'AFFILIATION'), | |
230 required=True, | |
231 type=base.unicode_decoder, | |
232 action="append", | |
233 nargs=2, | |
234 help=_(u"entity/affiliation couple(s)")) | |
235 | |
236 def psNodeAffiliationsSetCb(self): | |
237 self.disp(_(u"affiliations have been set"), 1) | |
238 self.host.quit() | |
239 | |
240 def psNodeAffiliationsSetEb(self, failure_): | |
241 self.disp(u"can't set node affiliations: {reason}".format( | |
242 reason=failure_), error=True) | |
243 self.host.quit(C.EXIT_BRIDGE_ERRBACK) | |
244 | |
245 def start(self): | |
246 affiliations = dict(self.args.affiliations) | |
247 self.host.bridge.psNodeAffiliationsSet( | |
248 self.args.service, | |
249 self.args.node, | |
250 affiliations, | |
251 self.profile, | |
252 callback=self.psNodeAffiliationsSetCb, | |
253 errback=self.psNodeAffiliationsSetEb) | |
254 | |
255 | |
256 class NodeAffiliations(base.CommandBase): | |
257 subcommands = (NodeAffiliationsGet, NodeAffiliationsSet) | |
258 | |
259 def __init__(self, host): | |
260 super(NodeAffiliations, self).__init__(host, 'affiliations', use_profile=False, help=_(u'set or retrieve node affiliations')) | |
261 | |
262 | |
263 class NodeSubscriptionsGet(base.CommandBase): | |
264 | |
265 def __init__(self, host): | |
266 base.CommandBase.__init__(self, host, 'get', use_output=C.OUTPUT_DICT, use_pubsub=True, pubsub_flags={C.NODE}, help=_(u'retrieve node subscriptions (for node owner)')) | |
267 self.need_loop=True | |
268 | |
269 def add_parser_options(self): | |
270 pass | |
271 | |
272 def psNodeSubscriptionsGetCb(self, subscriptions): | |
273 self.output(subscriptions) | |
274 self.host.quit() | |
275 | |
276 def psNodeSubscriptionsGetEb(self, failure_): | |
277 self.disp(u"can't get node subscriptions: {reason}".format( | |
278 reason=failure_), error=True) | |
279 self.host.quit(C.EXIT_BRIDGE_ERRBACK) | |
280 | |
281 def start(self): | |
282 self.host.bridge.psNodeSubscriptionsGet( | |
283 self.args.service, | |
284 self.args.node, | |
285 self.profile, | |
286 callback=self.psNodeSubscriptionsGetCb, | |
287 errback=self.psNodeSubscriptionsGetEb) | |
288 | |
289 | |
290 class StoreSubscriptionAction(argparse.Action): | |
291 """Action which handle subscription parameter for owner | |
292 | |
293 list is given by pairs: jid and subscription state | |
294 if subscription state is not specified, it default to "subscribed" | |
295 """ | |
296 | |
297 def __call__(self, parser, namespace, values, option_string): | |
298 dest_dict = getattr(namespace, self.dest) | |
299 while values: | |
300 jid_s = values.pop(0) | |
301 try: | |
302 subscription = values.pop(0) | |
303 except IndexError: | |
304 subscription = 'subscribed' | |
305 if subscription not in ALLOWED_SUBSCRIPTIONS_OWNER: | |
306 parser.error(_(u"subscription must be one of {}").format(u', '.join(ALLOWED_SUBSCRIPTIONS_OWNER))) | |
307 dest_dict[jid_s] = subscription | |
308 | |
309 | |
310 class NodeSubscriptionsSet(base.CommandBase): | |
311 | |
312 def __init__(self, host): | |
313 base.CommandBase.__init__(self, host, 'set', use_pubsub=True, pubsub_flags={C.NODE}, use_verbose=True, help=_(u'set/modify subscriptions (for node owner)')) | |
314 self.need_loop=True | |
315 | |
316 def add_parser_options(self): | |
317 # XXX: we use optional argument syntax for a required one because list of list of 2 elements | |
318 # (uses to construct dicts) don't work with positional arguments | |
319 self.parser.add_argument("-S", | |
320 "--subscription", | |
321 dest="subscriptions", | |
322 default={}, | |
323 nargs='+', | |
324 metavar=('JID [SUSBSCRIPTION]'), | |
325 required=True, | |
326 type=base.unicode_decoder, | |
327 action=StoreSubscriptionAction, | |
328 help=_(u"entity/subscription couple(s)")) | |
329 | |
330 def psNodeSubscriptionsSetCb(self): | |
331 self.disp(_(u"subscriptions have been set"), 1) | |
332 self.host.quit() | |
333 | |
334 def psNodeSubscriptionsSetEb(self, failure_): | |
335 self.disp(u"can't set node subscriptions: {reason}".format( | |
336 reason=failure_), error=True) | |
337 self.host.quit(C.EXIT_BRIDGE_ERRBACK) | |
338 | |
339 def start(self): | |
340 self.host.bridge.psNodeSubscriptionsSet( | |
341 self.args.service, | |
342 self.args.node, | |
343 self.args.subscriptions, | |
344 self.profile, | |
345 callback=self.psNodeSubscriptionsSetCb, | |
346 errback=self.psNodeSubscriptionsSetEb) | |
347 | |
348 | |
349 class NodeSubscriptions(base.CommandBase): | |
350 subcommands = (NodeSubscriptionsGet, NodeSubscriptionsSet) | |
351 | |
352 def __init__(self, host): | |
353 super(NodeSubscriptions, self).__init__(host, 'subscriptions', use_profile=False, help=_(u'get or modify node subscriptions')) | |
354 | |
355 | |
356 class NodeSchemaSet(base.CommandBase): | |
357 | |
358 def __init__(self, host): | |
359 base.CommandBase.__init__(self, host, 'set', use_pubsub=True, pubsub_flags={C.NODE}, use_verbose=True, help=_(u'set/replace a schema')) | |
360 self.need_loop = True | |
361 | |
362 def add_parser_options(self): | |
363 self.parser.add_argument('schema', help=_(u"schema to set (must be XML)")) | |
364 | |
365 def psSchemaSetCb(self): | |
366 self.disp(_(u'schema has been set'), 1) | |
367 self.host.quit() | |
368 | |
369 def start(self): | |
370 self.host.bridge.psSchemaSet( | |
371 self.args.service, | |
372 self.args.node, | |
373 self.args.schema, | |
374 self.profile, | |
375 callback=self.psSchemaSetCb, | |
376 errback=partial(self.errback, | |
377 msg=_(u"can't set schema: {}"), | |
378 exit_code=C.EXIT_BRIDGE_ERRBACK)) | |
379 | |
380 | |
381 class NodeSchemaEdit(base.CommandBase, common.BaseEdit): | |
382 use_items=False | |
383 | |
384 def __init__(self, host): | |
385 base.CommandBase.__init__(self, host, 'edit', use_pubsub=True, pubsub_flags={C.NODE}, use_draft=True, use_verbose=True, help=_(u'edit a schema')) | |
386 common.BaseEdit.__init__(self, self.host, PUBSUB_SCHEMA_TMP_DIR) | |
387 self.need_loop=True | |
388 | |
389 def add_parser_options(self): | |
390 pass | |
391 | |
392 def psSchemaSetCb(self): | |
393 self.disp(_(u'schema has been set'), 1) | |
394 self.host.quit() | |
395 | |
396 def publish(self, schema): | |
397 self.host.bridge.psSchemaSet( | |
398 self.args.service, | |
399 self.args.node, | |
400 schema, | |
401 self.profile, | |
402 callback=self.psSchemaSetCb, | |
403 errback=partial(self.errback, | |
404 msg=_(u"can't set schema: {}"), | |
405 exit_code=C.EXIT_BRIDGE_ERRBACK)) | |
406 | |
407 def psSchemaGetCb(self, schema): | |
408 try: | |
409 from lxml import etree | |
410 except ImportError: | |
411 self.disp(u"lxml module must be installed to use edit, please install it with \"pip install lxml\"", error=True) | |
412 self.host.quit(1) | |
413 content_file_obj, content_file_path = self.getTmpFile() | |
414 schema = schema.strip() | |
415 if schema: | |
416 parser = etree.XMLParser(remove_blank_text=True) | |
417 schema_elt = etree.fromstring(schema, parser) | |
418 content_file_obj.write(etree.tostring(schema_elt, encoding="utf-8", pretty_print=True)) | |
419 content_file_obj.seek(0) | |
420 self.runEditor("pubsub_schema_editor_args", content_file_path, content_file_obj) | |
421 | |
422 def start(self): | |
423 self.host.bridge.psSchemaGet( | |
424 self.args.service, | |
425 self.args.node, | |
426 self.profile, | |
427 callback=self.psSchemaGetCb, | |
428 errback=partial(self.errback, | |
429 msg=_(u"can't edit schema: {}"), | |
430 exit_code=C.EXIT_BRIDGE_ERRBACK)) | |
431 | |
432 | |
433 class NodeSchemaGet(base.CommandBase): | |
434 | |
435 def __init__(self, host): | |
436 base.CommandBase.__init__(self, host, 'get', use_output=C.OUTPUT_XML, use_pubsub=True, pubsub_flags={C.NODE}, use_verbose=True, help=_(u'get schema')) | |
437 self.need_loop=True | |
438 | |
439 def add_parser_options(self): | |
440 pass | |
441 | |
442 def psSchemaGetCb(self, schema): | |
443 if not schema: | |
444 self.disp(_(u'no schema found'), 1) | |
445 self.host.quit(1) | |
446 self.output(schema) | |
447 self.host.quit() | |
448 | |
449 def start(self): | |
450 self.host.bridge.psSchemaGet( | |
451 self.args.service, | |
452 self.args.node, | |
453 self.profile, | |
454 callback=self.psSchemaGetCb, | |
455 errback=partial(self.errback, | |
456 msg=_(u"can't get schema: {}"), | |
457 exit_code=C.EXIT_BRIDGE_ERRBACK)) | |
458 | |
459 | |
460 class NodeSchema(base.CommandBase): | |
461 subcommands = (NodeSchemaSet, NodeSchemaEdit, NodeSchemaGet) | |
462 | |
463 def __init__(self, host): | |
464 super(NodeSchema, self).__init__(host, 'schema', use_profile=False, help=_(u"data schema manipulation")) | |
465 | |
466 | |
467 class Node(base.CommandBase): | |
468 subcommands = (NodeInfo, NodeCreate, NodeDelete, NodeSet, NodeAffiliations, NodeSubscriptions, NodeSchema) | |
469 | |
470 def __init__(self, host): | |
471 super(Node, self).__init__(host, 'node', use_profile=False, help=_('node handling')) | |
472 | |
473 | |
474 class Set(base.CommandBase): | |
475 | |
476 def __init__(self, host): | |
477 base.CommandBase.__init__(self, host, 'set', use_pubsub=True, pubsub_flags={C.NODE}, help=_(u'publish a new item or update an existing one')) | |
478 self.need_loop=True | |
479 | |
480 def add_parser_options(self): | |
481 self.parser.add_argument("item", type=base.unicode_decoder, nargs='?', default=u'', help=_(u"id, URL of the item to update, keyword, or nothing for new item")) | |
482 | |
483 def psItemsSendCb(self, published_id): | |
484 if published_id: | |
485 self.disp(u"Item published at {pub_id}".format(pub_id=published_id)) | |
486 else: | |
487 self.disp(u"Item published") | |
488 self.host.quit(C.EXIT_OK) | |
489 | |
490 def start(self): | |
491 try: | |
492 from lxml import etree | |
493 except ImportError: | |
494 self.disp(u"lxml module must be installed to use edit, please install it with \"pip install lxml\"", error=True) | |
495 self.host.quit(1) | |
496 try: | |
497 element = etree.parse(sys.stdin).getroot() | |
498 except Exception as e: | |
499 self.parser.error(_(u"Can't parse the payload XML in input: {msg}").format(msg=e)) | |
500 if element.tag in ('item', '{http://jabber.org/protocol/pubsub}item'): | |
501 if len(element) > 1: | |
502 self.parser.error(_(u"<item> can only have one child element (the payload)")) | |
503 element = element[0] | |
504 payload = etree.tostring(element, encoding='unicode') | |
505 | |
506 self.host.bridge.psItemSend(self.args.service, | |
507 self.args.node, | |
508 payload, | |
509 self.args.item, | |
510 {}, | |
511 self.profile, | |
512 callback=self.psItemsSendCb, | |
513 errback=partial(self.errback, | |
514 msg=_(u"can't send item: {}"), | |
515 exit_code=C.EXIT_BRIDGE_ERRBACK)) | |
516 | |
517 | |
518 class Get(base.CommandBase): | |
519 | |
520 def __init__(self, host): | |
521 base.CommandBase.__init__(self, host, 'get', use_output=C.OUTPUT_LIST_XML, use_pubsub=True, pubsub_flags={C.NODE, C.MULTI_ITEMS}, help=_(u'get pubsub item(s)')) | |
522 self.need_loop=True | |
523 | |
524 def add_parser_options(self): | |
525 self.parser.add_argument("-S", "--sub-id", type=base.unicode_decoder, default=u'', | |
526 help=_(u"subscription id")) | |
527 # TODO: a key(s) argument to select keys to display | |
528 # TODO: add MAM filters | |
529 | |
530 | |
531 def psItemsGetCb(self, ps_result): | |
532 self.output(ps_result[0]) | |
533 self.host.quit(C.EXIT_OK) | |
534 | |
535 def psItemsGetEb(self, failure_): | |
536 self.disp(u"can't get pubsub items: {reason}".format( | |
537 reason=failure_), error=True) | |
538 self.host.quit(C.EXIT_BRIDGE_ERRBACK) | |
539 | |
540 def start(self): | |
541 self.host.bridge.psItemsGet( | |
542 self.args.service, | |
543 self.args.node, | |
544 self.args.max, | |
545 self.args.items, | |
546 self.args.sub_id, | |
547 {}, | |
548 self.profile, | |
549 callback=self.psItemsGetCb, | |
550 errback=self.psItemsGetEb) | |
551 | |
552 class Delete(base.CommandBase): | |
553 | |
554 def __init__(self, host): | |
555 base.CommandBase.__init__(self, host, 'delete', use_pubsub=True, pubsub_flags={C.NODE, C.SINGLE_ITEM}, help=_(u'delete an item')) | |
556 self.need_loop=True | |
557 | |
558 def add_parser_options(self): | |
559 self.parser.add_argument("-f", "--force", action='store_true', help=_(u"delete without confirmation")) | |
560 self.parser.add_argument("-N", "--notify", action='store_true', help=_(u"notify deletion")) | |
561 | |
562 def psItemsDeleteCb(self): | |
563 self.disp(_(u'item {item_id} has been deleted').format(item_id=self.args.item)) | |
564 self.host.quit(C.EXIT_OK) | |
565 | |
566 def start(self): | |
567 if not self.args.item: | |
568 self.parser.error(_(u"You need to specify an item to delete")) | |
569 if not self.args.force: | |
570 message = _(u"Are you sure to delete item {item_id} ?").format(item_id=self.args.item) | |
571 self.host.confirmOrQuit(message, _(u"item deletion cancelled")) | |
572 self.host.bridge.psRetractItem( | |
573 self.args.service, | |
574 self.args.node, | |
575 self.args.item, | |
576 self.args.notify, | |
577 self.profile, | |
578 callback=self.psItemsDeleteCb, | |
579 errback=partial(self.errback, | |
580 msg=_(u"can't delete item: {}"), | |
581 exit_code=C.EXIT_BRIDGE_ERRBACK)) | |
582 | |
583 | |
584 class Edit(base.CommandBase, common.BaseEdit): | |
585 | |
586 def __init__(self, host): | |
587 base.CommandBase.__init__(self, host, 'edit', use_verbose=True, use_pubsub=True, | |
588 pubsub_flags={C.NODE, C.SINGLE_ITEM}, use_draft=True, help=_(u'edit an existing or new pubsub item')) | |
589 common.BaseEdit.__init__(self, self.host, PUBSUB_TMP_DIR) | |
590 | |
591 def add_parser_options(self): | |
592 pass | |
593 | |
594 def edit(self, content_file_path, content_file_obj): | |
595 # we launch editor | |
596 self.runEditor("pubsub_editor_args", content_file_path, content_file_obj) | |
597 | |
598 def publish(self, content): | |
599 published_id = self.host.bridge.psItemSend(self.pubsub_service, self.pubsub_node, content, self.pubsub_item or '', {}, self.profile) | |
600 if published_id: | |
601 self.disp(u"Item published at {pub_id}".format(pub_id=published_id)) | |
602 else: | |
603 self.disp(u"Item published") | |
604 | |
605 def getItemData(self, service, node, item): | |
606 try: | |
607 from lxml import etree | |
608 except ImportError: | |
609 self.disp(u"lxml module must be installed to use edit, please install it with \"pip install lxml\"", error=True) | |
610 self.host.quit(1) | |
611 items = [item] if item is not None else [] | |
612 item_raw = self.host.bridge.psItemsGet(service, node, 1, items, "", {}, self.profile)[0][0] | |
613 parser = etree.XMLParser(remove_blank_text=True) | |
614 item_elt = etree.fromstring(item_raw, parser) | |
615 item_id = item_elt.get('id') | |
616 try: | |
617 payload = item_elt[0] | |
618 except IndexError: | |
619 self.disp(_(u'Item has not payload'), 1) | |
620 return u'' | |
621 return etree.tostring(payload, encoding="unicode", pretty_print=True), item_id | |
622 | |
623 def start(self): | |
624 self.pubsub_service, self.pubsub_node, self.pubsub_item, content_file_path, content_file_obj = self.getItemPath() | |
625 self.edit(content_file_path, content_file_obj) | |
626 | |
627 | |
628 class Subscribe(base.CommandBase): | |
629 | |
630 def __init__(self, host): | |
631 base.CommandBase.__init__(self, host, 'subscribe', use_pubsub=True, pubsub_flags={C.NODE}, use_verbose=True, help=_(u'subscribe to a node')) | |
632 self.need_loop=True | |
633 | |
634 def add_parser_options(self): | |
635 pass | |
636 | |
637 def psSubscribeCb(self, sub_id): | |
638 self.disp(_(u'subscription done'), 1) | |
639 if sub_id: | |
640 self.disp(_(u'subscription id: {sub_id}').format(sub_id=sub_id)) | |
641 self.host.quit() | |
642 | |
643 def start(self): | |
644 self.host.bridge.psSubscribe( | |
645 self.args.service, | |
646 self.args.node, | |
647 {}, | |
648 self.profile, | |
649 callback=self.psSubscribeCb, | |
650 errback=partial(self.errback, | |
651 msg=_(u"can't subscribe to node: {}"), | |
652 exit_code=C.EXIT_BRIDGE_ERRBACK)) | |
653 | |
654 | |
655 class Unsubscribe(base.CommandBase): | |
656 # TODO: voir pourquoi NodeNotFound sur subscribe juste après unsubscribe | |
657 | |
658 def __init__(self, host): | |
659 base.CommandBase.__init__(self, host, 'unsubscribe', use_pubsub=True, pubsub_flags={C.NODE}, use_verbose=True, help=_(u'unsubscribe from a node')) | |
660 self.need_loop=True | |
661 | |
662 def add_parser_options(self): | |
663 pass | |
664 | |
665 def psUnsubscribeCb(self): | |
666 self.disp(_(u'subscription removed'), 1) | |
667 self.host.quit() | |
668 | |
669 def start(self): | |
670 self.host.bridge.psUnsubscribe( | |
671 self.args.service, | |
672 self.args.node, | |
673 self.profile, | |
674 callback=self.psUnsubscribeCb, | |
675 errback=partial(self.errback, | |
676 msg=_(u"can't unsubscribe from node: {}"), | |
677 exit_code=C.EXIT_BRIDGE_ERRBACK)) | |
678 | |
679 | |
680 class Subscriptions(base.CommandBase): | |
681 | |
682 def __init__(self, host): | |
683 base.CommandBase.__init__(self, host, 'subscriptions', use_output=C.OUTPUT_LIST_DICT, use_pubsub=True, help=_(u'retrieve all subscriptions on a service')) | |
684 self.need_loop=True | |
685 | |
686 def add_parser_options(self): | |
687 pass | |
688 | |
689 def psSubscriptionsGetCb(self, subscriptions): | |
690 self.output(subscriptions) | |
691 self.host.quit() | |
692 | |
693 def start(self): | |
694 self.host.bridge.psSubscriptionsGet( | |
695 self.args.service, | |
696 self.args.node, | |
697 self.profile, | |
698 callback=self.psSubscriptionsGetCb, | |
699 errback=partial(self.errback, | |
700 msg=_(u"can't retrieve subscriptions: {}"), | |
701 exit_code=C.EXIT_BRIDGE_ERRBACK)) | |
702 | |
703 | |
704 class Affiliations(base.CommandBase): | |
705 | |
706 def __init__(self, host): | |
707 base.CommandBase.__init__(self, host, 'affiliations', use_output=C.OUTPUT_DICT, use_pubsub=True, help=_(u'retrieve all affiliations on a service')) | |
708 self.need_loop=True | |
709 | |
710 def add_parser_options(self): | |
711 pass | |
712 | |
713 def psAffiliationsGetCb(self, affiliations): | |
714 self.output(affiliations) | |
715 self.host.quit() | |
716 | |
717 def psAffiliationsGetEb(self, failure_): | |
718 self.disp(u"can't get node affiliations: {reason}".format( | |
719 reason=failure_), error=True) | |
720 self.host.quit(C.EXIT_BRIDGE_ERRBACK) | |
721 | |
722 def start(self): | |
723 self.host.bridge.psAffiliationsGet( | |
724 self.args.service, | |
725 self.args.node, | |
726 self.profile, | |
727 callback=self.psAffiliationsGetCb, | |
728 errback=self.psAffiliationsGetEb) | |
729 | |
730 | |
731 class Search(base.CommandBase): | |
732 """this command to a search without using MAM, i.e. by checking every items if dound by itself, so it may be heavy in resources both for server and client""" | |
733 RE_FLAGS = re.MULTILINE | re.UNICODE | |
734 EXEC_ACTIONS = (u'exec', u'external') | |
735 | |
736 def __init__(self, host): | |
737 base.CommandBase.__init__(self, host, 'search', use_output=C.OUTPUT_XML, use_pubsub=True, pubsub_flags={C.MULTI_ITEMS, C.NO_MAX}, | |
738 use_verbose=True, help=_(u'search items corresponding to filters')) | |
739 self.need_loop=True | |
740 | |
741 @property | |
742 def etree(self): | |
743 """load lxml.etree only if needed""" | |
744 if self._etree is None: | |
745 from lxml import etree | |
746 self._etree = etree | |
747 return self._etree | |
748 | |
749 def filter_opt(self, value, type_): | |
750 value = base.unicode_decoder(value) | |
751 return (type_, value) | |
752 | |
753 def filter_flag(self, value, type_): | |
754 value = C.bool(value) | |
755 return (type_, value) | |
756 | |
757 def add_parser_options(self): | |
758 self.parser.add_argument("-D", "--max-depth", type=int, default=0, help=_(u"maximum depth of recursion (will search linked nodes if > 0, default: 0)")) | |
759 self.parser.add_argument("-m", "--max", type=int, default=30, help=_(u"maximum number of items to get per node ({} to get all items, default: 30)".format(C.NO_LIMIT))) | |
760 self.parser.add_argument("-N", "--namespace", action='append', nargs=2, default=[], | |
761 metavar="NAME NAMESPACE", help=_(u"namespace to use for xpath")) | |
762 | |
763 # filters | |
764 filter_text = partial(self.filter_opt, type_=u'text') | |
765 filter_re = partial(self.filter_opt, type_=u'regex') | |
766 filter_xpath = partial(self.filter_opt, type_=u'xpath') | |
767 filter_python = partial(self.filter_opt, type_=u'python') | |
768 filters = self.parser.add_argument_group(_(u'filters'), _(u'only items corresponding to following filters will be kept')) | |
769 filters.add_argument("-t", "--text", | |
770 action='append', dest='filters', type=filter_text, | |
771 metavar='TEXT', | |
772 help=_(u"full text filter, item must contain this string (XML included)")) | |
773 filters.add_argument("-r", "--regex", | |
774 action='append', dest='filters', type=filter_re, | |
775 metavar='EXPRESSION', | |
776 help=_(u"like --text but using a regular expression")) | |
777 filters.add_argument("-x", "--xpath", | |
778 action='append', dest='filters', type=filter_xpath, | |
779 metavar='XPATH', | |
780 help=_(u"filter items which has elements matching this xpath")) | |
781 filters.add_argument("-P", "--python", | |
782 action='append', dest='filters', type=filter_python, | |
783 metavar='PYTHON_CODE', | |
784 help=_(u'Python expression which much return a bool (True to keep item, False to reject it). "item" is raw text item, "item_xml" is lxml\'s etree.Element')) | |
785 | |
786 # filters flags | |
787 flag_case = partial(self.filter_flag, type_=u'ignore-case') | |
788 flag_invert = partial(self.filter_flag, type_=u'invert') | |
789 flag_dotall = partial(self.filter_flag, type_=u'dotall') | |
790 flag_matching = partial(self.filter_flag, type_=u'only-matching') | |
791 flags = self.parser.add_argument_group(_(u'filters flags'), _(u'filters modifiers (change behaviour of following filters)')) | |
792 flags.add_argument("-C", "--ignore-case", | |
793 action='append', dest='filters', type=flag_case, | |
794 const=('ignore-case', True), nargs='?', | |
795 metavar='BOOLEAN', | |
796 help=_(u"(don't) ignore case in following filters (default: case sensitive)")) | |
797 flags.add_argument("-I", "--invert", | |
798 action='append', dest='filters', type=flag_invert, | |
799 const=('invert', True), nargs='?', | |
800 metavar='BOOLEAN', | |
801 help=_(u"(don't) invert effect of following filters (default: don't invert)")) | |
802 flags.add_argument("-A", "--dot-all", | |
803 action='append', dest='filters', type=flag_dotall, | |
804 const=('dotall', True), nargs='?', | |
805 metavar='BOOLEAN', | |
806 help=_(u"(don't) use DOTALL option for regex (default: don't use)")) | |
807 flags.add_argument("-o", "--only-matching", | |
808 action='append', dest='filters', type=flag_matching, | |
809 const=('only-matching', True), nargs='?', | |
810 metavar='BOOLEAN', | |
811 help=_(u"keep only the matching part of the item")) | |
812 | |
813 # action | |
814 self.parser.add_argument("action", | |
815 default="print", | |
816 nargs='?', | |
817 choices=('print', 'exec', 'external'), | |
818 help=_(u"action to do on found items (default: print)")) | |
819 self.parser.add_argument("command", nargs=argparse.REMAINDER) | |
820 | |
821 def psItemsGetEb(self, failure_, service, node): | |
822 self.disp(u"can't get pubsub items at {service} (node: {node}): {reason}".format( | |
823 service=service, | |
824 node=node, | |
825 reason=failure_), error=True) | |
826 self.to_get -= 1 | |
827 | |
828 def getItems(self, depth, service, node, items): | |
829 search = partial(self.search, depth=depth) | |
830 errback = partial(self.psItemsGetEb, service=service, node=node) | |
831 self.host.bridge.psItemsGet( | |
832 service, | |
833 node, | |
834 self.args.max, | |
835 items, | |
836 "", | |
837 {}, | |
838 self.profile, | |
839 callback=search, | |
840 errback=errback | |
841 ) | |
842 self.to_get += 1 | |
843 | |
844 def _checkPubsubURL(self, match, found_nodes): | |
845 """check that the matched URL is an xmpp: one | |
846 | |
847 @param found_nodes(list[unicode]): found_nodes | |
848 this list will be filled while xmpp: URIs are discovered | |
849 """ | |
850 url = match.group(0) | |
851 if url.startswith(u'xmpp'): | |
852 try: | |
853 url_data = uri.parseXMPPUri(url) | |
854 except ValueError: | |
855 return | |
856 if url_data[u'type'] == u'pubsub': | |
857 found_node = {u'service': url_data[u'path'], | |
858 u'node': url_data[u'node']} | |
859 if u'item' in url_data: | |
860 found_node[u'item'] = url_data[u'item'] | |
861 found_nodes.append(found_node) | |
862 | |
863 def getSubNodes(self, item, depth): | |
864 """look for pubsub URIs in item, and getItems on the linked nodes""" | |
865 found_nodes = [] | |
866 checkURI = partial(self._checkPubsubURL, found_nodes=found_nodes) | |
867 strings.RE_URL.sub(checkURI, item) | |
868 for data in found_nodes: | |
869 self.getItems(depth+1, | |
870 data[u'service'], | |
871 data[u'node'], | |
872 [data[u'item']] if u'item' in data else [] | |
873 ) | |
874 | |
875 def parseXml(self, item): | |
876 try: | |
877 return self.etree.fromstring(item) | |
878 except self.etree.XMLSyntaxError: | |
879 self.disp(_(u"item doesn't looks like XML, you have probably used --only-matching somewhere before and we have no more XML"), error=True) | |
880 self.host.quit(C.EXIT_BAD_ARG) | |
881 | |
882 def filter(self, item): | |
883 """apply filters given on command line | |
884 | |
885 if only-matching is used, item may be modified | |
886 @return (tuple[bool, unicode]): a tuple with: | |
887 - keep: True if item passed the filters | |
888 - item: it is returned in case of modifications | |
889 """ | |
890 ignore_case = False | |
891 invert = False | |
892 dotall = False | |
893 only_matching = False | |
894 item_xml = None | |
895 for type_, value in self.args.filters: | |
896 keep = True | |
897 | |
898 ## filters | |
899 | |
900 if type_ == u'text': | |
901 if ignore_case: | |
902 if value.lower() not in item.lower(): | |
903 keep = False | |
904 else: | |
905 if value not in item: | |
906 keep = False | |
907 if keep and only_matching: | |
908 # doesn't really make sens to keep a fixed string | |
909 # so we raise an error | |
910 self.host.disp(_(u"--only-matching used with fixed --text string, are you sure?"), error=True) | |
911 self.host.quit(C.EXIT_BAD_ARG) | |
912 elif type_ == u'regex': | |
913 flags = self.RE_FLAGS | |
914 if ignore_case: | |
915 flags |= re.IGNORECASE | |
916 if dotall: | |
917 flags |= re.DOTALL | |
918 match = re.search(value, item, flags) | |
919 keep = match != None | |
920 if keep and only_matching: | |
921 item = match.group() | |
922 item_xml = None | |
923 elif type_ == u'xpath': | |
924 if item_xml is None: | |
925 item_xml = self.parseXml(item) | |
926 try: | |
927 elts = item_xml.xpath(value, namespaces=self.args.namespace) | |
928 except self.etree.XPathEvalError as e: | |
929 self.disp(_(u"can't use xpath: {reason}").format(reason=e), error=True) | |
930 self.host.quit(C.EXIT_BAD_ARG) | |
931 keep = bool(elts) | |
932 if keep and only_matching: | |
933 item_xml = elts[0] | |
934 try: | |
935 item = self.etree.tostring(item_xml, encoding='unicode') | |
936 except TypeError: | |
937 # we have a string only, not an element | |
938 item = unicode(item_xml) | |
939 item_xml = None | |
940 elif type_ == u'python': | |
941 if item_xml is None: | |
942 item_xml = self.parseXml(item) | |
943 cmd_ns = {u'item': item, | |
944 u'item_xml': item_xml | |
945 } | |
946 try: | |
947 keep = eval(value, cmd_ns) | |
948 except SyntaxError as e: | |
949 self.disp(unicode(e), error=True) | |
950 self.host.quit(C.EXIT_BAD_ARG) | |
951 | |
952 ## flags | |
953 | |
954 elif type_ == u'ignore-case': | |
955 ignore_case = value | |
956 elif type_ == u'invert': | |
957 invert = value | |
958 # we need to continue, else loop would end here | |
959 continue | |
960 elif type_ == u'dotall': | |
961 dotall = value | |
962 elif type_ == u'only-matching': | |
963 only_matching = value | |
964 else: | |
965 raise exceptions.InternalError(_(u"unknown filter type {type}").format(type=type_)) | |
966 | |
967 if invert: | |
968 keep = not keep | |
969 if not keep: | |
970 return False, item | |
971 | |
972 return True, item | |
973 | |
974 def doItemAction(self, item, metadata): | |
975 """called when item has been kepts and the action need to be done | |
976 | |
977 @param item(unicode): accepted item | |
978 """ | |
979 action = self.args.action | |
980 if action == u'print' or self.host.verbosity > 0: | |
981 try: | |
982 self.output(item) | |
983 except self.etree.XMLSyntaxError: | |
984 # item is not valid XML, but a string | |
985 # can happen when --only-matching is used | |
986 self.disp(item) | |
987 if action in self.EXEC_ACTIONS: | |
988 item_elt = self.parseXml(item) | |
989 if action == u'exec': | |
990 use = {'service': metadata[u'service'], | |
991 'node': metadata[u'node'], | |
992 'item': item_elt.get('id'), | |
993 'profile': self.profile | |
994 } | |
995 # we need to send a copy of self.args.command | |
996 # else it would be modified | |
997 parser_args, use_args = arg_tools.get_use_args(self.host, | |
998 self.args.command, | |
999 use, | |
1000 verbose=self.host.verbosity > 1 | |
1001 ) | |
1002 cmd_args = sys.argv[0:1] + parser_args + use_args | |
1003 else: | |
1004 cmd_args = self.args.command | |
1005 | |
1006 | |
1007 self.disp(u'COMMAND: {command}'.format( | |
1008 command = u' '.join([arg_tools.escape(a) for a in cmd_args])), 2) | |
1009 if action == u'exec': | |
1010 ret = subprocess.call(cmd_args) | |
1011 else: | |
1012 p = subprocess.Popen(cmd_args, stdin=subprocess.PIPE) | |
1013 p.communicate(item.encode('utf-8')) | |
1014 ret = p.wait() | |
1015 if ret != 0: | |
1016 self.disp(A.color(C.A_FAILURE, _(u"executed command failed with exit code {code}").format(code=ret))) | |
1017 | |
1018 def search(self, items_data, depth): | |
1019 """callback of getItems | |
1020 | |
1021 this method filters items, get sub nodes if needed, | |
1022 do the requested action, and exit the command when everything is done | |
1023 @param items_data(tuple): result of getItems | |
1024 @param depth(int): current depth level | |
1025 0 for first node, 1 for first children, and so on | |
1026 """ | |
1027 items, metadata = items_data | |
1028 for item in items: | |
1029 if depth < self.args.max_depth: | |
1030 self.getSubNodes(item, depth) | |
1031 keep, item = self.filter(item) | |
1032 if not keep: | |
1033 continue | |
1034 self.doItemAction(item, metadata) | |
1035 | |
1036 # we check if we got all getItems results | |
1037 self.to_get -= 1 | |
1038 if self.to_get == 0: | |
1039 # yes, we can quit | |
1040 self.host.quit() | |
1041 assert self.to_get > 0 | |
1042 | |
1043 def start(self): | |
1044 if self.args.command: | |
1045 if self.args.action not in self.EXEC_ACTIONS: | |
1046 self.parser.error(_(u"Command can only be used with {actions} actions").format( | |
1047 actions=u', '.join(self.EXEC_ACTIONS))) | |
1048 else: | |
1049 if self.args.action in self.EXEC_ACTIONS: | |
1050 self.parser.error(_(u"you need to specify a command to execute")) | |
1051 if not self.args.node: | |
1052 # TODO: handle get service affiliations when node is not set | |
1053 self.parser.error(_(u"empty node is not handled yet")) | |
1054 # to_get is increased on each get and decreased on each answer | |
1055 # when it reach 0 again, the command is finished | |
1056 self.to_get = 0 | |
1057 self._etree = None | |
1058 if self.args.filters is None: | |
1059 self.args.filters = [] | |
1060 self.args.namespace = dict(self.args.namespace + [('pubsub', "http://jabber.org/protocol/pubsub")]) | |
1061 self.getItems(0, self.args.service, self.args.node, self.args.items) | |
1062 | |
1063 | |
1064 class Uri(base.CommandBase): | |
1065 | |
1066 def __init__(self, host): | |
1067 base.CommandBase.__init__(self, host, 'uri', use_profile=False, use_pubsub=True, pubsub_flags={C.NODE, C.SINGLE_ITEM}, help=_(u'build URI')) | |
1068 self.need_loop=True | |
1069 | |
1070 def add_parser_options(self): | |
1071 self.parser.add_argument("-p", "--profile", type=base.unicode_decoder, default=C.PROF_KEY_DEFAULT, help=_(u"profile (used when no server is specified)")) | |
1072 | |
1073 def display_uri(self, jid_): | |
1074 uri_args = {} | |
1075 if not self.args.service: | |
1076 self.args.service = jid.JID(jid_).bare | |
1077 | |
1078 for key in ('node', 'service', 'item'): | |
1079 value = getattr(self.args, key) | |
1080 if key == 'service': | |
1081 key = 'path' | |
1082 if value: | |
1083 uri_args[key] = value | |
1084 self.disp(uri.buildXMPPUri(u'pubsub', **uri_args)) | |
1085 self.host.quit() | |
1086 | |
1087 def start(self): | |
1088 if not self.args.service: | |
1089 self.host.bridge.asyncGetParamA( | |
1090 u'JabberID', | |
1091 u'Connection', | |
1092 profile_key=self.args.profile, | |
1093 callback=self.display_uri, | |
1094 errback=partial(self.errback, | |
1095 msg=_(u"can't retrieve jid: {}"), | |
1096 exit_code=C.EXIT_BRIDGE_ERRBACK)) | |
1097 else: | |
1098 self.display_uri(None) | |
1099 | |
1100 | |
1101 class HookCreate(base.CommandBase): | |
1102 | |
1103 def __init__(self, host): | |
1104 base.CommandBase.__init__(self, host, 'create', use_pubsub=True, pubsub_flags={C.NODE}, help=_(u'create a Pubsub hook')) | |
1105 self.need_loop=True | |
1106 | |
1107 def add_parser_options(self): | |
1108 self.parser.add_argument('-t', '--type', default=u'python', choices=('python', 'python_file', 'python_code'), help=_(u"hook type")) | |
1109 self.parser.add_argument('-P', '--persistent', action='store_true', help=_(u"make hook persistent across restarts")) | |
1110 self.parser.add_argument("hook_arg", type=base.unicode_decoder, help=_(u"argument of the hook (depend of the type)")) | |
1111 | |
1112 @staticmethod | |
1113 def checkArgs(self): | |
1114 if self.args.type == u'python_file': | |
1115 self.args.hook_arg = os.path.abspath(self.args.hook_arg) | |
1116 if not os.path.isfile(self.args.hook_arg): | |
1117 self.parser.error(_(u"{path} is not a file").format(path=self.args.hook_arg)) | |
1118 | |
1119 def start(self): | |
1120 self.checkArgs(self) | |
1121 self.host.bridge.psHookAdd( | |
1122 self.args.service, | |
1123 self.args.node, | |
1124 self.args.type, | |
1125 self.args.hook_arg, | |
1126 self.args.persistent, | |
1127 self.profile, | |
1128 callback=self.host.quit, | |
1129 errback=partial(self.errback, | |
1130 msg=_(u"can't create hook: {}"), | |
1131 exit_code=C.EXIT_BRIDGE_ERRBACK)) | |
1132 | |
1133 | |
1134 class HookDelete(base.CommandBase): | |
1135 | |
1136 def __init__(self, host): | |
1137 base.CommandBase.__init__(self, host, 'delete', use_pubsub=True, pubsub_flags={C.NODE}, help=_(u'delete a Pubsub hook')) | |
1138 self.need_loop=True | |
1139 | |
1140 def add_parser_options(self): | |
1141 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)")) | |
1142 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)")) | |
1143 | |
1144 def psHookRemoveCb(self, nb_deleted): | |
1145 self.disp(_(u'{nb_deleted} hook(s) have been deleted').format( | |
1146 nb_deleted = nb_deleted)) | |
1147 self.host.quit() | |
1148 | |
1149 def start(self): | |
1150 HookCreate.checkArgs(self) | |
1151 self.host.bridge.psHookRemove( | |
1152 self.args.service, | |
1153 self.args.node, | |
1154 self.args.type, | |
1155 self.args.hook_arg, | |
1156 self.profile, | |
1157 callback=self.psHookRemoveCb, | |
1158 errback=partial(self.errback, | |
1159 msg=_(u"can't delete hook: {}"), | |
1160 exit_code=C.EXIT_BRIDGE_ERRBACK)) | |
1161 | |
1162 | |
1163 class HookList(base.CommandBase): | |
1164 | |
1165 def __init__(self, host): | |
1166 base.CommandBase.__init__(self, host, 'list', use_output=C.OUTPUT_LIST_DICT, help=_(u'list hooks of a profile')) | |
1167 self.need_loop = True | |
1168 | |
1169 def add_parser_options(self): | |
1170 pass | |
1171 | |
1172 def psHookListCb(self, data): | |
1173 if not data: | |
1174 self.disp(_(u'No hook found.')) | |
1175 self.output(data) | |
1176 self.host.quit() | |
1177 | |
1178 def start(self): | |
1179 self.host.bridge.psHookList( | |
1180 self.profile, | |
1181 callback=self.psHookListCb, | |
1182 errback=partial(self.errback, | |
1183 msg=_(u"can't list hooks: {}"), | |
1184 exit_code=C.EXIT_BRIDGE_ERRBACK)) | |
1185 | |
1186 | |
1187 class Hook(base.CommandBase): | |
1188 subcommands = (HookCreate, HookDelete, HookList) | |
1189 | |
1190 def __init__(self, host): | |
1191 super(Hook, self).__init__(host, 'hook', use_profile=False, use_verbose=True, help=_('trigger action on Pubsub notifications')) | |
1192 | |
1193 | |
1194 class Pubsub(base.CommandBase): | |
1195 subcommands = (Set, Get, Delete, Edit, Subscribe, Unsubscribe, Subscriptions, Node, Affiliations, Search, Hook, Uri) | |
1196 | |
1197 def __init__(self, host): | |
1198 super(Pubsub, self).__init__(host, 'pubsub', use_profile=False, help=_('PubSub nodes/items management')) |