comparison sat_frontends/jp/cmd_pubsub.py @ 3028:ab2696e34d29

Python 3 port: /!\ this is a huge commit /!\ starting from this commit, SàT is needs Python 3.6+ /!\ SàT maybe be instable or some feature may not work anymore, this will improve with time This patch port backend, bridge and frontends to Python 3. Roughly this has been done this way: - 2to3 tools has been applied (with python 3.7) - all references to python2 have been replaced with python3 (notably shebangs) - fixed files not handled by 2to3 (notably the shell script) - several manual fixes - fixed issues reported by Python 3 that where not handled in Python 2 - replaced "async" with "async_" when needed (it's a reserved word from Python 3.7) - replaced zope's "implements" with @implementer decorator - temporary hack to handle data pickled in database, as str or bytes may be returned, to be checked later - fixed hash comparison for password - removed some code which is not needed anymore with Python 3 - deactivated some code which needs to be checked (notably certificate validation) - tested with jp, fixed reported issues until some basic commands worked - ported Primitivus (after porting dependencies like urwid satext) - more manual fixes
author Goffi <goffi@goffi.org>
date Tue, 13 Aug 2019 19:08:41 +0200
parents b2f323237fce
children fee60f17ebac
comparison
equal deleted inserted replaced
3027:ff5bcb12ae60 3028:ab2696e34d29
16 16
17 # You should have received a copy of the GNU Affero General Public License 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/>. 18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 19
20 20
21 import base 21 from . import base
22 from sat.core.i18n import _ 22 from sat.core.i18n import _
23 from sat.core import exceptions 23 from sat.core import exceptions
24 from sat_frontends.jp.constants import Const as C 24 from sat_frontends.jp.constants import Const as C
25 from sat_frontends.jp import common 25 from sat_frontends.jp import common
26 from sat_frontends.jp import arg_tools 26 from sat_frontends.jp import arg_tools
35 import subprocess 35 import subprocess
36 import sys 36 import sys
37 37
38 __commands__ = ["Pubsub"] 38 __commands__ = ["Pubsub"]
39 39
40 PUBSUB_TMP_DIR = u"pubsub" 40 PUBSUB_TMP_DIR = "pubsub"
41 PUBSUB_SCHEMA_TMP_DIR = PUBSUB_TMP_DIR + "_schema" 41 PUBSUB_SCHEMA_TMP_DIR = PUBSUB_TMP_DIR + "_schema"
42 ALLOWED_SUBSCRIPTIONS_OWNER = ("subscribed", "pending", "none") 42 ALLOWED_SUBSCRIPTIONS_OWNER = ("subscribed", "pending", "none")
43 43
44 # TODO: need to split this class in several modules, plugin should handle subcommands 44 # TODO: need to split this class in several modules, plugin should handle subcommands
45 45
51 host, 51 host,
52 "info", 52 "info",
53 use_output=C.OUTPUT_DICT, 53 use_output=C.OUTPUT_DICT,
54 use_pubsub=True, 54 use_pubsub=True,
55 pubsub_flags={C.NODE}, 55 pubsub_flags={C.NODE},
56 help=_(u"retrieve node configuration"), 56 help=_("retrieve node configuration"),
57 ) 57 )
58 self.need_loop = True 58 self.need_loop = True
59 59
60 def add_parser_options(self): 60 def add_parser_options(self):
61 self.parser.add_argument( 61 self.parser.add_argument(
62 "-k", 62 "-k",
63 "--key", 63 "--key",
64 type=base.unicode_decoder,
65 action="append", 64 action="append",
66 dest="keys", 65 dest="keys",
67 help=_(u"data key to filter"), 66 help=_("data key to filter"),
68 ) 67 )
69 68
70 def removePrefix(self, key): 69 def removePrefix(self, key):
71 return key[7:] if key.startswith(u"pubsub#") else key 70 return key[7:] if key.startswith("pubsub#") else key
72 71
73 def filterKey(self, key): 72 def filterKey(self, key):
74 return any((key == k or key == u"pubsub#" + k) for k in self.args.keys) 73 return any((key == k or key == "pubsub#" + k) for k in self.args.keys)
75 74
76 def psNodeConfigurationGetCb(self, config_dict): 75 def psNodeConfigurationGetCb(self, config_dict):
77 key_filter = (lambda k: True) if not self.args.keys else self.filterKey 76 key_filter = (lambda k: True) if not self.args.keys else self.filterKey
78 config_dict = { 77 config_dict = {
79 self.removePrefix(k): v for k, v in config_dict.iteritems() if key_filter(k) 78 self.removePrefix(k): v for k, v in config_dict.items() if key_filter(k)
80 } 79 }
81 self.output(config_dict) 80 self.output(config_dict)
82 self.host.quit() 81 self.host.quit()
83 82
84 def psNodeConfigurationGetEb(self, failure_): 83 def psNodeConfigurationGetEb(self, failure_):
85 self.disp( 84 self.disp(
86 u"can't get node configuration: {reason}".format(reason=failure_), error=True 85 "can't get node configuration: {reason}".format(reason=failure_), error=True
87 ) 86 )
88 self.host.quit(C.EXIT_BRIDGE_ERRBACK) 87 self.host.quit(C.EXIT_BRIDGE_ERRBACK)
89 88
90 def start(self): 89 def start(self):
91 self.host.bridge.psNodeConfigurationGet( 90 self.host.bridge.psNodeConfigurationGet(
105 "create", 104 "create",
106 use_output=C.OUTPUT_DICT, 105 use_output=C.OUTPUT_DICT,
107 use_pubsub=True, 106 use_pubsub=True,
108 pubsub_flags={C.NODE}, 107 pubsub_flags={C.NODE},
109 use_verbose=True, 108 use_verbose=True,
110 help=_(u"create a node"), 109 help=_("create a node"),
111 ) 110 )
112 self.need_loop = True 111 self.need_loop = True
113 112
114 def add_parser_options(self): 113 def add_parser_options(self):
115 self.parser.add_argument( 114 self.parser.add_argument(
116 "-f", 115 "-f",
117 "--field", 116 "--field",
118 type=base.unicode_decoder,
119 action="append", 117 action="append",
120 nargs=2, 118 nargs=2,
121 dest="fields", 119 dest="fields",
122 default=[], 120 default=[],
123 metavar=(u"KEY", u"VALUE"), 121 metavar=("KEY", "VALUE"),
124 help=_(u"configuration field to set"), 122 help=_("configuration field to set"),
125 ) 123 )
126 self.parser.add_argument( 124 self.parser.add_argument(
127 "-F", 125 "-F",
128 "--full-prefix", 126 "--full-prefix",
129 action="store_true", 127 action="store_true",
130 help=_(u'don\'t prepend "pubsub#" prefix to field names'), 128 help=_('don\'t prepend "pubsub#" prefix to field names'),
131 ) 129 )
132 130
133 def psNodeCreateCb(self, node_id): 131 def psNodeCreateCb(self, node_id):
134 if self.host.verbosity: 132 if self.host.verbosity:
135 announce = _(u"node created successfully: ") 133 announce = _("node created successfully: ")
136 else: 134 else:
137 announce = u"" 135 announce = ""
138 self.disp(announce + node_id) 136 self.disp(announce + node_id)
139 self.host.quit() 137 self.host.quit()
140 138
141 def psNodeCreateEb(self, failure_): 139 def psNodeCreateEb(self, failure_):
142 self.disp(u"can't create: {reason}".format(reason=failure_), error=True) 140 self.disp("can't create: {reason}".format(reason=failure_), error=True)
143 self.host.quit(C.EXIT_BRIDGE_ERRBACK) 141 self.host.quit(C.EXIT_BRIDGE_ERRBACK)
144 142
145 def start(self): 143 def start(self):
146 if not self.args.full_prefix: 144 if not self.args.full_prefix:
147 options = {u"pubsub#" + k: v for k, v in self.args.fields} 145 options = {"pubsub#" + k: v for k, v in self.args.fields}
148 else: 146 else:
149 options = dict(self.args.fields) 147 options = dict(self.args.fields)
150 self.host.bridge.psNodeCreate( 148 self.host.bridge.psNodeCreate(
151 self.args.service, 149 self.args.service,
152 self.args.node, 150 self.args.node,
153 options, 151 options,
154 self.profile, 152 self.profile,
155 callback=self.psNodeCreateCb, 153 callback=self.psNodeCreateCb,
156 errback=partial( 154 errback=partial(
157 self.errback, 155 self.errback,
158 msg=_(u"can't create node: {}"), 156 msg=_("can't create node: {}"),
159 exit_code=C.EXIT_BRIDGE_ERRBACK, 157 exit_code=C.EXIT_BRIDGE_ERRBACK,
160 ), 158 ),
161 ) 159 )
162 160
163 161
167 super(NodePurge, self).__init__( 165 super(NodePurge, self).__init__(
168 host, 166 host,
169 "purge", 167 "purge",
170 use_pubsub=True, 168 use_pubsub=True,
171 pubsub_flags={C.NODE}, 169 pubsub_flags={C.NODE},
172 help=_(u"purge a node (i.e. remove all items from it)"), 170 help=_("purge a node (i.e. remove all items from it)"),
173 ) 171 )
174 self.need_loop = True 172 self.need_loop = True
175 173
176 def add_parser_options(self): 174 def add_parser_options(self):
177 self.parser.add_argument( 175 self.parser.add_argument(
178 "-f", 176 "-f",
179 "--force", 177 "--force",
180 action="store_true", 178 action="store_true",
181 help=_(u"purge node without confirmation"), 179 help=_("purge node without confirmation"),
182 ) 180 )
183 181
184 def psNodePurgeCb(self): 182 def psNodePurgeCb(self):
185 self.disp(_(u"node [{node}] purged successfully").format(node=self.args.node)) 183 self.disp(_("node [{node}] purged successfully").format(node=self.args.node))
186 self.host.quit() 184 self.host.quit()
187 185
188 def start(self): 186 def start(self):
189 if not self.args.force: 187 if not self.args.force:
190 if not self.args.service: 188 if not self.args.service:
191 message = _(u"Are you sure to purge PEP node [{node_id}]? " 189 message = _("Are you sure to purge PEP node [{node_id}]? "
192 u"This will delete ALL items from it!").format( 190 "This will delete ALL items from it!").format(
193 node_id=self.args.node 191 node_id=self.args.node
194 ) 192 )
195 else: 193 else:
196 message = _( 194 message = _(
197 u"Are you sure to delete node [{node_id}] on service [{service}]? " 195 "Are you sure to delete node [{node_id}] on service [{service}]? "
198 u"This will delete ALL items from it!" 196 "This will delete ALL items from it!"
199 ).format(node_id=self.args.node, service=self.args.service) 197 ).format(node_id=self.args.node, service=self.args.service)
200 self.host.confirmOrQuit(message, _(u"node purge cancelled")) 198 self.host.confirmOrQuit(message, _("node purge cancelled"))
201 199
202 self.host.bridge.psNodePurge( 200 self.host.bridge.psNodePurge(
203 self.args.service, 201 self.args.service,
204 self.args.node, 202 self.args.node,
205 self.profile, 203 self.profile,
206 callback=self.psNodePurgeCb, 204 callback=self.psNodePurgeCb,
207 errback=partial( 205 errback=partial(
208 self.errback, 206 self.errback,
209 msg=_(u"can't purge node: {}"), 207 msg=_("can't purge node: {}"),
210 exit_code=C.EXIT_BRIDGE_ERRBACK, 208 exit_code=C.EXIT_BRIDGE_ERRBACK,
211 ), 209 ),
212 ) 210 )
213 211
214 212
218 self, 216 self,
219 host, 217 host,
220 "delete", 218 "delete",
221 use_pubsub=True, 219 use_pubsub=True,
222 pubsub_flags={C.NODE}, 220 pubsub_flags={C.NODE},
223 help=_(u"delete a node"), 221 help=_("delete a node"),
224 ) 222 )
225 self.need_loop = True 223 self.need_loop = True
226 224
227 def add_parser_options(self): 225 def add_parser_options(self):
228 self.parser.add_argument( 226 self.parser.add_argument(
229 "-f", 227 "-f",
230 "--force", 228 "--force",
231 action="store_true", 229 action="store_true",
232 help=_(u"delete node without confirmation"), 230 help=_("delete node without confirmation"),
233 ) 231 )
234 232
235 def psNodeDeleteCb(self): 233 def psNodeDeleteCb(self):
236 self.disp(_(u"node [{node}] deleted successfully").format(node=self.args.node)) 234 self.disp(_("node [{node}] deleted successfully").format(node=self.args.node))
237 self.host.quit() 235 self.host.quit()
238 236
239 def start(self): 237 def start(self):
240 if not self.args.force: 238 if not self.args.force:
241 if not self.args.service: 239 if not self.args.service:
242 message = _(u"Are you sure to delete PEP node [{node_id}] ?").format( 240 message = _("Are you sure to delete PEP node [{node_id}] ?").format(
243 node_id=self.args.node 241 node_id=self.args.node
244 ) 242 )
245 else: 243 else:
246 message = _( 244 message = _(
247 u"Are you sure to delete node [{node_id}] on service [{service}] ?" 245 "Are you sure to delete node [{node_id}] on service [{service}] ?"
248 ).format(node_id=self.args.node, service=self.args.service) 246 ).format(node_id=self.args.node, service=self.args.service)
249 self.host.confirmOrQuit(message, _(u"node deletion cancelled")) 247 self.host.confirmOrQuit(message, _("node deletion cancelled"))
250 248
251 self.host.bridge.psNodeDelete( 249 self.host.bridge.psNodeDelete(
252 self.args.service, 250 self.args.service,
253 self.args.node, 251 self.args.node,
254 self.profile, 252 self.profile,
255 callback=self.psNodeDeleteCb, 253 callback=self.psNodeDeleteCb,
256 errback=partial( 254 errback=partial(
257 self.errback, 255 self.errback,
258 msg=_(u"can't delete node: {}"), 256 msg=_("can't delete node: {}"),
259 exit_code=C.EXIT_BRIDGE_ERRBACK, 257 exit_code=C.EXIT_BRIDGE_ERRBACK,
260 ), 258 ),
261 ) 259 )
262 260
263 261
269 "set", 267 "set",
270 use_output=C.OUTPUT_DICT, 268 use_output=C.OUTPUT_DICT,
271 use_pubsub=True, 269 use_pubsub=True,
272 pubsub_flags={C.NODE}, 270 pubsub_flags={C.NODE},
273 use_verbose=True, 271 use_verbose=True,
274 help=_(u"set node configuration"), 272 help=_("set node configuration"),
275 ) 273 )
276 self.need_loop = True 274 self.need_loop = True
277 275
278 def add_parser_options(self): 276 def add_parser_options(self):
279 self.parser.add_argument( 277 self.parser.add_argument(
280 "-f", 278 "-f",
281 "--field", 279 "--field",
282 type=base.unicode_decoder,
283 action="append", 280 action="append",
284 nargs=2, 281 nargs=2,
285 dest="fields", 282 dest="fields",
286 required=True, 283 required=True,
287 metavar=(u"KEY", u"VALUE"), 284 metavar=("KEY", "VALUE"),
288 help=_(u"configuration field to set (required)"), 285 help=_("configuration field to set (required)"),
289 ) 286 )
290 287
291 def psNodeConfigurationSetCb(self): 288 def psNodeConfigurationSetCb(self):
292 self.disp(_(u"node configuration successful"), 1) 289 self.disp(_("node configuration successful"), 1)
293 self.host.quit() 290 self.host.quit()
294 291
295 def psNodeConfigurationSetEb(self, failure_): 292 def psNodeConfigurationSetEb(self, failure_):
296 self.disp( 293 self.disp(
297 u"can't set node configuration: {reason}".format(reason=failure_), error=True 294 "can't set node configuration: {reason}".format(reason=failure_), error=True
298 ) 295 )
299 self.host.quit(C.EXIT_BRIDGE_ERRBACK) 296 self.host.quit(C.EXIT_BRIDGE_ERRBACK)
300 297
301 def getKeyName(self, k): 298 def getKeyName(self, k):
302 if not k.startswith(u"pubsub#"): 299 if not k.startswith("pubsub#"):
303 return u"pubsub#" + k 300 return "pubsub#" + k
304 else: 301 else:
305 return k 302 return k
306 303
307 def start(self): 304 def start(self):
308 self.host.bridge.psNodeConfigurationSet( 305 self.host.bridge.psNodeConfigurationSet(
321 super(NodeImport, self).__init__( 318 super(NodeImport, self).__init__(
322 host, 319 host,
323 "import", 320 "import",
324 use_pubsub=True, 321 use_pubsub=True,
325 pubsub_flags={C.NODE}, 322 pubsub_flags={C.NODE},
326 help=_(u"import raw XML to a node"), 323 help=_("import raw XML to a node"),
327 ) 324 )
328 self.need_loop = True 325 self.need_loop = True
329 326
330 def add_parser_options(self): 327 def add_parser_options(self):
331 self.parser.add_argument( 328 self.parser.add_argument(
332 "--admin", 329 "--admin",
333 action="store_true", 330 action="store_true",
334 help=_(u"do a pubsub admin request, needed to change publisher"), 331 help=_("do a pubsub admin request, needed to change publisher"),
335 ) 332 )
336 self.parser.add_argument( 333 self.parser.add_argument(
337 "import_file", 334 "import_file",
338 type=file, 335 type=argparse.FileType(),
339 help=_(u"path to the XML file with data to import. The file must contain " 336 help=_("path to the XML file with data to import. The file must contain "
340 u"whole XML of each item to import."), 337 "whole XML of each item to import."),
341 ) 338 )
342 339
343 def psItemsSendCb(self, item_ids): 340 def psItemsSendCb(self, item_ids):
344 self.disp(_(u'items published with id(s) {item_ids}').format( 341 self.disp(_('items published with id(s) {item_ids}').format(
345 item_ids=u', '.join(item_ids))) 342 item_ids=', '.join(item_ids)))
346 self.host.quit() 343 self.host.quit()
347 344
348 def start(self): 345 def start(self):
349 try: 346 try:
350 element, etree = xml_tools.etreeParse(self, self.args.import_file, 347 element, etree = xml_tools.etreeParse(self, self.args.import_file,
362 # TODO: make this more explicit and add an option 359 # TODO: make this more explicit and add an option
363 element[:] = reversed(element) 360 element[:] = reversed(element)
364 361
365 if not all([i.tag == '{http://jabber.org/protocol/pubsub}item' for i in element]): 362 if not all([i.tag == '{http://jabber.org/protocol/pubsub}item' for i in element]):
366 self.disp( 363 self.disp(
367 _(u"You are not using list of pubsub items, we can't import this file"), 364 _("You are not using list of pubsub items, we can't import this file"),
368 error=True) 365 error=True)
369 self.host.quit(C.EXIT_DATA_ERROR) 366 self.host.quit(C.EXIT_DATA_ERROR)
370 367
371 items = [etree.tostring(i, encoding="utf-8") for i in element] 368 items = [etree.tostring(i, encoding="utf-8") for i in element]
372 if self.args.admin: 369 if self.args.admin:
373 self.host.bridge.psAdminItemsSend( 370 self.host.bridge.psAdminItemsSend(
374 self.args.service, 371 self.args.service,
375 self.args.node, 372 self.args.node,
376 items, 373 items,
377 u"", 374 "",
378 self.profile, 375 self.profile,
379 callback=partial(self.psItemsSendCb), 376 callback=partial(self.psItemsSendCb),
380 errback=partial( 377 errback=partial(
381 self.errback, 378 self.errback,
382 msg=_(u"can't send item: {}"), 379 msg=_("can't send item: {}"),
383 exit_code=C.EXIT_BRIDGE_ERRBACK, 380 exit_code=C.EXIT_BRIDGE_ERRBACK,
384 ), 381 ),
385 ) 382 )
386 else: 383 else:
387 self.disp(_(u"Items are imported without using admin mode, publisher can't " 384 self.disp(_("Items are imported without using admin mode, publisher can't "
388 u"be changed")) 385 "be changed"))
389 self.host.bridge.psItemsSend( 386 self.host.bridge.psItemsSend(
390 self.args.service, 387 self.args.service,
391 self.args.node, 388 self.args.node,
392 items, 389 items,
393 u"", 390 "",
394 self.profile, 391 self.profile,
395 callback=partial(self.psItemsSendCb), 392 callback=partial(self.psItemsSendCb),
396 errback=partial( 393 errback=partial(
397 self.errback, 394 self.errback,
398 msg=_(u"can't send item: {}"), 395 msg=_("can't send item: {}"),
399 exit_code=C.EXIT_BRIDGE_ERRBACK, 396 exit_code=C.EXIT_BRIDGE_ERRBACK,
400 ), 397 ),
401 ) 398 )
402 399
403 400
408 host, 405 host,
409 "get", 406 "get",
410 use_output=C.OUTPUT_DICT, 407 use_output=C.OUTPUT_DICT,
411 use_pubsub=True, 408 use_pubsub=True,
412 pubsub_flags={C.NODE}, 409 pubsub_flags={C.NODE},
413 help=_(u"retrieve node affiliations (for node owner)"), 410 help=_("retrieve node affiliations (for node owner)"),
414 ) 411 )
415 self.need_loop = True 412 self.need_loop = True
416 413
417 def add_parser_options(self): 414 def add_parser_options(self):
418 pass 415 pass
421 self.output(affiliations) 418 self.output(affiliations)
422 self.host.quit() 419 self.host.quit()
423 420
424 def psNodeAffiliationsGetEb(self, failure_): 421 def psNodeAffiliationsGetEb(self, failure_):
425 self.disp( 422 self.disp(
426 u"can't get node affiliations: {reason}".format(reason=failure_), error=True 423 "can't get node affiliations: {reason}".format(reason=failure_), error=True
427 ) 424 )
428 self.host.quit(C.EXIT_BRIDGE_ERRBACK) 425 self.host.quit(C.EXIT_BRIDGE_ERRBACK)
429 426
430 def start(self): 427 def start(self):
431 self.host.bridge.psNodeAffiliationsGet( 428 self.host.bridge.psNodeAffiliationsGet(
444 host, 441 host,
445 "set", 442 "set",
446 use_pubsub=True, 443 use_pubsub=True,
447 pubsub_flags={C.NODE}, 444 pubsub_flags={C.NODE},
448 use_verbose=True, 445 use_verbose=True,
449 help=_(u"set affiliations (for node owner)"), 446 help=_("set affiliations (for node owner)"),
450 ) 447 )
451 self.need_loop = True 448 self.need_loop = True
452 449
453 def add_parser_options(self): 450 def add_parser_options(self):
454 # XXX: we use optional argument syntax for a required one because list of list of 2 elements 451 # XXX: we use optional argument syntax for a required one because list of list of 2 elements
457 "-a", 454 "-a",
458 "--affiliation", 455 "--affiliation",
459 dest="affiliations", 456 dest="affiliations",
460 metavar=("JID", "AFFILIATION"), 457 metavar=("JID", "AFFILIATION"),
461 required=True, 458 required=True,
462 type=base.unicode_decoder,
463 action="append", 459 action="append",
464 nargs=2, 460 nargs=2,
465 help=_(u"entity/affiliation couple(s)"), 461 help=_("entity/affiliation couple(s)"),
466 ) 462 )
467 463
468 def psNodeAffiliationsSetCb(self): 464 def psNodeAffiliationsSetCb(self):
469 self.disp(_(u"affiliations have been set"), 1) 465 self.disp(_("affiliations have been set"), 1)
470 self.host.quit() 466 self.host.quit()
471 467
472 def psNodeAffiliationsSetEb(self, failure_): 468 def psNodeAffiliationsSetEb(self, failure_):
473 self.disp( 469 self.disp(
474 u"can't set node affiliations: {reason}".format(reason=failure_), error=True 470 "can't set node affiliations: {reason}".format(reason=failure_), error=True
475 ) 471 )
476 self.host.quit(C.EXIT_BRIDGE_ERRBACK) 472 self.host.quit(C.EXIT_BRIDGE_ERRBACK)
477 473
478 def start(self): 474 def start(self):
479 affiliations = dict(self.args.affiliations) 475 affiliations = dict(self.args.affiliations)
493 def __init__(self, host): 489 def __init__(self, host):
494 super(NodeAffiliations, self).__init__( 490 super(NodeAffiliations, self).__init__(
495 host, 491 host,
496 "affiliations", 492 "affiliations",
497 use_profile=False, 493 use_profile=False,
498 help=_(u"set or retrieve node affiliations"), 494 help=_("set or retrieve node affiliations"),
499 ) 495 )
500 496
501 497
502 class NodeSubscriptionsGet(base.CommandBase): 498 class NodeSubscriptionsGet(base.CommandBase):
503 def __init__(self, host): 499 def __init__(self, host):
506 host, 502 host,
507 "get", 503 "get",
508 use_output=C.OUTPUT_DICT, 504 use_output=C.OUTPUT_DICT,
509 use_pubsub=True, 505 use_pubsub=True,
510 pubsub_flags={C.NODE}, 506 pubsub_flags={C.NODE},
511 help=_(u"retrieve node subscriptions (for node owner)"), 507 help=_("retrieve node subscriptions (for node owner)"),
512 ) 508 )
513 self.need_loop = True 509 self.need_loop = True
514 510
515 def add_parser_options(self): 511 def add_parser_options(self):
516 pass 512 pass
519 self.output(subscriptions) 515 self.output(subscriptions)
520 self.host.quit() 516 self.host.quit()
521 517
522 def psNodeSubscriptionsGetEb(self, failure_): 518 def psNodeSubscriptionsGetEb(self, failure_):
523 self.disp( 519 self.disp(
524 u"can't get node subscriptions: {reason}".format(reason=failure_), error=True 520 "can't get node subscriptions: {reason}".format(reason=failure_), error=True
525 ) 521 )
526 self.host.quit(C.EXIT_BRIDGE_ERRBACK) 522 self.host.quit(C.EXIT_BRIDGE_ERRBACK)
527 523
528 def start(self): 524 def start(self):
529 self.host.bridge.psNodeSubscriptionsGet( 525 self.host.bridge.psNodeSubscriptionsGet(
550 subscription = values.pop(0) 546 subscription = values.pop(0)
551 except IndexError: 547 except IndexError:
552 subscription = "subscribed" 548 subscription = "subscribed"
553 if subscription not in ALLOWED_SUBSCRIPTIONS_OWNER: 549 if subscription not in ALLOWED_SUBSCRIPTIONS_OWNER:
554 parser.error( 550 parser.error(
555 _(u"subscription must be one of {}").format( 551 _("subscription must be one of {}").format(
556 u", ".join(ALLOWED_SUBSCRIPTIONS_OWNER) 552 ", ".join(ALLOWED_SUBSCRIPTIONS_OWNER)
557 ) 553 )
558 ) 554 )
559 dest_dict[jid_s] = subscription 555 dest_dict[jid_s] = subscription
560 556
561 557
566 host, 562 host,
567 "set", 563 "set",
568 use_pubsub=True, 564 use_pubsub=True,
569 pubsub_flags={C.NODE}, 565 pubsub_flags={C.NODE},
570 use_verbose=True, 566 use_verbose=True,
571 help=_(u"set/modify subscriptions (for node owner)"), 567 help=_("set/modify subscriptions (for node owner)"),
572 ) 568 )
573 self.need_loop = True 569 self.need_loop = True
574 570
575 def add_parser_options(self): 571 def add_parser_options(self):
576 # XXX: we use optional argument syntax for a required one because list of list of 2 elements 572 # XXX: we use optional argument syntax for a required one because list of list of 2 elements
581 dest="subscriptions", 577 dest="subscriptions",
582 default={}, 578 default={},
583 nargs="+", 579 nargs="+",
584 metavar=("JID [SUSBSCRIPTION]"), 580 metavar=("JID [SUSBSCRIPTION]"),
585 required=True, 581 required=True,
586 type=base.unicode_decoder,
587 action=StoreSubscriptionAction, 582 action=StoreSubscriptionAction,
588 help=_(u"entity/subscription couple(s)"), 583 help=_("entity/subscription couple(s)"),
589 ) 584 )
590 585
591 def psNodeSubscriptionsSetCb(self): 586 def psNodeSubscriptionsSetCb(self):
592 self.disp(_(u"subscriptions have been set"), 1) 587 self.disp(_("subscriptions have been set"), 1)
593 self.host.quit() 588 self.host.quit()
594 589
595 def psNodeSubscriptionsSetEb(self, failure_): 590 def psNodeSubscriptionsSetEb(self, failure_):
596 self.disp( 591 self.disp(
597 u"can't set node subscriptions: {reason}".format(reason=failure_), error=True 592 "can't set node subscriptions: {reason}".format(reason=failure_), error=True
598 ) 593 )
599 self.host.quit(C.EXIT_BRIDGE_ERRBACK) 594 self.host.quit(C.EXIT_BRIDGE_ERRBACK)
600 595
601 def start(self): 596 def start(self):
602 self.host.bridge.psNodeSubscriptionsSet( 597 self.host.bridge.psNodeSubscriptionsSet(
615 def __init__(self, host): 610 def __init__(self, host):
616 super(NodeSubscriptions, self).__init__( 611 super(NodeSubscriptions, self).__init__(
617 host, 612 host,
618 "subscriptions", 613 "subscriptions",
619 use_profile=False, 614 use_profile=False,
620 help=_(u"get or modify node subscriptions"), 615 help=_("get or modify node subscriptions"),
621 ) 616 )
622 617
623 618
624 class NodeSchemaSet(base.CommandBase): 619 class NodeSchemaSet(base.CommandBase):
625 def __init__(self, host): 620 def __init__(self, host):
628 host, 623 host,
629 "set", 624 "set",
630 use_pubsub=True, 625 use_pubsub=True,
631 pubsub_flags={C.NODE}, 626 pubsub_flags={C.NODE},
632 use_verbose=True, 627 use_verbose=True,
633 help=_(u"set/replace a schema"), 628 help=_("set/replace a schema"),
634 ) 629 )
635 self.need_loop = True 630 self.need_loop = True
636 631
637 def add_parser_options(self): 632 def add_parser_options(self):
638 self.parser.add_argument("schema", help=_(u"schema to set (must be XML)")) 633 self.parser.add_argument("schema", help=_("schema to set (must be XML)"))
639 634
640 def psSchemaSetCb(self): 635 def psSchemaSetCb(self):
641 self.disp(_(u"schema has been set"), 1) 636 self.disp(_("schema has been set"), 1)
642 self.host.quit() 637 self.host.quit()
643 638
644 def start(self): 639 def start(self):
645 self.host.bridge.psSchemaSet( 640 self.host.bridge.psSchemaSet(
646 self.args.service, 641 self.args.service,
648 self.args.schema, 643 self.args.schema,
649 self.profile, 644 self.profile,
650 callback=self.psSchemaSetCb, 645 callback=self.psSchemaSetCb,
651 errback=partial( 646 errback=partial(
652 self.errback, 647 self.errback,
653 msg=_(u"can't set schema: {}"), 648 msg=_("can't set schema: {}"),
654 exit_code=C.EXIT_BRIDGE_ERRBACK, 649 exit_code=C.EXIT_BRIDGE_ERRBACK,
655 ), 650 ),
656 ) 651 )
657 652
658 653
666 "edit", 661 "edit",
667 use_pubsub=True, 662 use_pubsub=True,
668 pubsub_flags={C.NODE}, 663 pubsub_flags={C.NODE},
669 use_draft=True, 664 use_draft=True,
670 use_verbose=True, 665 use_verbose=True,
671 help=_(u"edit a schema"), 666 help=_("edit a schema"),
672 ) 667 )
673 common.BaseEdit.__init__(self, self.host, PUBSUB_SCHEMA_TMP_DIR) 668 common.BaseEdit.__init__(self, self.host, PUBSUB_SCHEMA_TMP_DIR)
674 self.need_loop = True 669 self.need_loop = True
675 670
676 def add_parser_options(self): 671 def add_parser_options(self):
677 pass 672 pass
678 673
679 def psSchemaSetCb(self): 674 def psSchemaSetCb(self):
680 self.disp(_(u"schema has been set"), 1) 675 self.disp(_("schema has been set"), 1)
681 self.host.quit() 676 self.host.quit()
682 677
683 def publish(self, schema): 678 def publish(self, schema):
684 self.host.bridge.psSchemaSet( 679 self.host.bridge.psSchemaSet(
685 self.args.service, 680 self.args.service,
687 schema, 682 schema,
688 self.profile, 683 self.profile,
689 callback=self.psSchemaSetCb, 684 callback=self.psSchemaSetCb,
690 errback=partial( 685 errback=partial(
691 self.errback, 686 self.errback,
692 msg=_(u"can't set schema: {}"), 687 msg=_("can't set schema: {}"),
693 exit_code=C.EXIT_BRIDGE_ERRBACK, 688 exit_code=C.EXIT_BRIDGE_ERRBACK,
694 ), 689 ),
695 ) 690 )
696 691
697 def psSchemaGetCb(self, schema): 692 def psSchemaGetCb(self, schema):
698 try: 693 try:
699 from lxml import etree 694 from lxml import etree
700 except ImportError: 695 except ImportError:
701 self.disp(u'lxml module must be installed to use edit, please install it ' 696 self.disp('lxml module must be installed to use edit, please install it '
702 u'with "pip install lxml"', 697 'with "pip install lxml"',
703 error=True, 698 error=True,
704 ) 699 )
705 self.host.quit(1) 700 self.host.quit(1)
706 content_file_obj, content_file_path = self.getTmpFile() 701 content_file_obj, content_file_path = self.getTmpFile()
707 schema = schema.strip() 702 schema = schema.strip()
720 self.args.node, 715 self.args.node,
721 self.profile, 716 self.profile,
722 callback=self.psSchemaGetCb, 717 callback=self.psSchemaGetCb,
723 errback=partial( 718 errback=partial(
724 self.errback, 719 self.errback,
725 msg=_(u"can't edit schema: {}"), 720 msg=_("can't edit schema: {}"),
726 exit_code=C.EXIT_BRIDGE_ERRBACK, 721 exit_code=C.EXIT_BRIDGE_ERRBACK,
727 ), 722 ),
728 ) 723 )
729 724
730 725
736 "get", 731 "get",
737 use_output=C.OUTPUT_XML, 732 use_output=C.OUTPUT_XML,
738 use_pubsub=True, 733 use_pubsub=True,
739 pubsub_flags={C.NODE}, 734 pubsub_flags={C.NODE},
740 use_verbose=True, 735 use_verbose=True,
741 help=_(u"get schema"), 736 help=_("get schema"),
742 ) 737 )
743 self.need_loop = True 738 self.need_loop = True
744 739
745 def add_parser_options(self): 740 def add_parser_options(self):
746 pass 741 pass
747 742
748 def psSchemaGetCb(self, schema): 743 def psSchemaGetCb(self, schema):
749 if not schema: 744 if not schema:
750 self.disp(_(u"no schema found"), 1) 745 self.disp(_("no schema found"), 1)
751 self.host.quit(1) 746 self.host.quit(1)
752 self.output(schema) 747 self.output(schema)
753 self.host.quit() 748 self.host.quit()
754 749
755 def start(self): 750 def start(self):
758 self.args.node, 753 self.args.node,
759 self.profile, 754 self.profile,
760 callback=self.psSchemaGetCb, 755 callback=self.psSchemaGetCb,
761 errback=partial( 756 errback=partial(
762 self.errback, 757 self.errback,
763 msg=_(u"can't get schema: {}"), 758 msg=_("can't get schema: {}"),
764 exit_code=C.EXIT_BRIDGE_ERRBACK, 759 exit_code=C.EXIT_BRIDGE_ERRBACK,
765 ), 760 ),
766 ) 761 )
767 762
768 763
769 class NodeSchema(base.CommandBase): 764 class NodeSchema(base.CommandBase):
770 subcommands = (NodeSchemaSet, NodeSchemaEdit, NodeSchemaGet) 765 subcommands = (NodeSchemaSet, NodeSchemaEdit, NodeSchemaGet)
771 766
772 def __init__(self, host): 767 def __init__(self, host):
773 super(NodeSchema, self).__init__( 768 super(NodeSchema, self).__init__(
774 host, "schema", use_profile=False, help=_(u"data schema manipulation") 769 host, "schema", use_profile=False, help=_("data schema manipulation")
775 ) 770 )
776 771
777 772
778 class Node(base.CommandBase): 773 class Node(base.CommandBase):
779 subcommands = ( 774 subcommands = (
800 self, 795 self,
801 host, 796 host,
802 "set", 797 "set",
803 use_pubsub=True, 798 use_pubsub=True,
804 pubsub_flags={C.NODE}, 799 pubsub_flags={C.NODE},
805 help=_(u"publish a new item or update an existing one"), 800 help=_("publish a new item or update an existing one"),
806 ) 801 )
807 self.need_loop = True 802 self.need_loop = True
808 803
809 def add_parser_options(self): 804 def add_parser_options(self):
810 self.parser.add_argument( 805 self.parser.add_argument(
811 "item", 806 "item",
812 type=base.unicode_decoder,
813 nargs="?", 807 nargs="?",
814 default=u"", 808 default="",
815 help=_(u"id, URL of the item to update, keyword, or nothing for new item"), 809 help=_("id, URL of the item to update, keyword, or nothing for new item"),
816 ) 810 )
817 811
818 def psItemsSendCb(self, published_id): 812 def psItemsSendCb(self, published_id):
819 if published_id: 813 if published_id:
820 self.disp(u"Item published at {pub_id}".format(pub_id=published_id)) 814 self.disp("Item published at {pub_id}".format(pub_id=published_id))
821 else: 815 else:
822 self.disp(u"Item published") 816 self.disp("Item published")
823 self.host.quit(C.EXIT_OK) 817 self.host.quit(C.EXIT_OK)
824 818
825 def start(self): 819 def start(self):
826 element, etree = xml_tools.etreeParse(self, sys.stdin) 820 element, etree = xml_tools.etreeParse(self, sys.stdin)
827 element = xml_tools.getPayload(self, element) 821 element = xml_tools.getPayload(self, element)
835 {}, 829 {},
836 self.profile, 830 self.profile,
837 callback=self.psItemsSendCb, 831 callback=self.psItemsSendCb,
838 errback=partial( 832 errback=partial(
839 self.errback, 833 self.errback,
840 msg=_(u"can't send item: {}"), 834 msg=_("can't send item: {}"),
841 exit_code=C.EXIT_BRIDGE_ERRBACK, 835 exit_code=C.EXIT_BRIDGE_ERRBACK,
842 ), 836 ),
843 ) 837 )
844 838
845 839
850 host, 844 host,
851 "get", 845 "get",
852 use_output=C.OUTPUT_LIST_XML, 846 use_output=C.OUTPUT_LIST_XML,
853 use_pubsub=True, 847 use_pubsub=True,
854 pubsub_flags={C.NODE, C.MULTI_ITEMS}, 848 pubsub_flags={C.NODE, C.MULTI_ITEMS},
855 help=_(u"get pubsub item(s)"), 849 help=_("get pubsub item(s)"),
856 ) 850 )
857 self.need_loop = True 851 self.need_loop = True
858 852
859 def add_parser_options(self): 853 def add_parser_options(self):
860 self.parser.add_argument( 854 self.parser.add_argument(
861 "-S", 855 "-S",
862 "--sub-id", 856 "--sub-id",
863 type=base.unicode_decoder, 857 default="",
864 default=u"", 858 help=_("subscription id"),
865 help=_(u"subscription id"),
866 ) 859 )
867 #  TODO: a key(s) argument to select keys to display 860 #  TODO: a key(s) argument to select keys to display
868 # TODO: add MAM filters 861 # TODO: add MAM filters
869 862
870 def psItemsGetCb(self, ps_result): 863 def psItemsGetCb(self, ps_result):
871 self.output(ps_result[0]) 864 self.output(ps_result[0])
872 self.host.quit(C.EXIT_OK) 865 self.host.quit(C.EXIT_OK)
873 866
874 def psItemsGetEb(self, failure_): 867 def psItemsGetEb(self, failure_):
875 self.disp(u"can't get pubsub items: {reason}".format(reason=failure_), error=True) 868 self.disp("can't get pubsub items: {reason}".format(reason=failure_), error=True)
876 self.host.quit(C.EXIT_BRIDGE_ERRBACK) 869 self.host.quit(C.EXIT_BRIDGE_ERRBACK)
877 870
878 def start(self): 871 def start(self):
879 self.host.bridge.psItemsGet( 872 self.host.bridge.psItemsGet(
880 self.args.service, 873 self.args.service,
895 self, 888 self,
896 host, 889 host,
897 "delete", 890 "delete",
898 use_pubsub=True, 891 use_pubsub=True,
899 pubsub_flags={C.NODE, C.ITEM, C.SINGLE_ITEM}, 892 pubsub_flags={C.NODE, C.ITEM, C.SINGLE_ITEM},
900 help=_(u"delete an item"), 893 help=_("delete an item"),
901 ) 894 )
902 self.need_loop = True 895 self.need_loop = True
903 896
904 def add_parser_options(self): 897 def add_parser_options(self):
905 self.parser.add_argument( 898 self.parser.add_argument(
906 "-f", "--force", action="store_true", help=_(u"delete without confirmation") 899 "-f", "--force", action="store_true", help=_("delete without confirmation")
907 ) 900 )
908 self.parser.add_argument( 901 self.parser.add_argument(
909 "-N", "--notify", action="store_true", help=_(u"notify deletion") 902 "-N", "--notify", action="store_true", help=_("notify deletion")
910 ) 903 )
911 904
912 def psItemsDeleteCb(self): 905 def psItemsDeleteCb(self):
913 self.disp(_(u"item {item_id} has been deleted").format(item_id=self.args.item)) 906 self.disp(_("item {item_id} has been deleted").format(item_id=self.args.item))
914 self.host.quit(C.EXIT_OK) 907 self.host.quit(C.EXIT_OK)
915 908
916 def start(self): 909 def start(self):
917 if not self.args.item: 910 if not self.args.item:
918 self.parser.error(_(u"You need to specify an item to delete")) 911 self.parser.error(_("You need to specify an item to delete"))
919 if not self.args.force: 912 if not self.args.force:
920 message = _(u"Are you sure to delete item {item_id} ?").format( 913 message = _("Are you sure to delete item {item_id} ?").format(
921 item_id=self.args.item 914 item_id=self.args.item
922 ) 915 )
923 self.host.confirmOrQuit(message, _(u"item deletion cancelled")) 916 self.host.confirmOrQuit(message, _("item deletion cancelled"))
924 self.host.bridge.psRetractItem( 917 self.host.bridge.psRetractItem(
925 self.args.service, 918 self.args.service,
926 self.args.node, 919 self.args.node,
927 self.args.item, 920 self.args.item,
928 self.args.notify, 921 self.args.notify,
929 self.profile, 922 self.profile,
930 callback=self.psItemsDeleteCb, 923 callback=self.psItemsDeleteCb,
931 errback=partial( 924 errback=partial(
932 self.errback, 925 self.errback,
933 msg=_(u"can't delete item: {}"), 926 msg=_("can't delete item: {}"),
934 exit_code=C.EXIT_BRIDGE_ERRBACK, 927 exit_code=C.EXIT_BRIDGE_ERRBACK,
935 ), 928 ),
936 ) 929 )
937 930
938 931
944 "edit", 937 "edit",
945 use_verbose=True, 938 use_verbose=True,
946 use_pubsub=True, 939 use_pubsub=True,
947 pubsub_flags={C.NODE, C.SINGLE_ITEM}, 940 pubsub_flags={C.NODE, C.SINGLE_ITEM},
948 use_draft=True, 941 use_draft=True,
949 help=_(u"edit an existing or new pubsub item"), 942 help=_("edit an existing or new pubsub item"),
950 ) 943 )
951 common.BaseEdit.__init__(self, self.host, PUBSUB_TMP_DIR) 944 common.BaseEdit.__init__(self, self.host, PUBSUB_TMP_DIR)
952 945
953 def add_parser_options(self): 946 def add_parser_options(self):
954 pass 947 pass
965 self.pubsub_item or "", 958 self.pubsub_item or "",
966 {}, 959 {},
967 self.profile, 960 self.profile,
968 ) 961 )
969 if published_id: 962 if published_id:
970 self.disp(u"Item published at {pub_id}".format(pub_id=published_id)) 963 self.disp("Item published at {pub_id}".format(pub_id=published_id))
971 else: 964 else:
972 self.disp(u"Item published") 965 self.disp("Item published")
973 966
974 def getItemData(self, service, node, item): 967 def getItemData(self, service, node, item):
975 try: 968 try:
976 from lxml import etree 969 from lxml import etree
977 except ImportError: 970 except ImportError:
978 self.disp(u'lxml module must be installed to use edit, please install it ' 971 self.disp('lxml module must be installed to use edit, please install it '
979 u'with "pip install lxml"', 972 'with "pip install lxml"',
980 error=True, 973 error=True,
981 ) 974 )
982 self.host.quit(1) 975 self.host.quit(1)
983 items = [item] if item else [] 976 items = [item] if item else []
984 item_raw = self.host.bridge.psItemsGet( 977 item_raw = self.host.bridge.psItemsGet(
988 item_elt = etree.fromstring(item_raw, parser) 981 item_elt = etree.fromstring(item_raw, parser)
989 item_id = item_elt.get("id") 982 item_id = item_elt.get("id")
990 try: 983 try:
991 payload = item_elt[0] 984 payload = item_elt[0]
992 except IndexError: 985 except IndexError:
993 self.disp(_(u"Item has not payload"), 1) 986 self.disp(_("Item has not payload"), 1)
994 return u"" 987 return ""
995 return etree.tostring(payload, encoding="unicode", pretty_print=True), item_id 988 return etree.tostring(payload, encoding="unicode", pretty_print=True), item_id
996 989
997 def start(self): 990 def start(self):
998 self.pubsub_service, self.pubsub_node, self.pubsub_item, content_file_path, content_file_obj = ( 991 self.pubsub_service, self.pubsub_node, self.pubsub_item, content_file_path, content_file_obj = (
999 self.getItemPath() 992 self.getItemPath()
1008 host, 1001 host,
1009 "subscribe", 1002 "subscribe",
1010 use_pubsub=True, 1003 use_pubsub=True,
1011 pubsub_flags={C.NODE}, 1004 pubsub_flags={C.NODE},
1012 use_verbose=True, 1005 use_verbose=True,
1013 help=_(u"subscribe to a node"), 1006 help=_("subscribe to a node"),
1014 ) 1007 )
1015 self.need_loop = True 1008 self.need_loop = True
1016 1009
1017 def add_parser_options(self): 1010 def add_parser_options(self):
1018 pass 1011 pass
1019 1012
1020 def psSubscribeCb(self, sub_id): 1013 def psSubscribeCb(self, sub_id):
1021 self.disp(_(u"subscription done"), 1) 1014 self.disp(_("subscription done"), 1)
1022 if sub_id: 1015 if sub_id:
1023 self.disp(_(u"subscription id: {sub_id}").format(sub_id=sub_id)) 1016 self.disp(_("subscription id: {sub_id}").format(sub_id=sub_id))
1024 self.host.quit() 1017 self.host.quit()
1025 1018
1026 def start(self): 1019 def start(self):
1027 self.host.bridge.psSubscribe( 1020 self.host.bridge.psSubscribe(
1028 self.args.service, 1021 self.args.service,
1030 {}, 1023 {},
1031 self.profile, 1024 self.profile,
1032 callback=self.psSubscribeCb, 1025 callback=self.psSubscribeCb,
1033 errback=partial( 1026 errback=partial(
1034 self.errback, 1027 self.errback,
1035 msg=_(u"can't subscribe to node: {}"), 1028 msg=_("can't subscribe to node: {}"),
1036 exit_code=C.EXIT_BRIDGE_ERRBACK, 1029 exit_code=C.EXIT_BRIDGE_ERRBACK,
1037 ), 1030 ),
1038 ) 1031 )
1039 1032
1040 1033
1047 host, 1040 host,
1048 "unsubscribe", 1041 "unsubscribe",
1049 use_pubsub=True, 1042 use_pubsub=True,
1050 pubsub_flags={C.NODE}, 1043 pubsub_flags={C.NODE},
1051 use_verbose=True, 1044 use_verbose=True,
1052 help=_(u"unsubscribe from a node"), 1045 help=_("unsubscribe from a node"),
1053 ) 1046 )
1054 self.need_loop = True 1047 self.need_loop = True
1055 1048
1056 def add_parser_options(self): 1049 def add_parser_options(self):
1057 pass 1050 pass
1058 1051
1059 def psUnsubscribeCb(self): 1052 def psUnsubscribeCb(self):
1060 self.disp(_(u"subscription removed"), 1) 1053 self.disp(_("subscription removed"), 1)
1061 self.host.quit() 1054 self.host.quit()
1062 1055
1063 def start(self): 1056 def start(self):
1064 self.host.bridge.psUnsubscribe( 1057 self.host.bridge.psUnsubscribe(
1065 self.args.service, 1058 self.args.service,
1066 self.args.node, 1059 self.args.node,
1067 self.profile, 1060 self.profile,
1068 callback=self.psUnsubscribeCb, 1061 callback=self.psUnsubscribeCb,
1069 errback=partial( 1062 errback=partial(
1070 self.errback, 1063 self.errback,
1071 msg=_(u"can't unsubscribe from node: {}"), 1064 msg=_("can't unsubscribe from node: {}"),
1072 exit_code=C.EXIT_BRIDGE_ERRBACK, 1065 exit_code=C.EXIT_BRIDGE_ERRBACK,
1073 ), 1066 ),
1074 ) 1067 )
1075 1068
1076 1069
1080 self, 1073 self,
1081 host, 1074 host,
1082 "subscriptions", 1075 "subscriptions",
1083 use_output=C.OUTPUT_LIST_DICT, 1076 use_output=C.OUTPUT_LIST_DICT,
1084 use_pubsub=True, 1077 use_pubsub=True,
1085 help=_(u"retrieve all subscriptions on a service"), 1078 help=_("retrieve all subscriptions on a service"),
1086 ) 1079 )
1087 self.need_loop = True 1080 self.need_loop = True
1088 1081
1089 def add_parser_options(self): 1082 def add_parser_options(self):
1090 pass 1083 pass
1099 self.args.node, 1092 self.args.node,
1100 self.profile, 1093 self.profile,
1101 callback=self.psSubscriptionsGetCb, 1094 callback=self.psSubscriptionsGetCb,
1102 errback=partial( 1095 errback=partial(
1103 self.errback, 1096 self.errback,
1104 msg=_(u"can't retrieve subscriptions: {}"), 1097 msg=_("can't retrieve subscriptions: {}"),
1105 exit_code=C.EXIT_BRIDGE_ERRBACK, 1098 exit_code=C.EXIT_BRIDGE_ERRBACK,
1106 ), 1099 ),
1107 ) 1100 )
1108 1101
1109 1102
1113 self, 1106 self,
1114 host, 1107 host,
1115 "affiliations", 1108 "affiliations",
1116 use_output=C.OUTPUT_DICT, 1109 use_output=C.OUTPUT_DICT,
1117 use_pubsub=True, 1110 use_pubsub=True,
1118 help=_(u"retrieve all affiliations on a service"), 1111 help=_("retrieve all affiliations on a service"),
1119 ) 1112 )
1120 self.need_loop = True 1113 self.need_loop = True
1121 1114
1122 def add_parser_options(self): 1115 def add_parser_options(self):
1123 pass 1116 pass
1126 self.output(affiliations) 1119 self.output(affiliations)
1127 self.host.quit() 1120 self.host.quit()
1128 1121
1129 def psAffiliationsGetEb(self, failure_): 1122 def psAffiliationsGetEb(self, failure_):
1130 self.disp( 1123 self.disp(
1131 u"can't get node affiliations: {reason}".format(reason=failure_), error=True 1124 "can't get node affiliations: {reason}".format(reason=failure_), error=True
1132 ) 1125 )
1133 self.host.quit(C.EXIT_BRIDGE_ERRBACK) 1126 self.host.quit(C.EXIT_BRIDGE_ERRBACK)
1134 1127
1135 def start(self): 1128 def start(self):
1136 self.host.bridge.psAffiliationsGet( 1129 self.host.bridge.psAffiliationsGet(
1148 This commands checks every items it finds by itself, 1141 This commands checks every items it finds by itself,
1149 so it may be heavy in resources both for server and client 1142 so it may be heavy in resources both for server and client
1150 """ 1143 """
1151 1144
1152 RE_FLAGS = re.MULTILINE | re.UNICODE 1145 RE_FLAGS = re.MULTILINE | re.UNICODE
1153 EXEC_ACTIONS = (u"exec", u"external") 1146 EXEC_ACTIONS = ("exec", "external")
1154 1147
1155 def __init__(self, host): 1148 def __init__(self, host):
1156 # FIXME: C.NO_MAX is not needed here, and this can be globally removed from consts 1149 # FIXME: C.NO_MAX is not needed here, and this can be globally removed from consts
1157 # the only interest is to change the help string, but this can be explained 1150 # the only interest is to change the help string, but this can be explained
1158 # extensively in man pages (max is for each node found) 1151 # extensively in man pages (max is for each node found)
1162 "search", 1155 "search",
1163 use_output=C.OUTPUT_XML, 1156 use_output=C.OUTPUT_XML,
1164 use_pubsub=True, 1157 use_pubsub=True,
1165 pubsub_flags={C.MULTI_ITEMS, C.NO_MAX}, 1158 pubsub_flags={C.MULTI_ITEMS, C.NO_MAX},
1166 use_verbose=True, 1159 use_verbose=True,
1167 help=_(u"search items corresponding to filters"), 1160 help=_("search items corresponding to filters"),
1168 ) 1161 )
1169 self.need_loop = True 1162 self.need_loop = True
1170 1163
1171 @property 1164 @property
1172 def etree(self): 1165 def etree(self):
1176 1169
1177 self._etree = etree 1170 self._etree = etree
1178 return self._etree 1171 return self._etree
1179 1172
1180 def filter_opt(self, value, type_): 1173 def filter_opt(self, value, type_):
1181 value = base.unicode_decoder(value)
1182 return (type_, value) 1174 return (type_, value)
1183 1175
1184 def filter_flag(self, value, type_): 1176 def filter_flag(self, value, type_):
1185 value = C.bool(value) 1177 value = C.bool(value)
1186 return (type_, value) 1178 return (type_, value)
1189 self.parser.add_argument( 1181 self.parser.add_argument(
1190 "-D", 1182 "-D",
1191 "--max-depth", 1183 "--max-depth",
1192 type=int, 1184 type=int,
1193 default=0, 1185 default=0,
1194 help=_(u"maximum depth of recursion (will search linked nodes if > 0, " 1186 help=_("maximum depth of recursion (will search linked nodes if > 0, "
1195 u"DEFAULT: 0)"), 1187 "DEFAULT: 0)"),
1196 ) 1188 )
1197 self.parser.add_argument( 1189 self.parser.add_argument(
1198 "-M", 1190 "-M",
1199 "--node-max", 1191 "--node-max",
1200 type=int, 1192 type=int,
1201 default=30, 1193 default=30,
1202 help=_(u"maximum number of items to get per node ({} to get all items, " 1194 help=_("maximum number of items to get per node ({} to get all items, "
1203 u"DEFAULT: 30)".format( C.NO_LIMIT)), 1195 "DEFAULT: 30)".format( C.NO_LIMIT)),
1204 ) 1196 )
1205 self.parser.add_argument( 1197 self.parser.add_argument(
1206 "-N", 1198 "-N",
1207 "--namespace", 1199 "--namespace",
1208 action="append", 1200 action="append",
1209 nargs=2, 1201 nargs=2,
1210 default=[], 1202 default=[],
1211 metavar="NAME NAMESPACE", 1203 metavar="NAME NAMESPACE",
1212 help=_(u"namespace to use for xpath"), 1204 help=_("namespace to use for xpath"),
1213 ) 1205 )
1214 1206
1215 # filters 1207 # filters
1216 filter_text = partial(self.filter_opt, type_=u"text") 1208 filter_text = partial(self.filter_opt, type_="text")
1217 filter_re = partial(self.filter_opt, type_=u"regex") 1209 filter_re = partial(self.filter_opt, type_="regex")
1218 filter_xpath = partial(self.filter_opt, type_=u"xpath") 1210 filter_xpath = partial(self.filter_opt, type_="xpath")
1219 filter_python = partial(self.filter_opt, type_=u"python") 1211 filter_python = partial(self.filter_opt, type_="python")
1220 filters = self.parser.add_argument_group( 1212 filters = self.parser.add_argument_group(
1221 _(u"filters"), 1213 _("filters"),
1222 _(u"only items corresponding to following filters will be kept"), 1214 _("only items corresponding to following filters will be kept"),
1223 ) 1215 )
1224 filters.add_argument( 1216 filters.add_argument(
1225 "-t", 1217 "-t",
1226 "--text", 1218 "--text",
1227 action="append", 1219 action="append",
1228 dest="filters", 1220 dest="filters",
1229 type=filter_text, 1221 type=filter_text,
1230 metavar="TEXT", 1222 metavar="TEXT",
1231 help=_(u"full text filter, item must contain this string (XML included)"), 1223 help=_("full text filter, item must contain this string (XML included)"),
1232 ) 1224 )
1233 filters.add_argument( 1225 filters.add_argument(
1234 "-r", 1226 "-r",
1235 "--regex", 1227 "--regex",
1236 action="append", 1228 action="append",
1237 dest="filters", 1229 dest="filters",
1238 type=filter_re, 1230 type=filter_re,
1239 metavar="EXPRESSION", 1231 metavar="EXPRESSION",
1240 help=_(u"like --text but using a regular expression"), 1232 help=_("like --text but using a regular expression"),
1241 ) 1233 )
1242 filters.add_argument( 1234 filters.add_argument(
1243 "-x", 1235 "-x",
1244 "--xpath", 1236 "--xpath",
1245 action="append", 1237 action="append",
1246 dest="filters", 1238 dest="filters",
1247 type=filter_xpath, 1239 type=filter_xpath,
1248 metavar="XPATH", 1240 metavar="XPATH",
1249 help=_(u"filter items which has elements matching this xpath"), 1241 help=_("filter items which has elements matching this xpath"),
1250 ) 1242 )
1251 filters.add_argument( 1243 filters.add_argument(
1252 "-P", 1244 "-P",
1253 "--python", 1245 "--python",
1254 action="append", 1246 action="append",
1255 dest="filters", 1247 dest="filters",
1256 type=filter_python, 1248 type=filter_python,
1257 metavar="PYTHON_CODE", 1249 metavar="PYTHON_CODE",
1258 help=_(u'Python expression which much return a bool (True to keep item, ' 1250 help=_('Python expression which much return a bool (True to keep item, '
1259 u'False to reject it). "item" is raw text item, "item_xml" is ' 1251 'False to reject it). "item" is raw text item, "item_xml" is '
1260 u'lxml\'s etree.Element' 1252 'lxml\'s etree.Element'
1261 ), 1253 ),
1262 ) 1254 )
1263 1255
1264 # filters flags 1256 # filters flags
1265 flag_case = partial(self.filter_flag, type_=u"ignore-case") 1257 flag_case = partial(self.filter_flag, type_="ignore-case")
1266 flag_invert = partial(self.filter_flag, type_=u"invert") 1258 flag_invert = partial(self.filter_flag, type_="invert")
1267 flag_dotall = partial(self.filter_flag, type_=u"dotall") 1259 flag_dotall = partial(self.filter_flag, type_="dotall")
1268 flag_matching = partial(self.filter_flag, type_=u"only-matching") 1260 flag_matching = partial(self.filter_flag, type_="only-matching")
1269 flags = self.parser.add_argument_group( 1261 flags = self.parser.add_argument_group(
1270 _(u"filters flags"), 1262 _("filters flags"),
1271 _(u"filters modifiers (change behaviour of following filters)"), 1263 _("filters modifiers (change behaviour of following filters)"),
1272 ) 1264 )
1273 flags.add_argument( 1265 flags.add_argument(
1274 "-C", 1266 "-C",
1275 "--ignore-case", 1267 "--ignore-case",
1276 action="append", 1268 action="append",
1277 dest="filters", 1269 dest="filters",
1278 type=flag_case, 1270 type=flag_case,
1279 const=("ignore-case", True), 1271 const=("ignore-case", True),
1280 nargs="?", 1272 nargs="?",
1281 metavar="BOOLEAN", 1273 metavar="BOOLEAN",
1282 help=_(u"(don't) ignore case in following filters (DEFAULT: case sensitive)"), 1274 help=_("(don't) ignore case in following filters (DEFAULT: case sensitive)"),
1283 ) 1275 )
1284 flags.add_argument( 1276 flags.add_argument(
1285 "-I", 1277 "-I",
1286 "--invert", 1278 "--invert",
1287 action="append", 1279 action="append",
1288 dest="filters", 1280 dest="filters",
1289 type=flag_invert, 1281 type=flag_invert,
1290 const=("invert", True), 1282 const=("invert", True),
1291 nargs="?", 1283 nargs="?",
1292 metavar="BOOLEAN", 1284 metavar="BOOLEAN",
1293 help=_(u"(don't) invert effect of following filters (DEFAULT: don't invert)"), 1285 help=_("(don't) invert effect of following filters (DEFAULT: don't invert)"),
1294 ) 1286 )
1295 flags.add_argument( 1287 flags.add_argument(
1296 "-A", 1288 "-A",
1297 "--dot-all", 1289 "--dot-all",
1298 action="append", 1290 action="append",
1299 dest="filters", 1291 dest="filters",
1300 type=flag_dotall, 1292 type=flag_dotall,
1301 const=("dotall", True), 1293 const=("dotall", True),
1302 nargs="?", 1294 nargs="?",
1303 metavar="BOOLEAN", 1295 metavar="BOOLEAN",
1304 help=_(u"(don't) use DOTALL option for regex (DEFAULT: don't use)"), 1296 help=_("(don't) use DOTALL option for regex (DEFAULT: don't use)"),
1305 ) 1297 )
1306 flags.add_argument( 1298 flags.add_argument(
1307 "-k", 1299 "-k",
1308 "--only-matching", 1300 "--only-matching",
1309 action="append", 1301 action="append",
1310 dest="filters", 1302 dest="filters",
1311 type=flag_matching, 1303 type=flag_matching,
1312 const=("only-matching", True), 1304 const=("only-matching", True),
1313 nargs="?", 1305 nargs="?",
1314 metavar="BOOLEAN", 1306 metavar="BOOLEAN",
1315 help=_(u"keep only the matching part of the item"), 1307 help=_("keep only the matching part of the item"),
1316 ) 1308 )
1317 1309
1318 # action 1310 # action
1319 self.parser.add_argument( 1311 self.parser.add_argument(
1320 "action", 1312 "action",
1321 default="print", 1313 default="print",
1322 nargs="?", 1314 nargs="?",
1323 choices=("print", "exec", "external"), 1315 choices=("print", "exec", "external"),
1324 help=_(u"action to do on found items (DEFAULT: print)"), 1316 help=_("action to do on found items (DEFAULT: print)"),
1325 ) 1317 )
1326 self.parser.add_argument("command", nargs=argparse.REMAINDER) 1318 self.parser.add_argument("command", nargs=argparse.REMAINDER)
1327 1319
1328 def psItemsGetEb(self, failure_, service, node): 1320 def psItemsGetEb(self, failure_, service, node):
1329 self.disp( 1321 self.disp(
1330 u"can't get pubsub items at {service} (node: {node}): {reason}".format( 1322 "can't get pubsub items at {service} (node: {node}): {reason}".format(
1331 service=service, node=node, reason=failure_ 1323 service=service, node=node, reason=failure_
1332 ), 1324 ),
1333 error=True, 1325 error=True,
1334 ) 1326 )
1335 self.to_get -= 1 1327 self.to_get -= 1
1355 1347
1356 @param found_nodes(list[unicode]): found_nodes 1348 @param found_nodes(list[unicode]): found_nodes
1357 this list will be filled while xmpp: URIs are discovered 1349 this list will be filled while xmpp: URIs are discovered
1358 """ 1350 """
1359 url = match.group(0) 1351 url = match.group(0)
1360 if url.startswith(u"xmpp"): 1352 if url.startswith("xmpp"):
1361 try: 1353 try:
1362 url_data = uri.parseXMPPUri(url) 1354 url_data = uri.parseXMPPUri(url)
1363 except ValueError: 1355 except ValueError:
1364 return 1356 return
1365 if url_data[u"type"] == u"pubsub": 1357 if url_data["type"] == "pubsub":
1366 found_node = {u"service": url_data[u"path"], u"node": url_data[u"node"]} 1358 found_node = {"service": url_data["path"], "node": url_data["node"]}
1367 if u"item" in url_data: 1359 if "item" in url_data:
1368 found_node[u"item"] = url_data[u"item"] 1360 found_node["item"] = url_data["item"]
1369 found_nodes.append(found_node) 1361 found_nodes.append(found_node)
1370 1362
1371 def getSubNodes(self, item, depth): 1363 def getSubNodes(self, item, depth):
1372 """look for pubsub URIs in item, and getItems on the linked nodes""" 1364 """look for pubsub URIs in item, and getItems on the linked nodes"""
1373 found_nodes = [] 1365 found_nodes = []
1374 checkURI = partial(self._checkPubsubURL, found_nodes=found_nodes) 1366 checkURI = partial(self._checkPubsubURL, found_nodes=found_nodes)
1375 strings.RE_URL.sub(checkURI, item) 1367 strings.RE_URL.sub(checkURI, item)
1376 for data in found_nodes: 1368 for data in found_nodes:
1377 self.getItems( 1369 self.getItems(
1378 depth + 1, 1370 depth + 1,
1379 data[u"service"], 1371 data["service"],
1380 data[u"node"], 1372 data["node"],
1381 [data[u"item"]] if u"item" in data else [], 1373 [data["item"]] if "item" in data else [],
1382 ) 1374 )
1383 1375
1384 def parseXml(self, item): 1376 def parseXml(self, item):
1385 try: 1377 try:
1386 return self.etree.fromstring(item) 1378 return self.etree.fromstring(item)
1387 except self.etree.XMLSyntaxError: 1379 except self.etree.XMLSyntaxError:
1388 self.disp( 1380 self.disp(
1389 _(u"item doesn't looks like XML, you have probably used --only-matching " 1381 _("item doesn't looks like XML, you have probably used --only-matching "
1390 u"somewhere before and we have no more XML"), 1382 "somewhere before and we have no more XML"),
1391 error=True, 1383 error=True,
1392 ) 1384 )
1393 self.host.quit(C.EXIT_BAD_ARG) 1385 self.host.quit(C.EXIT_BAD_ARG)
1394 1386
1395 def filter(self, item): 1387 def filter(self, item):
1408 for type_, value in self.args.filters: 1400 for type_, value in self.args.filters:
1409 keep = True 1401 keep = True
1410 1402
1411 ## filters 1403 ## filters
1412 1404
1413 if type_ == u"text": 1405 if type_ == "text":
1414 if ignore_case: 1406 if ignore_case:
1415 if value.lower() not in item.lower(): 1407 if value.lower() not in item.lower():
1416 keep = False 1408 keep = False
1417 else: 1409 else:
1418 if value not in item: 1410 if value not in item:
1420 if keep and only_matching: 1412 if keep and only_matching:
1421 # doesn't really make sens to keep a fixed string 1413 # doesn't really make sens to keep a fixed string
1422 # so we raise an error 1414 # so we raise an error
1423 self.host.disp( 1415 self.host.disp(
1424 _( 1416 _(
1425 u"--only-matching used with fixed --text string, are you sure?" 1417 "--only-matching used with fixed --text string, are you sure?"
1426 ), 1418 ),
1427 error=True, 1419 error=True,
1428 ) 1420 )
1429 self.host.quit(C.EXIT_BAD_ARG) 1421 self.host.quit(C.EXIT_BAD_ARG)
1430 elif type_ == u"regex": 1422 elif type_ == "regex":
1431 flags = self.RE_FLAGS 1423 flags = self.RE_FLAGS
1432 if ignore_case: 1424 if ignore_case:
1433 flags |= re.IGNORECASE 1425 flags |= re.IGNORECASE
1434 if dotall: 1426 if dotall:
1435 flags |= re.DOTALL 1427 flags |= re.DOTALL
1436 match = re.search(value, item, flags) 1428 match = re.search(value, item, flags)
1437 keep = match != None 1429 keep = match != None
1438 if keep and only_matching: 1430 if keep and only_matching:
1439 item = match.group() 1431 item = match.group()
1440 item_xml = None 1432 item_xml = None
1441 elif type_ == u"xpath": 1433 elif type_ == "xpath":
1442 if item_xml is None: 1434 if item_xml is None:
1443 item_xml = self.parseXml(item) 1435 item_xml = self.parseXml(item)
1444 try: 1436 try:
1445 elts = item_xml.xpath(value, namespaces=self.args.namespace) 1437 elts = item_xml.xpath(value, namespaces=self.args.namespace)
1446 except self.etree.XPathEvalError as e: 1438 except self.etree.XPathEvalError as e:
1447 self.disp( 1439 self.disp(
1448 _(u"can't use xpath: {reason}").format(reason=e), error=True 1440 _("can't use xpath: {reason}").format(reason=e), error=True
1449 ) 1441 )
1450 self.host.quit(C.EXIT_BAD_ARG) 1442 self.host.quit(C.EXIT_BAD_ARG)
1451 keep = bool(elts) 1443 keep = bool(elts)
1452 if keep and only_matching: 1444 if keep and only_matching:
1453 item_xml = elts[0] 1445 item_xml = elts[0]
1454 try: 1446 try:
1455 item = self.etree.tostring(item_xml, encoding="unicode") 1447 item = self.etree.tostring(item_xml, encoding="unicode")
1456 except TypeError: 1448 except TypeError:
1457 # we have a string only, not an element 1449 # we have a string only, not an element
1458 item = unicode(item_xml) 1450 item = str(item_xml)
1459 item_xml = None 1451 item_xml = None
1460 elif type_ == u"python": 1452 elif type_ == "python":
1461 if item_xml is None: 1453 if item_xml is None:
1462 item_xml = self.parseXml(item) 1454 item_xml = self.parseXml(item)
1463 cmd_ns = {u"item": item, u"item_xml": item_xml} 1455 cmd_ns = {"item": item, "item_xml": item_xml}
1464 try: 1456 try:
1465 keep = eval(value, cmd_ns) 1457 keep = eval(value, cmd_ns)
1466 except SyntaxError as e: 1458 except SyntaxError as e:
1467 self.disp(unicode(e), error=True) 1459 self.disp(str(e), error=True)
1468 self.host.quit(C.EXIT_BAD_ARG) 1460 self.host.quit(C.EXIT_BAD_ARG)
1469 1461
1470 ## flags 1462 ## flags
1471 1463
1472 elif type_ == u"ignore-case": 1464 elif type_ == "ignore-case":
1473 ignore_case = value 1465 ignore_case = value
1474 elif type_ == u"invert": 1466 elif type_ == "invert":
1475 invert = value 1467 invert = value
1476 #  we need to continue, else loop would end here 1468 #  we need to continue, else loop would end here
1477 continue 1469 continue
1478 elif type_ == u"dotall": 1470 elif type_ == "dotall":
1479 dotall = value 1471 dotall = value
1480 elif type_ == u"only-matching": 1472 elif type_ == "only-matching":
1481 only_matching = value 1473 only_matching = value
1482 else: 1474 else:
1483 raise exceptions.InternalError( 1475 raise exceptions.InternalError(
1484 _(u"unknown filter type {type}").format(type=type_) 1476 _("unknown filter type {type}").format(type=type_)
1485 ) 1477 )
1486 1478
1487 if invert: 1479 if invert:
1488 keep = not keep 1480 keep = not keep
1489 if not keep: 1481 if not keep:
1495 """called when item has been kepts and the action need to be done 1487 """called when item has been kepts and the action need to be done
1496 1488
1497 @param item(unicode): accepted item 1489 @param item(unicode): accepted item
1498 """ 1490 """
1499 action = self.args.action 1491 action = self.args.action
1500 if action == u"print" or self.host.verbosity > 0: 1492 if action == "print" or self.host.verbosity > 0:
1501 try: 1493 try:
1502 self.output(item) 1494 self.output(item)
1503 except self.etree.XMLSyntaxError: 1495 except self.etree.XMLSyntaxError:
1504 # item is not valid XML, but a string 1496 # item is not valid XML, but a string
1505 # can happen when --only-matching is used 1497 # can happen when --only-matching is used
1506 self.disp(item) 1498 self.disp(item)
1507 if action in self.EXEC_ACTIONS: 1499 if action in self.EXEC_ACTIONS:
1508 item_elt = self.parseXml(item) 1500 item_elt = self.parseXml(item)
1509 if action == u"exec": 1501 if action == "exec":
1510 use = { 1502 use = {
1511 "service": metadata[u"service"], 1503 "service": metadata["service"],
1512 "node": metadata[u"node"], 1504 "node": metadata["node"],
1513 "item": item_elt.get("id"), 1505 "item": item_elt.get("id"),
1514 "profile": self.profile, 1506 "profile": self.profile,
1515 } 1507 }
1516 # we need to send a copy of self.args.command 1508 # we need to send a copy of self.args.command
1517 # else it would be modified 1509 # else it would be modified
1521 cmd_args = sys.argv[0:1] + parser_args + use_args 1513 cmd_args = sys.argv[0:1] + parser_args + use_args
1522 else: 1514 else:
1523 cmd_args = self.args.command 1515 cmd_args = self.args.command
1524 1516
1525 self.disp( 1517 self.disp(
1526 u"COMMAND: {command}".format( 1518 "COMMAND: {command}".format(
1527 command=u" ".join([arg_tools.escape(a) for a in cmd_args]) 1519 command=" ".join([arg_tools.escape(a) for a in cmd_args])
1528 ), 1520 ),
1529 2, 1521 2,
1530 ) 1522 )
1531 if action == u"exec": 1523 if action == "exec":
1532 ret = subprocess.call(cmd_args) 1524 ret = subprocess.call(cmd_args)
1533 else: 1525 else:
1534 p = subprocess.Popen(cmd_args, stdin=subprocess.PIPE) 1526 p = subprocess.Popen(cmd_args, stdin=subprocess.PIPE)
1535 p.communicate(item.encode("utf-8")) 1527 p.communicate(item.encode("utf-8"))
1536 ret = p.wait() 1528 ret = p.wait()
1537 if ret != 0: 1529 if ret != 0:
1538 self.disp( 1530 self.disp(
1539 A.color( 1531 A.color(
1540 C.A_FAILURE, 1532 C.A_FAILURE,
1541 _(u"executed command failed with exit code {code}").format( 1533 _("executed command failed with exit code {code}").format(
1542 code=ret 1534 code=ret
1543 ), 1535 ),
1544 ) 1536 )
1545 ) 1537 )
1546 1538
1571 1563
1572 def start(self): 1564 def start(self):
1573 if self.args.command: 1565 if self.args.command:
1574 if self.args.action not in self.EXEC_ACTIONS: 1566 if self.args.action not in self.EXEC_ACTIONS:
1575 self.parser.error( 1567 self.parser.error(
1576 _(u"Command can only be used with {actions} actions").format( 1568 _("Command can only be used with {actions} actions").format(
1577 actions=u", ".join(self.EXEC_ACTIONS) 1569 actions=", ".join(self.EXEC_ACTIONS)
1578 ) 1570 )
1579 ) 1571 )
1580 else: 1572 else:
1581 if self.args.action in self.EXEC_ACTIONS: 1573 if self.args.action in self.EXEC_ACTIONS:
1582 self.parser.error(_(u"you need to specify a command to execute")) 1574 self.parser.error(_("you need to specify a command to execute"))
1583 if not self.args.node: 1575 if not self.args.node:
1584 # TODO: handle get service affiliations when node is not set 1576 # TODO: handle get service affiliations when node is not set
1585 self.parser.error(_(u"empty node is not handled yet")) 1577 self.parser.error(_("empty node is not handled yet"))
1586 # to_get is increased on each get and decreased on each answer 1578 # to_get is increased on each get and decreased on each answer
1587 # when it reach 0 again, the command is finished 1579 # when it reach 0 again, the command is finished
1588 self.to_get = 0 1580 self.to_get = 0
1589 self._etree = None 1581 self._etree = None
1590 if self.args.filters is None: 1582 if self.args.filters is None:
1601 self, 1593 self,
1602 host, 1594 host,
1603 "transform", 1595 "transform",
1604 use_pubsub=True, 1596 use_pubsub=True,
1605 pubsub_flags={C.NODE, C.MULTI_ITEMS}, 1597 pubsub_flags={C.NODE, C.MULTI_ITEMS},
1606 help=_(u"modify items of a node using an external command/script"), 1598 help=_("modify items of a node using an external command/script"),
1607 ) 1599 )
1608 self.need_loop = True 1600 self.need_loop = True
1609 1601
1610 def add_parser_options(self): 1602 def add_parser_options(self):
1611 self.parser.add_argument( 1603 self.parser.add_argument(
1612 "--apply", 1604 "--apply",
1613 action="store_true", 1605 action="store_true",
1614 help=_(u"apply transformation (DEFAULT: do a dry run)"), 1606 help=_("apply transformation (DEFAULT: do a dry run)"),
1615 ) 1607 )
1616 self.parser.add_argument( 1608 self.parser.add_argument(
1617 "--admin", 1609 "--admin",
1618 action="store_true", 1610 action="store_true",
1619 help=_(u"do a pubsub admin request, needed to change publisher"), 1611 help=_("do a pubsub admin request, needed to change publisher"),
1620 ) 1612 )
1621 self.parser.add_argument( 1613 self.parser.add_argument(
1622 "-I", 1614 "-I",
1623 "--ignore_errors", 1615 "--ignore_errors",
1624 action="store_true", 1616 action="store_true",
1625 help=_( 1617 help=_(
1626 u"if command return a non zero exit code, ignore the item and continue"), 1618 "if command return a non zero exit code, ignore the item and continue"),
1627 ) 1619 )
1628 self.parser.add_argument( 1620 self.parser.add_argument(
1629 "-A", 1621 "-A",
1630 "--all", 1622 "--all",
1631 action="store_true", 1623 action="store_true",
1632 help=_(u"get all items by looping over all pages using RSM") 1624 help=_("get all items by looping over all pages using RSM")
1633 ) 1625 )
1634 self.parser.add_argument( 1626 self.parser.add_argument(
1635 "command_path", 1627 "command_path",
1636 help=_(u"path to the command to use. Will be called repetitivly with an " 1628 help=_("path to the command to use. Will be called repetitivly with an "
1637 u"item as input. Output (full item XML) will be used as new one. " 1629 "item as input. Output (full item XML) will be used as new one. "
1638 u'Return "DELETE" string to delete the item, and "SKIP" to ignore it'), 1630 'Return "DELETE" string to delete the item, and "SKIP" to ignore it'),
1639 ) 1631 )
1640 1632
1641 def psItemsSendCb(self, item_ids, metadata): 1633 def psItemsSendCb(self, item_ids, metadata):
1642 if item_ids: 1634 if item_ids:
1643 self.disp(_(u'items published with ids {item_ids}').format( 1635 self.disp(_('items published with ids {item_ids}').format(
1644 item_ids=u', '.join(item_ids))) 1636 item_ids=', '.join(item_ids)))
1645 else: 1637 else:
1646 self.disp(_(u'items published')) 1638 self.disp(_('items published'))
1647 if self.args.all: 1639 if self.args.all:
1648 return self.handleNextPage(metadata) 1640 return self.handleNextPage(metadata)
1649 else: 1641 else:
1650 self.host.quit() 1642 self.host.quit()
1651 1643
1654 1646
1655 use to handle --all option 1647 use to handle --all option
1656 @param metadata(dict): metadata as returned by psItemsGet 1648 @param metadata(dict): metadata as returned by psItemsGet
1657 """ 1649 """
1658 try: 1650 try:
1659 last = metadata[u'rsm_last'] 1651 last = metadata['rsm_last']
1660 index = int(metadata[u'rsm_index']) 1652 index = int(metadata['rsm_index'])
1661 count = int(metadata[u'rsm_count']) 1653 count = int(metadata['rsm_count'])
1662 except KeyError: 1654 except KeyError:
1663 self.disp(_(u"Can't retrieve all items, RSM metadata not available"), 1655 self.disp(_("Can't retrieve all items, RSM metadata not available"),
1664 error=True) 1656 error=True)
1665 self.host.quit(C.EXIT_MISSING_FEATURE) 1657 self.host.quit(C.EXIT_MISSING_FEATURE)
1666 except ValueError as e: 1658 except ValueError as e:
1667 self.disp(_(u"Can't retrieve all items, bad RSM metadata: {msg}") 1659 self.disp(_("Can't retrieve all items, bad RSM metadata: {msg}")
1668 .format(msg=e), error=True) 1660 .format(msg=e), error=True)
1669 self.host.quit(C.EXIT_ERROR) 1661 self.host.quit(C.EXIT_ERROR)
1670 1662
1671 if index + self.args.rsm_max >= count: 1663 if index + self.args.rsm_max >= count:
1672 self.disp(_(u'All items transformed')) 1664 self.disp(_('All items transformed'))
1673 self.host.quit(0) 1665 self.host.quit(0)
1674 1666
1675 self.disp(_(u'Retrieving next page ({page_idx}/{page_total})').format( 1667 self.disp(_('Retrieving next page ({page_idx}/{page_total})').format(
1676 page_idx = int(index/self.args.rsm_max) + 1, 1668 page_idx = int(index/self.args.rsm_max) + 1,
1677 page_total = int(count/self.args.rsm_max), 1669 page_total = int(count/self.args.rsm_max),
1678 ) 1670 )
1679 ) 1671 )
1680 1672
1681 extra = self.getPubsubExtra() 1673 extra = self.getPubsubExtra()
1682 extra[u'rsm_after'] = last 1674 extra['rsm_after'] = last
1683 self.host.bridge.psItemsGet( 1675 self.host.bridge.psItemsGet(
1684 self.args.service, 1676 self.args.service,
1685 self.args.node, 1677 self.args.node,
1686 self.args.rsm_max, 1678 self.args.rsm_max,
1687 self.args.items, 1679 self.args.items,
1689 extra, 1681 extra,
1690 self.profile, 1682 self.profile,
1691 callback=self.psItemsGetCb, 1683 callback=self.psItemsGetCb,
1692 errback=partial( 1684 errback=partial(
1693 self.errback, 1685 self.errback,
1694 msg=_(u"can't retrieve items: {}"), 1686 msg=_("can't retrieve items: {}"),
1695 exit_code=C.EXIT_BRIDGE_ERRBACK, 1687 exit_code=C.EXIT_BRIDGE_ERRBACK,
1696 ), 1688 ),
1697 ) 1689 )
1698 1690
1699 def psItemsGetCb(self, ps_result): 1691 def psItemsGetCb(self, ps_result):
1706 # to avoid infinite loop 1698 # to avoid infinite loop
1707 item_elt, __ = xml_tools.etreeParse(self, item) 1699 item_elt, __ = xml_tools.etreeParse(self, item)
1708 item_id = item_elt.get('id') 1700 item_id = item_elt.get('id')
1709 if item_id in self.items_ids: 1701 if item_id in self.items_ids:
1710 self.disp(_( 1702 self.disp(_(
1711 u"Duplicate found on item {item_id}, we have probably handled " 1703 "Duplicate found on item {item_id}, we have probably handled "
1712 u"all items.").format(item_id=item_id)) 1704 "all items.").format(item_id=item_id))
1713 self.host.quit() 1705 self.host.quit()
1714 self.items_ids.append(item_id) 1706 self.items_ids.append(item_id)
1715 1707
1716 # we launch the command to filter the item 1708 # we launch the command to filter the item
1717 try: 1709 try:
1718 p = subprocess.Popen(self.args.command_path, stdin=subprocess.PIPE, 1710 p = subprocess.Popen(self.args.command_path, stdin=subprocess.PIPE,
1719 stdout=subprocess.PIPE) 1711 stdout=subprocess.PIPE)
1720 except OSError as e: 1712 except OSError as e:
1721 exit_code = C.EXIT_CMD_NOT_FOUND if e.errno == 2 else C.EXIT_ERROR 1713 exit_code = C.EXIT_CMD_NOT_FOUND if e.errno == 2 else C.EXIT_ERROR
1722 e = str(e).decode('utf-8', errors="ignore") 1714 e = str(e).decode('utf-8', errors="ignore")
1723 self.disp(u"Can't execute the command: {msg}".format(msg=e), error=True) 1715 self.disp("Can't execute the command: {msg}".format(msg=e), error=True)
1724 self.host.quit(exit_code) 1716 self.host.quit(exit_code)
1725 cmd_std_out, cmd_std_err = p.communicate(item.encode("utf-8")) 1717 cmd_std_out, cmd_std_err = p.communicate(item.encode("utf-8"))
1726 ret = p.wait() 1718 ret = p.wait()
1727 if ret != 0: 1719 if ret != 0:
1728 self.disp(u"The command returned a non zero status while parsing the " 1720 self.disp("The command returned a non zero status while parsing the "
1729 u"following item:\n\n{item}".format(item=item), error=True) 1721 "following item:\n\n{item}".format(item=item), error=True)
1730 if self.args.ignore_errors: 1722 if self.args.ignore_errors:
1731 continue 1723 continue
1732 else: 1724 else:
1733 self.host.quit(C.EXIT_CMD_ERROR) 1725 self.host.quit(C.EXIT_CMD_ERROR)
1734 if cmd_std_err is not None: 1726 if cmd_std_err is not None:
1736 self.disp(cmd_std_err, error=True) 1728 self.disp(cmd_std_err, error=True)
1737 cmd_std_out = cmd_std_out.strip() 1729 cmd_std_out = cmd_std_out.strip()
1738 if cmd_std_out == "DELETE": 1730 if cmd_std_out == "DELETE":
1739 item_elt, __ = xml_tools.etreeParse(self, item) 1731 item_elt, __ = xml_tools.etreeParse(self, item)
1740 item_id = item_elt.get('id') 1732 item_id = item_elt.get('id')
1741 self.disp(_(u"Deleting item {item_id}").format(item_id=item_id)) 1733 self.disp(_("Deleting item {item_id}").format(item_id=item_id))
1742 if self.args.apply: 1734 if self.args.apply:
1743 # FIXME: we don't wait for item to be retracted which can cause 1735 # FIXME: we don't wait for item to be retracted which can cause
1744 # trouble in case of error just before the end of the command 1736 # trouble in case of error just before the end of the command
1745 # (the error message may be missed). 1737 # (the error message may be missed).
1746 # Once moved to Python 3, we must wait for it by using a 1738 # Once moved to Python 3, we must wait for it by using a
1751 item_id, 1743 item_id,
1752 False, 1744 False,
1753 self.profile, 1745 self.profile,
1754 errback=partial( 1746 errback=partial(
1755 self.errback, 1747 self.errback,
1756 msg=_(u"can't delete item [%s]: {}" % item_id), 1748 msg=_("can't delete item [%s]: {}" % item_id),
1757 exit_code=C.EXIT_BRIDGE_ERRBACK, 1749 exit_code=C.EXIT_BRIDGE_ERRBACK,
1758 ), 1750 ),
1759 ) 1751 )
1760 continue 1752 continue
1761 elif cmd_std_out == "SKIP": 1753 elif cmd_std_out == "SKIP":
1762 item_elt, __ = xml_tools.etreeParse(self, item) 1754 item_elt, __ = xml_tools.etreeParse(self, item)
1763 item_id = item_elt.get('id') 1755 item_id = item_elt.get('id')
1764 self.disp(_(u"Skipping item {item_id}").format(item_id=item_id)) 1756 self.disp(_("Skipping item {item_id}").format(item_id=item_id))
1765 continue 1757 continue
1766 element, etree = xml_tools.etreeParse(self, cmd_std_out) 1758 element, etree = xml_tools.etreeParse(self, cmd_std_out)
1767 1759
1768 # at this point command has been run and we have a etree.Element object 1760 # at this point command has been run and we have a etree.Element object
1769 if element.tag not in ("item", "{http://jabber.org/protocol/pubsub}item"): 1761 if element.tag not in ("item", "{http://jabber.org/protocol/pubsub}item"):
1770 self.disp(u"your script must return a whole item, this is not:\n{xml}" 1762 self.disp("your script must return a whole item, this is not:\n{xml}"
1771 .format(xml=etree.tostring(element, encoding="unicode")), error=True) 1763 .format(xml=etree.tostring(element, encoding="unicode")), error=True)
1772 self.host.quit(C.EXIT_DATA_ERROR) 1764 self.host.quit(C.EXIT_DATA_ERROR)
1773 1765
1774 if not self.args.apply: 1766 if not self.args.apply:
1775 # we have a dry run, we just display filtered items 1767 # we have a dry run, we just display filtered items
1776 serialised = etree.tostring(element, encoding=u'unicode', 1768 serialised = etree.tostring(element, encoding='unicode',
1777 pretty_print=True) 1769 pretty_print=True)
1778 self.disp(serialised) 1770 self.disp(serialised)
1779 else: 1771 else:
1780 new_items.append(etree.tostring(element, encoding="unicode")) 1772 new_items.append(etree.tostring(element, encoding="unicode"))
1781 1773
1788 if self.args.admin: 1780 if self.args.admin:
1789 self.host.bridge.psAdminItemsSend( 1781 self.host.bridge.psAdminItemsSend(
1790 self.args.service, 1782 self.args.service,
1791 self.args.node, 1783 self.args.node,
1792 new_items, 1784 new_items,
1793 u"", 1785 "",
1794 self.profile, 1786 self.profile,
1795 callback=partial(self.psItemsSendCb, metadata=metadata), 1787 callback=partial(self.psItemsSendCb, metadata=metadata),
1796 errback=partial( 1788 errback=partial(
1797 self.errback, 1789 self.errback,
1798 msg=_(u"can't send item: {}"), 1790 msg=_("can't send item: {}"),
1799 exit_code=C.EXIT_BRIDGE_ERRBACK, 1791 exit_code=C.EXIT_BRIDGE_ERRBACK,
1800 ), 1792 ),
1801 ) 1793 )
1802 else: 1794 else:
1803 self.host.bridge.psItemsSend( 1795 self.host.bridge.psItemsSend(
1804 self.args.service, 1796 self.args.service,
1805 self.args.node, 1797 self.args.node,
1806 new_items, 1798 new_items,
1807 u"", 1799 "",
1808 self.profile, 1800 self.profile,
1809 callback=partial(self.psItemsSendCb, metadata=metadata), 1801 callback=partial(self.psItemsSendCb, metadata=metadata),
1810 errback=partial( 1802 errback=partial(
1811 self.errback, 1803 self.errback,
1812 msg=_(u"can't send item: {}"), 1804 msg=_("can't send item: {}"),
1813 exit_code=C.EXIT_BRIDGE_ERRBACK, 1805 exit_code=C.EXIT_BRIDGE_ERRBACK,
1814 ), 1806 ),
1815 ) 1807 )
1816 1808
1817 def start(self): 1809 def start(self):
1818 if self.args.all and self.args.order_by != C.ORDER_BY_CREATION: 1810 if self.args.all and self.args.order_by != C.ORDER_BY_CREATION:
1819 self.check_duplicates = True 1811 self.check_duplicates = True
1820 self.items_ids = [] 1812 self.items_ids = []
1821 self.disp(A.color( 1813 self.disp(A.color(
1822 A.FG_RED, A.BOLD, 1814 A.FG_RED, A.BOLD,
1823 u'/!\\ "--all" should be used with "--order-by creation" /!\\\n', 1815 '/!\\ "--all" should be used with "--order-by creation" /!\\\n',
1824 A.RESET, 1816 A.RESET,
1825 u"We'll update items, so order may change during transformation,\n" 1817 "We'll update items, so order may change during transformation,\n"
1826 u"we'll try to mitigate that by stopping on first duplicate,\n" 1818 "we'll try to mitigate that by stopping on first duplicate,\n"
1827 u"but this method is not safe, and some items may be missed.\n---\n")) 1819 "but this method is not safe, and some items may be missed.\n---\n"))
1828 else: 1820 else:
1829 self.check_duplicates = False 1821 self.check_duplicates = False
1830 self.host.bridge.psItemsGet( 1822 self.host.bridge.psItemsGet(
1831 self.args.service, 1823 self.args.service,
1832 self.args.node, 1824 self.args.node,
1836 self.getPubsubExtra(), 1828 self.getPubsubExtra(),
1837 self.profile, 1829 self.profile,
1838 callback=self.psItemsGetCb, 1830 callback=self.psItemsGetCb,
1839 errback=partial( 1831 errback=partial(
1840 self.errback, 1832 self.errback,
1841 msg=_(u"can't retrieve items: {}"), 1833 msg=_("can't retrieve items: {}"),
1842 exit_code=C.EXIT_BRIDGE_ERRBACK, 1834 exit_code=C.EXIT_BRIDGE_ERRBACK,
1843 ), 1835 ),
1844 ) 1836 )
1845 1837
1846 1838
1851 host, 1843 host,
1852 "uri", 1844 "uri",
1853 use_profile=False, 1845 use_profile=False,
1854 use_pubsub=True, 1846 use_pubsub=True,
1855 pubsub_flags={C.NODE, C.SINGLE_ITEM}, 1847 pubsub_flags={C.NODE, C.SINGLE_ITEM},
1856 help=_(u"build URI"), 1848 help=_("build URI"),
1857 ) 1849 )
1858 self.need_loop = True 1850 self.need_loop = True
1859 1851
1860 def add_parser_options(self): 1852 def add_parser_options(self):
1861 self.parser.add_argument( 1853 self.parser.add_argument(
1862 "-p", 1854 "-p",
1863 "--profile", 1855 "--profile",
1864 type=base.unicode_decoder,
1865 default=C.PROF_KEY_DEFAULT, 1856 default=C.PROF_KEY_DEFAULT,
1866 help=_(u"profile (used when no server is specified)"), 1857 help=_("profile (used when no server is specified)"),
1867 ) 1858 )
1868 1859
1869 def display_uri(self, jid_): 1860 def display_uri(self, jid_):
1870 uri_args = {} 1861 uri_args = {}
1871 if not self.args.service: 1862 if not self.args.service:
1875 value = getattr(self.args, key) 1866 value = getattr(self.args, key)
1876 if key == "service": 1867 if key == "service":
1877 key = "path" 1868 key = "path"
1878 if value: 1869 if value:
1879 uri_args[key] = value 1870 uri_args[key] = value
1880 self.disp(uri.buildXMPPUri(u"pubsub", **uri_args)) 1871 self.disp(uri.buildXMPPUri("pubsub", **uri_args))
1881 self.host.quit() 1872 self.host.quit()
1882 1873
1883 def start(self): 1874 def start(self):
1884 if not self.args.service: 1875 if not self.args.service:
1885 self.host.bridge.asyncGetParamA( 1876 self.host.bridge.asyncGetParamA(
1886 u"JabberID", 1877 "JabberID",
1887 u"Connection", 1878 "Connection",
1888 profile_key=self.args.profile, 1879 profile_key=self.args.profile,
1889 callback=self.display_uri, 1880 callback=self.display_uri,
1890 errback=partial( 1881 errback=partial(
1891 self.errback, 1882 self.errback,
1892 msg=_(u"can't retrieve jid: {}"), 1883 msg=_("can't retrieve jid: {}"),
1893 exit_code=C.EXIT_BRIDGE_ERRBACK, 1884 exit_code=C.EXIT_BRIDGE_ERRBACK,
1894 ), 1885 ),
1895 ) 1886 )
1896 else: 1887 else:
1897 self.display_uri(None) 1888 self.display_uri(None)
1903 self, 1894 self,
1904 host, 1895 host,
1905 "create", 1896 "create",
1906 use_pubsub=True, 1897 use_pubsub=True,
1907 pubsub_flags={C.NODE}, 1898 pubsub_flags={C.NODE},
1908 help=_(u"create a Pubsub hook"), 1899 help=_("create a Pubsub hook"),
1909 ) 1900 )
1910 self.need_loop = True 1901 self.need_loop = True
1911 1902
1912 def add_parser_options(self): 1903 def add_parser_options(self):
1913 self.parser.add_argument( 1904 self.parser.add_argument(
1914 "-t", 1905 "-t",
1915 "--type", 1906 "--type",
1916 default=u"python", 1907 default="python",
1917 choices=("python", "python_file", "python_code"), 1908 choices=("python", "python_file", "python_code"),
1918 help=_(u"hook type"), 1909 help=_("hook type"),
1919 ) 1910 )
1920 self.parser.add_argument( 1911 self.parser.add_argument(
1921 "-P", 1912 "-P",
1922 "--persistent", 1913 "--persistent",
1923 action="store_true", 1914 action="store_true",
1924 help=_(u"make hook persistent across restarts"), 1915 help=_("make hook persistent across restarts"),
1925 ) 1916 )
1926 self.parser.add_argument( 1917 self.parser.add_argument(
1927 "hook_arg", 1918 "hook_arg",
1928 type=base.unicode_decoder, 1919 help=_("argument of the hook (depend of the type)"),
1929 help=_(u"argument of the hook (depend of the type)"),
1930 ) 1920 )
1931 1921
1932 @staticmethod 1922 @staticmethod
1933 def checkArgs(self): 1923 def checkArgs(self):
1934 if self.args.type == u"python_file": 1924 if self.args.type == "python_file":
1935 self.args.hook_arg = os.path.abspath(self.args.hook_arg) 1925 self.args.hook_arg = os.path.abspath(self.args.hook_arg)
1936 if not os.path.isfile(self.args.hook_arg): 1926 if not os.path.isfile(self.args.hook_arg):
1937 self.parser.error( 1927 self.parser.error(
1938 _(u"{path} is not a file").format(path=self.args.hook_arg) 1928 _("{path} is not a file").format(path=self.args.hook_arg)
1939 ) 1929 )
1940 1930
1941 def start(self): 1931 def start(self):
1942 self.checkArgs(self) 1932 self.checkArgs(self)
1943 self.host.bridge.psHookAdd( 1933 self.host.bridge.psHookAdd(
1948 self.args.persistent, 1938 self.args.persistent,
1949 self.profile, 1939 self.profile,
1950 callback=self.host.quit, 1940 callback=self.host.quit,
1951 errback=partial( 1941 errback=partial(
1952 self.errback, 1942 self.errback,
1953 msg=_(u"can't create hook: {}"), 1943 msg=_("can't create hook: {}"),
1954 exit_code=C.EXIT_BRIDGE_ERRBACK, 1944 exit_code=C.EXIT_BRIDGE_ERRBACK,
1955 ), 1945 ),
1956 ) 1946 )
1957 1947
1958 1948
1962 self, 1952 self,
1963 host, 1953 host,
1964 "delete", 1954 "delete",
1965 use_pubsub=True, 1955 use_pubsub=True,
1966 pubsub_flags={C.NODE}, 1956 pubsub_flags={C.NODE},
1967 help=_(u"delete a Pubsub hook"), 1957 help=_("delete a Pubsub hook"),
1968 ) 1958 )
1969 self.need_loop = True 1959 self.need_loop = True
1970 1960
1971 def add_parser_options(self): 1961 def add_parser_options(self):
1972 self.parser.add_argument( 1962 self.parser.add_argument(
1973 "-t", 1963 "-t",
1974 "--type", 1964 "--type",
1975 default=u"", 1965 default="",
1976 choices=("", "python", "python_file", "python_code"), 1966 choices=("", "python", "python_file", "python_code"),
1977 help=_(u"hook type to remove, empty to remove all (DEFAULT: remove all)"), 1967 help=_("hook type to remove, empty to remove all (DEFAULT: remove all)"),
1978 ) 1968 )
1979 self.parser.add_argument( 1969 self.parser.add_argument(
1980 "-a", 1970 "-a",
1981 "--arg", 1971 "--arg",
1982 dest="hook_arg", 1972 dest="hook_arg",
1983 type=base.unicode_decoder, 1973 default="",
1984 default=u"",
1985 help=_( 1974 help=_(
1986 u"argument of the hook to remove, empty to remove all (DEFAULT: remove all)" 1975 "argument of the hook to remove, empty to remove all (DEFAULT: remove all)"
1987 ), 1976 ),
1988 ) 1977 )
1989 1978
1990 def psHookRemoveCb(self, nb_deleted): 1979 def psHookRemoveCb(self, nb_deleted):
1991 self.disp( 1980 self.disp(
1992 _(u"{nb_deleted} hook(s) have been deleted").format(nb_deleted=nb_deleted) 1981 _("{nb_deleted} hook(s) have been deleted").format(nb_deleted=nb_deleted)
1993 ) 1982 )
1994 self.host.quit() 1983 self.host.quit()
1995 1984
1996 def start(self): 1985 def start(self):
1997 HookCreate.checkArgs(self) 1986 HookCreate.checkArgs(self)
2002 self.args.hook_arg, 1991 self.args.hook_arg,
2003 self.profile, 1992 self.profile,
2004 callback=self.psHookRemoveCb, 1993 callback=self.psHookRemoveCb,
2005 errback=partial( 1994 errback=partial(
2006 self.errback, 1995 self.errback,
2007 msg=_(u"can't delete hook: {}"), 1996 msg=_("can't delete hook: {}"),
2008 exit_code=C.EXIT_BRIDGE_ERRBACK, 1997 exit_code=C.EXIT_BRIDGE_ERRBACK,
2009 ), 1998 ),
2010 ) 1999 )
2011 2000
2012 2001
2015 base.CommandBase.__init__( 2004 base.CommandBase.__init__(
2016 self, 2005 self,
2017 host, 2006 host,
2018 "list", 2007 "list",
2019 use_output=C.OUTPUT_LIST_DICT, 2008 use_output=C.OUTPUT_LIST_DICT,
2020 help=_(u"list hooks of a profile"), 2009 help=_("list hooks of a profile"),
2021 ) 2010 )
2022 self.need_loop = True 2011 self.need_loop = True
2023 2012
2024 def add_parser_options(self): 2013 def add_parser_options(self):
2025 pass 2014 pass
2026 2015
2027 def psHookListCb(self, data): 2016 def psHookListCb(self, data):
2028 if not data: 2017 if not data:
2029 self.disp(_(u"No hook found.")) 2018 self.disp(_("No hook found."))
2030 self.output(data) 2019 self.output(data)
2031 self.host.quit() 2020 self.host.quit()
2032 2021
2033 def start(self): 2022 def start(self):
2034 self.host.bridge.psHookList( 2023 self.host.bridge.psHookList(
2035 self.profile, 2024 self.profile,
2036 callback=self.psHookListCb, 2025 callback=self.psHookListCb,
2037 errback=partial( 2026 errback=partial(
2038 self.errback, 2027 self.errback,
2039 msg=_(u"can't list hooks: {}"), 2028 msg=_("can't list hooks: {}"),
2040 exit_code=C.EXIT_BRIDGE_ERRBACK, 2029 exit_code=C.EXIT_BRIDGE_ERRBACK,
2041 ), 2030 ),
2042 ) 2031 )
2043 2032
2044 2033