Mercurial > libervia-backend
comparison libervia/cli/base.py @ 4270:0d7bb4df2343
Reformatted code base using black.
author | Goffi <goffi@goffi.org> |
---|---|
date | Wed, 19 Jun 2024 18:44:57 +0200 |
parents | 5115976e1e3d |
children | 554a87ae17a6 |
comparison
equal
deleted
inserted
replaced
4269:64a85ce8be70 | 4270:0d7bb4df2343 |
---|---|
19 import asyncio | 19 import asyncio |
20 from libervia.backend.core.i18n import _ | 20 from libervia.backend.core.i18n import _ |
21 | 21 |
22 ### logging ### | 22 ### logging ### |
23 import logging as log | 23 import logging as log |
24 log.basicConfig(level=log.WARNING, | 24 |
25 format='[%(name)s] %(message)s') | 25 log.basicConfig(level=log.WARNING, format="[%(name)s] %(message)s") |
26 ### | 26 ### |
27 | 27 |
28 import sys | 28 import sys |
29 import os | 29 import os |
30 import os.path | 30 import os.path |
53 import xml.etree.ElementTree as ET # FIXME: used temporarily to manage XMLUI | 53 import xml.etree.ElementTree as ET # FIXME: used temporarily to manage XMLUI |
54 from collections import OrderedDict | 54 from collections import OrderedDict |
55 from rich import console | 55 from rich import console |
56 | 56 |
57 ## bridge handling | 57 ## bridge handling |
58 # we get bridge name from conf and initialise the right class accordingly | 58 # we get bridge name from conf and initialise the right class accordingly |
59 main_config = config.parse_main_conf() | 59 main_config = config.parse_main_conf() |
60 bridge_name = config.config_get(main_config, '', 'bridge', 'dbus') | 60 bridge_name = config.config_get(main_config, "", "bridge", "dbus") |
61 LiberviaCLILoop = get_libervia_cli_loop(bridge_name) | 61 LiberviaCLILoop = get_libervia_cli_loop(bridge_name) |
62 | 62 |
63 | 63 |
64 try: | 64 try: |
65 import progressbar | 65 import progressbar |
66 except ImportError: | 66 except ImportError: |
67 msg = (_('ProgressBar not available, please download it at ' | 67 msg = _( |
68 'http://pypi.python.org/pypi/progressbar\n' | 68 "ProgressBar not available, please download it at " |
69 'Progress bar deactivated\n--\n')) | 69 "http://pypi.python.org/pypi/progressbar\n" |
70 "Progress bar deactivated\n--\n" | |
71 ) | |
70 print(msg, file=sys.stderr) | 72 print(msg, file=sys.stderr) |
71 progressbar=None | 73 progressbar = None |
72 | 74 |
73 #consts | 75 # consts |
74 DESCRIPTION = """This software is a command line tool for XMPP. | 76 DESCRIPTION = ( |
75 Get the latest version at """ + C.APP_URL | 77 """This software is a command line tool for XMPP. |
78 Get the latest version at """ | |
79 + C.APP_URL | |
80 ) | |
76 | 81 |
77 COPYLEFT = """Copyright (C) 2009-2024 Jérôme Poisson, Adrien Cossa | 82 COPYLEFT = """Copyright (C) 2009-2024 Jérôme Poisson, Adrien Cossa |
78 This program comes with ABSOLUTELY NO WARRANTY; | 83 This program comes with ABSOLUTELY NO WARRANTY; |
79 This is free software, and you are welcome to redistribute it under certain conditions. | 84 This is free software, and you are welcome to redistribute it under certain conditions. |
80 """ | 85 """ |
81 | 86 |
82 PROGRESS_DELAY = 0.1 # the progression will be checked every PROGRESS_DELAY s | 87 PROGRESS_DELAY = 0.1 # the progression will be checked every PROGRESS_DELAY s |
83 | 88 |
84 | 89 |
85 def date_decoder(arg): | 90 def date_decoder(arg): |
86 return date_utils.date_parse_ext(arg, default_tz=date_utils.TZ_LOCAL) | 91 return date_utils.date_parse_ext(arg, default_tz=date_utils.TZ_LOCAL) |
87 | 92 |
93 | 98 |
94 To use it, you mainly have to redefine the method run to perform | 99 To use it, you mainly have to redefine the method run to perform |
95 specify what kind of operation you want to perform. | 100 specify what kind of operation you want to perform. |
96 | 101 |
97 """ | 102 """ |
103 | |
98 def __init__(self): | 104 def __init__(self): |
99 """ | 105 """ |
100 | 106 |
101 @attribute quit_on_progress_end (bool): set to False if you manage yourself | 107 @attribute quit_on_progress_end (bool): set to False if you manage yourself |
102 exiting, or if you want the user to stop by himself | 108 exiting, or if you want the user to stop by himself |
108 by default display a message | 114 by default display a message |
109 """ | 115 """ |
110 self.console = console.Console(theme=C.THEME_DEFAULT) | 116 self.console = console.Console(theme=C.THEME_DEFAULT) |
111 self.sat_conf = main_config | 117 self.sat_conf = main_config |
112 self.set_color_theme() | 118 self.set_color_theme() |
113 bridge_module = dynamic_import.bridge(bridge_name, 'libervia.frontends.bridge') | 119 bridge_module = dynamic_import.bridge(bridge_name, "libervia.frontends.bridge") |
114 if bridge_module is None: | 120 if bridge_module is None: |
115 log.error("Can't import {} bridge".format(bridge_name)) | 121 log.error("Can't import {} bridge".format(bridge_name)) |
116 sys.exit(1) | 122 sys.exit(1) |
117 | 123 |
118 self.bridge = bridge_module.AIOBridge() | 124 self.bridge = bridge_module.AIOBridge() |
130 except ValueError: | 136 except ValueError: |
131 vte_version = 0 | 137 vte_version = 0 |
132 | 138 |
133 color_fg_bg = os.getenv("COLORFGBG") | 139 color_fg_bg = os.getenv("COLORFGBG") |
134 | 140 |
135 if ((sys.stdin.isatty() and sys.stdout.isatty() | 141 if ( |
136 and ( | 142 sys.stdin.isatty() |
137 # XTerm | 143 and sys.stdout.isatty() |
138 os.getenv("XTERM_VERSION") | 144 and ( |
139 # Konsole | 145 # XTerm |
140 or os.getenv("KONSOLE_VERSION") | 146 os.getenv("XTERM_VERSION") |
141 # All VTE based terminals | 147 # Konsole |
142 or vte_version >= 3502 | 148 or os.getenv("KONSOLE_VERSION") |
143 ))): | 149 # All VTE based terminals |
150 or vte_version >= 3502 | |
151 ) | |
152 ): | |
144 # ANSI escape sequence | 153 # ANSI escape sequence |
145 stdin_fd = sys.stdin.fileno() | 154 stdin_fd = sys.stdin.fileno() |
146 old_settings = termios.tcgetattr(stdin_fd) | 155 old_settings = termios.tcgetattr(stdin_fd) |
147 try: | 156 try: |
148 tty.setraw(sys.stdin.fileno()) | 157 tty.setraw(sys.stdin.fileno()) |
153 for c in expected: | 162 for c in expected: |
154 ch = sys.stdin.read(1) | 163 ch = sys.stdin.read(1) |
155 if ch != c: | 164 if ch != c: |
156 # background id is not supported, we default to "dark" | 165 # background id is not supported, we default to "dark" |
157 # TODO: log something? | 166 # TODO: log something? |
158 return 'dark' | 167 return "dark" |
159 red, green, blue = [ | 168 red, green, blue = [ |
160 int(c, 16)/65535 for c in sys.stdin.read(14).split('/') | 169 int(c, 16) / 65535 for c in sys.stdin.read(14).split("/") |
161 ] | 170 ] |
162 # '\a' is the last character | 171 # '\a' is the last character |
163 sys.stdin.read(1) | 172 sys.stdin.read(1) |
164 finally: | 173 finally: |
165 termios.tcsetattr(stdin_fd, termios.TCSADRAIN, old_settings) | 174 termios.tcsetattr(stdin_fd, termios.TCSADRAIN, old_settings) |
166 | 175 |
167 lum = utils.per_luminance(red, green, blue) | 176 lum = utils.per_luminance(red, green, blue) |
168 if lum <= 0.5: | 177 if lum <= 0.5: |
169 return 'dark' | 178 return "dark" |
170 else: | 179 else: |
171 return 'light' | 180 return "light" |
172 elif color_fg_bg: | 181 elif color_fg_bg: |
173 # no luck with ANSI escape sequence, we try COLORFGBG environment variable | 182 # no luck with ANSI escape sequence, we try COLORFGBG environment variable |
174 try: | 183 try: |
175 bg = int(color_fg_bg.split(";")[-1]) | 184 bg = int(color_fg_bg.split(";")[-1]) |
176 except ValueError: | 185 except ValueError: |
182 else: | 191 else: |
183 # no autodetection method found | 192 # no autodetection method found |
184 return "dark" | 193 return "dark" |
185 | 194 |
186 def set_color_theme(self): | 195 def set_color_theme(self): |
187 background = self.get_config('background', default='auto') | 196 background = self.get_config("background", default="auto") |
188 if background == 'auto': | 197 if background == "auto": |
189 background = self.guess_background() | 198 background = self.guess_background() |
190 if background not in ('dark', 'light'): | 199 if background not in ("dark", "light"): |
191 raise exceptions.ConfigError(_( | 200 raise exceptions.ConfigError( |
192 'Invalid value set for "background" ({background}), please check ' | 201 _( |
193 'your settings in libervia.conf').format( | 202 'Invalid value set for "background" ({background}), please check ' |
194 background=repr(background) | 203 "your settings in libervia.conf" |
195 )) | 204 ).format(background=repr(background)) |
205 ) | |
196 self.background = background | 206 self.background = background |
197 if background == 'light': | 207 if background == "light": |
198 C.A_HEADER = A.FG_MAGENTA | 208 C.A_HEADER = A.FG_MAGENTA |
199 C.A_SUBHEADER = A.BOLD + A.FG_RED | 209 C.A_SUBHEADER = A.BOLD + A.FG_RED |
200 C.A_LEVEL_COLORS = (C.A_HEADER, A.BOLD + A.FG_BLUE, A.FG_MAGENTA, A.FG_CYAN) | 210 C.A_LEVEL_COLORS = (C.A_HEADER, A.BOLD + A.FG_BLUE, A.FG_MAGENTA, A.FG_CYAN) |
201 C.A_SUCCESS = A.FG_GREEN | 211 C.A_SUCCESS = A.FG_GREEN |
202 C.A_FAILURE = A.BOLD + A.FG_RED | 212 C.A_FAILURE = A.BOLD + A.FG_RED |
206 C.A_DIRECTORY = A.BOLD + A.FG_MAGENTA | 216 C.A_DIRECTORY = A.BOLD + A.FG_MAGENTA |
207 C.A_FILE = A.FG_BLACK | 217 C.A_FILE = A.FG_BLACK |
208 | 218 |
209 def _bridge_connected(self): | 219 def _bridge_connected(self): |
210 self.parser = argparse.ArgumentParser( | 220 self.parser = argparse.ArgumentParser( |
211 formatter_class=argparse.RawDescriptionHelpFormatter, description=DESCRIPTION) | 221 formatter_class=argparse.RawDescriptionHelpFormatter, description=DESCRIPTION |
222 ) | |
212 self._make_parents() | 223 self._make_parents() |
213 self.add_parser_options() | 224 self.add_parser_options() |
214 self.subparsers = self.parser.add_subparsers( | 225 self.subparsers = self.parser.add_subparsers( |
215 title=_('Available commands'), dest='command', required=True) | 226 title=_("Available commands"), dest="command", required=True |
227 ) | |
216 | 228 |
217 # progress attributes | 229 # progress attributes |
218 self._progress_id = None # TODO: manage several progress ids | 230 self._progress_id = None # TODO: manage several progress ids |
219 self.quit_on_progress_end = True | 231 self.quit_on_progress_end = True |
220 | 232 |
221 # outputs | 233 # outputs |
222 self._outputs = {} | 234 self._outputs = {} |
223 for type_ in C.OUTPUT_TYPES: | 235 for type_ in C.OUTPUT_TYPES: |
231 return self._progress_id | 243 return self._progress_id |
232 | 244 |
233 async def set_progress_id(self, progress_id): | 245 async def set_progress_id(self, progress_id): |
234 # because we use async, we need an explicit setter | 246 # because we use async, we need an explicit setter |
235 self._progress_id = progress_id | 247 self._progress_id = progress_id |
236 await self.replay_cache('progress_ids_cache') | 248 await self.replay_cache("progress_ids_cache") |
237 | 249 |
238 @property | 250 @property |
239 def watch_progress(self): | 251 def watch_progress(self): |
240 try: | 252 try: |
241 self.pbar | 253 self.pbar |
270 pass | 282 pass |
271 else: | 283 else: |
272 for cache_data in cache: | 284 for cache_data in cache: |
273 await cache_data[0](*cache_data[1:]) | 285 await cache_data[0](*cache_data[1:]) |
274 | 286 |
275 def disp(self, msg, verbosity=0, error=False, end='\n'): | 287 def disp(self, msg, verbosity=0, error=False, end="\n"): |
276 """Print a message to user | 288 """Print a message to user |
277 | 289 |
278 @param msg(unicode): message to print | 290 @param msg(unicode): message to print |
279 @param verbosity(int): minimal verbosity to display the message | 291 @param verbosity(int): minimal verbosity to display the message |
280 @param error(bool): if True, print to stderr instead of stdout | 292 @param error(bool): if True, print to stderr instead of stdout |
288 | 300 |
289 async def output(self, type_, name, extra_outputs, data): | 301 async def output(self, type_, name, extra_outputs, data): |
290 if name in extra_outputs: | 302 if name in extra_outputs: |
291 method = extra_outputs[name] | 303 method = extra_outputs[name] |
292 else: | 304 else: |
293 method = self._outputs[type_][name]['callback'] | 305 method = self._outputs[type_][name]["callback"] |
294 | 306 |
295 ret = method(data) | 307 ret = method(data) |
296 if inspect.isawaitable(ret): | 308 if inspect.isawaitable(ret): |
297 await ret | 309 await ret |
298 | 310 |
315 self.parents = {} | 327 self.parents = {} |
316 | 328 |
317 # we have a special case here as the start-session option is present only if | 329 # we have a special case here as the start-session option is present only if |
318 # connection is not needed, so we create two similar parents, one with the | 330 # connection is not needed, so we create two similar parents, one with the |
319 # option, the other one without it | 331 # option, the other one without it |
320 for parent_name in ('profile', 'profile_session'): | 332 for parent_name in ("profile", "profile_session"): |
321 parent = self.parents[parent_name] = argparse.ArgumentParser(add_help=False) | 333 parent = self.parents[parent_name] = argparse.ArgumentParser(add_help=False) |
322 parent.add_argument( | 334 parent.add_argument( |
323 "-p", "--profile", action="store", type=str, default='@DEFAULT@', | 335 "-p", |
324 help=_("Use PROFILE profile key (default: %(default)s)")) | 336 "--profile", |
337 action="store", | |
338 type=str, | |
339 default="@DEFAULT@", | |
340 help=_("Use PROFILE profile key (default: %(default)s)"), | |
341 ) | |
325 parent.add_argument( | 342 parent.add_argument( |
326 "--pwd", action="store", metavar='PASSWORD', | 343 "--pwd", |
327 help=_("Password used to connect profile, if necessary")) | 344 action="store", |
328 | 345 metavar="PASSWORD", |
329 profile_parent, profile_session_parent = (self.parents['profile'], | 346 help=_("Password used to connect profile, if necessary"), |
330 self.parents['profile_session']) | 347 ) |
348 | |
349 profile_parent, profile_session_parent = ( | |
350 self.parents["profile"], | |
351 self.parents["profile_session"], | |
352 ) | |
331 | 353 |
332 connect_short, connect_long, connect_action, connect_help = ( | 354 connect_short, connect_long, connect_action, connect_help = ( |
333 "-c", "--connect", "store_true", | 355 "-c", |
334 _("Connect the profile before doing anything else") | 356 "--connect", |
357 "store_true", | |
358 _("Connect the profile before doing anything else"), | |
335 ) | 359 ) |
336 profile_parent.add_argument( | 360 profile_parent.add_argument( |
337 connect_short, connect_long, action=connect_action, help=connect_help) | 361 connect_short, connect_long, action=connect_action, help=connect_help |
338 | 362 ) |
339 profile_session_connect_group = profile_session_parent.add_mutually_exclusive_group() | 363 |
364 profile_session_connect_group = ( | |
365 profile_session_parent.add_mutually_exclusive_group() | |
366 ) | |
340 profile_session_connect_group.add_argument( | 367 profile_session_connect_group.add_argument( |
341 connect_short, connect_long, action=connect_action, help=connect_help) | 368 connect_short, connect_long, action=connect_action, help=connect_help |
369 ) | |
342 profile_session_connect_group.add_argument( | 370 profile_session_connect_group.add_argument( |
343 "--start-session", action="store_true", | 371 "--start-session", |
344 help=_("Start a profile session without connecting")) | 372 action="store_true", |
345 | 373 help=_("Start a profile session without connecting"), |
346 progress_parent = self.parents['progress'] = argparse.ArgumentParser( | 374 ) |
347 add_help=False) | 375 |
376 progress_parent = self.parents["progress"] = argparse.ArgumentParser( | |
377 add_help=False | |
378 ) | |
348 if progressbar: | 379 if progressbar: |
349 progress_parent.add_argument( | 380 progress_parent.add_argument( |
350 "-P", "--progress", action="store_true", help=_("Show progress bar")) | 381 "-P", "--progress", action="store_true", help=_("Show progress bar") |
351 | 382 ) |
352 verbose_parent = self.parents['verbose'] = argparse.ArgumentParser(add_help=False) | 383 |
384 verbose_parent = self.parents["verbose"] = argparse.ArgumentParser(add_help=False) | |
353 verbose_parent.add_argument( | 385 verbose_parent.add_argument( |
354 '--verbose', '-v', action='count', default=0, | 386 "--verbose", |
355 help=_("Add a verbosity level (can be used multiple times)")) | 387 "-v", |
356 | 388 action="count", |
357 quiet_parent = self.parents['quiet'] = argparse.ArgumentParser(add_help=False) | 389 default=0, |
390 help=_("Add a verbosity level (can be used multiple times)"), | |
391 ) | |
392 | |
393 quiet_parent = self.parents["quiet"] = argparse.ArgumentParser(add_help=False) | |
358 quiet_parent.add_argument( | 394 quiet_parent.add_argument( |
359 '--quiet', '-q', action='store_true', | 395 "--quiet", |
360 help=_("be quiet (only output machine readable data)")) | 396 "-q", |
361 | 397 action="store_true", |
362 draft_parent = self.parents['draft'] = argparse.ArgumentParser(add_help=False) | 398 help=_("be quiet (only output machine readable data)"), |
363 draft_group = draft_parent.add_argument_group(_('draft handling')) | 399 ) |
400 | |
401 draft_parent = self.parents["draft"] = argparse.ArgumentParser(add_help=False) | |
402 draft_group = draft_parent.add_argument_group(_("draft handling")) | |
364 draft_group.add_argument( | 403 draft_group.add_argument( |
365 "-D", "--current", action="store_true", help=_("load current draft")) | 404 "-D", "--current", action="store_true", help=_("load current draft") |
405 ) | |
366 draft_group.add_argument( | 406 draft_group.add_argument( |
367 "-F", "--draft-path", type=Path, help=_("path to a draft file to retrieve")) | 407 "-F", "--draft-path", type=Path, help=_("path to a draft file to retrieve") |
368 | 408 ) |
369 | 409 |
370 def make_pubsub_group(self, flags, defaults): | 410 def make_pubsub_group(self, flags, defaults): |
371 """Generate pubsub options according to flags | 411 """Generate pubsub options according to flags |
372 | 412 |
373 @param flags(iterable[unicode]): see [CommandBase.__init__] | 413 @param flags(iterable[unicode]): see [CommandBase.__init__] |
376 value will be set in " (DEFAULT: {value})", or can be None to remove DEFAULT | 416 value will be set in " (DEFAULT: {value})", or can be None to remove DEFAULT |
377 @return (ArgumentParser): parser to add | 417 @return (ArgumentParser): parser to add |
378 """ | 418 """ |
379 flags = misc.FlagsHandler(flags) | 419 flags = misc.FlagsHandler(flags) |
380 parent = argparse.ArgumentParser(add_help=False) | 420 parent = argparse.ArgumentParser(add_help=False) |
381 pubsub_group = parent.add_argument_group('pubsub') | 421 pubsub_group = parent.add_argument_group("pubsub") |
382 pubsub_group.add_argument("-u", "--pubsub-url", | 422 pubsub_group.add_argument( |
383 help=_("Pubsub URL (xmpp or http)")) | 423 "-u", "--pubsub-url", help=_("Pubsub URL (xmpp or http)") |
424 ) | |
384 | 425 |
385 service_help = _("JID of the PubSub service") | 426 service_help = _("JID of the PubSub service") |
386 if not flags.service: | 427 if not flags.service: |
387 default = defaults.pop('service', _('PEP service')) | 428 default = defaults.pop("service", _("PEP service")) |
388 if default is not None: | 429 if default is not None: |
389 service_help += _(" (DEFAULT: {default})".format(default=default)) | 430 service_help += _(" (DEFAULT: {default})".format(default=default)) |
390 pubsub_group.add_argument("-s", "--service", default='', | 431 pubsub_group.add_argument("-s", "--service", default="", help=service_help) |
391 help=service_help) | |
392 | 432 |
393 node_help = _("node to request") | 433 node_help = _("node to request") |
394 if not flags.node: | 434 if not flags.node: |
395 default = defaults.pop('node', _('standard node')) | 435 default = defaults.pop("node", _("standard node")) |
396 if default is not None: | 436 if default is not None: |
397 node_help += _(" (DEFAULT: {default})".format(default=default)) | 437 node_help += _(" (DEFAULT: {default})".format(default=default)) |
398 pubsub_group.add_argument("-n", "--node", default='', help=node_help) | 438 pubsub_group.add_argument("-n", "--node", default="", help=node_help) |
399 | 439 |
400 if flags.single_item: | 440 if flags.single_item: |
401 item_help = ("item to retrieve") | 441 item_help = "item to retrieve" |
402 if not flags.item: | 442 if not flags.item: |
403 default = defaults.pop('item', _('last item')) | 443 default = defaults.pop("item", _("last item")) |
404 if default is not None: | 444 if default is not None: |
405 item_help += _(" (DEFAULT: {default})".format(default=default)) | 445 item_help += _(" (DEFAULT: {default})".format(default=default)) |
406 pubsub_group.add_argument("-i", "--item", default='', | 446 pubsub_group.add_argument("-i", "--item", default="", help=item_help) |
407 help=item_help) | |
408 pubsub_group.add_argument( | 447 pubsub_group.add_argument( |
409 "-L", "--last-item", action='store_true', help=_('retrieve last item')) | 448 "-L", "--last-item", action="store_true", help=_("retrieve last item") |
449 ) | |
410 elif flags.multi_items: | 450 elif flags.multi_items: |
411 # mutiple items, this activate several features: max-items, RSM, MAM | 451 # mutiple items, this activate several features: max-items, RSM, MAM |
412 # and Orbder-by | 452 # and Orbder-by |
413 pubsub_group.add_argument( | 453 pubsub_group.add_argument( |
414 "-i", "--item", action='append', dest='items', default=[], | 454 "-i", |
415 help=_("items to retrieve (DEFAULT: all)")) | 455 "--item", |
456 action="append", | |
457 dest="items", | |
458 default=[], | |
459 help=_("items to retrieve (DEFAULT: all)"), | |
460 ) | |
416 if not flags.no_max: | 461 if not flags.no_max: |
417 max_group = pubsub_group.add_mutually_exclusive_group() | 462 max_group = pubsub_group.add_mutually_exclusive_group() |
418 # XXX: defaut value for --max-items or --max is set in parse_pubsub_args | 463 # XXX: defaut value for --max-items or --max is set in parse_pubsub_args |
419 max_group.add_argument( | 464 max_group.add_argument( |
420 "-M", "--max-items", dest="max", type=int, | 465 "-M", |
421 help=_("maximum number of items to get ({no_limit} to get all items)" | 466 "--max-items", |
422 .format(no_limit=C.NO_LIMIT))) | 467 dest="max", |
468 type=int, | |
469 help=_( | |
470 "maximum number of items to get ({no_limit} to get all items)".format( | |
471 no_limit=C.NO_LIMIT | |
472 ) | |
473 ), | |
474 ) | |
423 # FIXME: it could be possible to no duplicate max (between pubsub | 475 # FIXME: it could be possible to no duplicate max (between pubsub |
424 # max-items and RSM max)should not be duplicated, RSM could be | 476 # max-items and RSM max)should not be duplicated, RSM could be |
425 # used when available and pubsub max otherwise | 477 # used when available and pubsub max otherwise |
426 max_group.add_argument( | 478 max_group.add_argument( |
427 "-m", "--max", dest="rsm_max", type=int, | 479 "-m", |
428 help=_("maximum number of items to get per page (DEFAULT: 10)")) | 480 "--max", |
481 dest="rsm_max", | |
482 type=int, | |
483 help=_("maximum number of items to get per page (DEFAULT: 10)"), | |
484 ) | |
429 | 485 |
430 # RSM | 486 # RSM |
431 | 487 |
432 rsm_page_group = pubsub_group.add_mutually_exclusive_group() | 488 rsm_page_group = pubsub_group.add_mutually_exclusive_group() |
433 rsm_page_group.add_argument( | 489 rsm_page_group.add_argument( |
434 "-a", "--after", dest="rsm_after", | 490 "-a", |
435 help=_("find page after this item"), metavar='ITEM_ID') | 491 "--after", |
492 dest="rsm_after", | |
493 help=_("find page after this item"), | |
494 metavar="ITEM_ID", | |
495 ) | |
436 rsm_page_group.add_argument( | 496 rsm_page_group.add_argument( |
437 "-b", "--before", dest="rsm_before", | 497 "-b", |
438 help=_("find page before this item"), metavar='ITEM_ID') | 498 "--before", |
499 dest="rsm_before", | |
500 help=_("find page before this item"), | |
501 metavar="ITEM_ID", | |
502 ) | |
439 rsm_page_group.add_argument( | 503 rsm_page_group.add_argument( |
440 "--index", dest="rsm_index", type=int, | 504 "--index", |
441 help=_("index of the first item to retrieve")) | 505 dest="rsm_index", |
442 | 506 type=int, |
507 help=_("index of the first item to retrieve"), | |
508 ) | |
443 | 509 |
444 # MAM | 510 # MAM |
445 | 511 |
446 pubsub_group.add_argument( | 512 pubsub_group.add_argument( |
447 "-f", "--filter", dest='mam_filters', nargs=2, | 513 "-f", |
448 action='append', default=[], help=_("MAM filters to use"), | 514 "--filter", |
449 metavar=("FILTER_NAME", "VALUE") | 515 dest="mam_filters", |
516 nargs=2, | |
517 action="append", | |
518 default=[], | |
519 help=_("MAM filters to use"), | |
520 metavar=("FILTER_NAME", "VALUE"), | |
450 ) | 521 ) |
451 | 522 |
452 # Order-By | 523 # Order-By |
453 | 524 |
454 # TODO: order-by should be a list to handle several levels of ordering | 525 # TODO: order-by should be a list to handle several levels of ordering |
455 # but this is not yet done in SàT (and not really useful with | 526 # but this is not yet done in SàT (and not really useful with |
456 # current specifications, as only "creation" and "modification" are | 527 # current specifications, as only "creation" and "modification" are |
457 # available) | 528 # available) |
458 pubsub_group.add_argument( | 529 pubsub_group.add_argument( |
459 "-o", "--order-by", choices=[C.ORDER_BY_CREATION, | 530 "-o", |
460 C.ORDER_BY_MODIFICATION], | 531 "--order-by", |
461 help=_("how items should be ordered")) | 532 choices=[C.ORDER_BY_CREATION, C.ORDER_BY_MODIFICATION], |
533 help=_("how items should be ordered"), | |
534 ) | |
462 | 535 |
463 if flags[C.CACHE]: | 536 if flags[C.CACHE]: |
464 pubsub_group.add_argument( | 537 pubsub_group.add_argument( |
465 "-C", "--no-cache", dest="use_cache", action='store_false', | 538 "-C", |
466 help=_("don't use Pubsub cache") | 539 "--no-cache", |
540 dest="use_cache", | |
541 action="store_false", | |
542 help=_("don't use Pubsub cache"), | |
467 ) | 543 ) |
468 | 544 |
469 if not flags.all_used: | 545 if not flags.all_used: |
470 raise exceptions.InternalError('unknown flags: {flags}'.format( | 546 raise exceptions.InternalError( |
471 flags=', '.join(flags.unused))) | 547 "unknown flags: {flags}".format(flags=", ".join(flags.unused)) |
548 ) | |
472 if defaults: | 549 if defaults: |
473 raise exceptions.InternalError(f'unused defaults: {defaults}') | 550 raise exceptions.InternalError(f"unused defaults: {defaults}") |
474 | 551 |
475 return parent | 552 return parent |
476 | 553 |
477 def add_parser_options(self): | 554 def add_parser_options(self): |
478 self.parser.add_argument( | 555 self.parser.add_argument( |
479 '--version', | 556 "--version", |
480 action='version', | 557 action="version", |
481 version=("{name} {version} {copyleft}".format( | 558 version=( |
482 name = C.APP_NAME, | 559 "{name} {version} {copyleft}".format( |
483 version = self.version, | 560 name=C.APP_NAME, version=self.version, copyleft=COPYLEFT |
484 copyleft = COPYLEFT)) | 561 ) |
562 ), | |
485 ) | 563 ) |
486 | 564 |
487 def register_output(self, type_, name, callback, description="", default=False): | 565 def register_output(self, type_, name, callback, description="", default=False): |
488 if type_ not in C.OUTPUT_TYPES: | 566 if type_ not in C.OUTPUT_TYPES: |
489 log.error("Invalid output type {}".format(type_)) | 567 log.error("Invalid output type {}".format(type_)) |
490 return | 568 return |
491 self._outputs[type_][name] = {'callback': callback, | 569 self._outputs[type_][name] = {"callback": callback, "description": description} |
492 'description': description | |
493 } | |
494 if default: | 570 if default: |
495 if type_ in self.default_output: | 571 if type_ in self.default_output: |
496 self.disp( | 572 self.disp( |
497 _('there is already a default output for {type}, ignoring new one') | 573 _( |
498 .format(type=type_) | 574 "there is already a default output for {type}, ignoring new one" |
575 ).format(type=type_) | |
499 ) | 576 ) |
500 else: | 577 else: |
501 self.default_output[type_] = name | 578 self.default_output[type_] = name |
502 | |
503 | 579 |
504 def parse_output_options(self): | 580 def parse_output_options(self): |
505 options = self.command.args.output_opts | 581 options = self.command.args.output_opts |
506 options_dict = {} | 582 options_dict = {} |
507 for option in options: | 583 for option in options: |
508 try: | 584 try: |
509 key, value = option.split('=', 1) | 585 key, value = option.split("=", 1) |
510 except ValueError: | 586 except ValueError: |
511 key, value = option, None | 587 key, value = option, None |
512 options_dict[key.strip()] = value.strip() if value is not None else None | 588 options_dict[key.strip()] = value.strip() if value is not None else None |
513 return options_dict | 589 return options_dict |
514 | 590 |
515 def check_output_options(self, accepted_set, options): | 591 def check_output_options(self, accepted_set, options): |
516 if not accepted_set.issuperset(options): | 592 if not accepted_set.issuperset(options): |
517 self.disp( | 593 self.disp( |
518 _("The following output options are invalid: {invalid_options}").format( | 594 _("The following output options are invalid: {invalid_options}").format( |
519 invalid_options = ', '.join(set(options).difference(accepted_set))), | 595 invalid_options=", ".join(set(options).difference(accepted_set)) |
520 error=True) | 596 ), |
597 error=True, | |
598 ) | |
521 self.quit(C.EXIT_BAD_ARG) | 599 self.quit(C.EXIT_BAD_ARG) |
522 | 600 |
523 def import_plugins(self): | 601 def import_plugins(self): |
524 """Automaticaly import commands and outputs in CLI frontend | 602 """Automaticaly import commands and outputs in CLI frontend |
525 | 603 |
526 looks from modules names cmd_*.py in CLI frontend path and import them | 604 looks from modules names cmd_*.py in CLI frontend path and import them |
527 """ | 605 """ |
528 path = os.path.dirname(libervia.cli.__file__) | 606 path = os.path.dirname(libervia.cli.__file__) |
529 # XXX: outputs must be imported before commands as they are used for arguments | 607 # XXX: outputs must be imported before commands as they are used for arguments |
530 for type_, pattern in ((C.PLUGIN_OUTPUT, 'output_*.py'), | 608 for type_, pattern in ( |
531 (C.PLUGIN_CMD, 'cmd_*.py')): | 609 (C.PLUGIN_OUTPUT, "output_*.py"), |
610 (C.PLUGIN_CMD, "cmd_*.py"), | |
611 ): | |
532 modules = ( | 612 modules = ( |
533 os.path.splitext(module)[0] | 613 os.path.splitext(module)[0] |
534 for module in map(os.path.basename, iglob(os.path.join(path, pattern)))) | 614 for module in map(os.path.basename, iglob(os.path.join(path, pattern))) |
615 ) | |
535 for module_name in modules: | 616 for module_name in modules: |
536 module_path = "libervia.cli." + module_name | 617 module_path = "libervia.cli." + module_name |
537 try: | 618 try: |
538 module = import_module(module_path) | 619 module = import_module(module_path) |
539 self.import_plugin_module(module, type_) | 620 self.import_plugin_module(module, type_) |
540 except exceptions.CancelError: | 621 except exceptions.CancelError: |
541 continue | 622 continue |
542 except exceptions.MissingModule as e: | 623 except exceptions.MissingModule as e: |
543 self.disp(_("Missing module for plugin {name}: {missing}".format( | 624 self.disp( |
544 name = module_path, | 625 _( |
545 missing = e)), error=True) | 626 "Missing module for plugin {name}: {missing}".format( |
627 name=module_path, missing=e | |
628 ) | |
629 ), | |
630 error=True, | |
631 ) | |
546 except Exception as e: | 632 except Exception as e: |
547 self.disp( | 633 self.disp( |
548 _("Can't import {module_path} plugin, ignoring it: {e}") | 634 _("Can't import {module_path} plugin, ignoring it: {e}").format( |
549 .format(module_path=module_path, e=e), | 635 module_path=module_path, e=e |
550 error=True) | 636 ), |
551 | 637 error=True, |
638 ) | |
552 | 639 |
553 def import_plugin_module(self, module, type_): | 640 def import_plugin_module(self, module, type_): |
554 """add commands or outpus from a module to CLI frontend | 641 """add commands or outpus from a module to CLI frontend |
555 | 642 |
556 @param module: module containing commands or outputs | 643 @param module: module containing commands or outputs |
557 @param type_(str): one of C_PLUGIN_* | 644 @param type_(str): one of C_PLUGIN_* |
558 """ | 645 """ |
559 try: | 646 try: |
560 class_names = getattr(module, '__{}__'.format(type_)) | 647 class_names = getattr(module, "__{}__".format(type_)) |
561 except AttributeError: | 648 except AttributeError: |
562 log.disp( | 649 log.disp( |
563 _("Invalid plugin module [{type}] {module}") | 650 _("Invalid plugin module [{type}] {module}").format( |
564 .format(type=type_, module=module), | 651 type=type_, module=module |
565 error=True) | 652 ), |
653 error=True, | |
654 ) | |
566 raise ImportError | 655 raise ImportError |
567 else: | 656 else: |
568 for class_name in class_names: | 657 for class_name in class_names: |
569 cls = getattr(module, class_name) | 658 cls = getattr(module, class_name) |
570 cls(self) | 659 cls(self) |
571 | 660 |
572 def get_xmpp_uri_from_http(self, http_url): | 661 def get_xmpp_uri_from_http(self, http_url): |
573 """parse HTML page at http(s) URL, and looks for xmpp: uri""" | 662 """parse HTML page at http(s) URL, and looks for xmpp: uri""" |
574 if http_url.startswith('https'): | 663 if http_url.startswith("https"): |
575 scheme = 'https' | 664 scheme = "https" |
576 elif http_url.startswith('http'): | 665 elif http_url.startswith("http"): |
577 scheme = 'http' | 666 scheme = "http" |
578 else: | 667 else: |
579 raise exceptions.InternalError('An HTTP scheme is expected in this method') | 668 raise exceptions.InternalError("An HTTP scheme is expected in this method") |
580 self.disp(f"{scheme.upper()} URL found, trying to find associated xmpp: URI", 1) | 669 self.disp(f"{scheme.upper()} URL found, trying to find associated xmpp: URI", 1) |
581 # HTTP URL, we try to find xmpp: links | 670 # HTTP URL, we try to find xmpp: links |
582 try: | 671 try: |
583 from lxml import etree | 672 from lxml import etree |
584 except ImportError: | 673 except ImportError: |
585 self.disp( | 674 self.disp( |
586 "lxml module must be installed to use http(s) scheme, please install it " | 675 "lxml module must be installed to use http(s) scheme, please install it " |
587 "with \"pip install lxml\"", | 676 'with "pip install lxml"', |
588 error=True) | 677 error=True, |
678 ) | |
589 self.quit(1) | 679 self.quit(1) |
590 import urllib.request, urllib.error, urllib.parse | 680 import urllib.request, urllib.error, urllib.parse |
681 | |
591 parser = etree.HTMLParser() | 682 parser = etree.HTMLParser() |
592 try: | 683 try: |
593 root = etree.parse(urllib.request.urlopen(http_url), parser) | 684 root = etree.parse(urllib.request.urlopen(http_url), parser) |
594 except etree.XMLSyntaxError as e: | 685 except etree.XMLSyntaxError as e: |
595 self.disp(_("Can't parse HTML page : {msg}").format(msg=e)) | 686 self.disp(_("Can't parse HTML page : {msg}").format(msg=e)) |
596 links = [] | 687 links = [] |
597 else: | 688 else: |
598 links = root.xpath("//link[@rel='alternate' and starts-with(@href, 'xmpp:')]") | 689 links = root.xpath("//link[@rel='alternate' and starts-with(@href, 'xmpp:')]") |
599 if not links: | 690 if not links: |
600 self.disp( | 691 self.disp( |
601 _('Could not find alternate "xmpp:" URI, can\'t find associated XMPP ' | 692 _( |
602 'PubSub node/item'), | 693 'Could not find alternate "xmpp:" URI, can\'t find associated XMPP ' |
603 error=True) | 694 "PubSub node/item" |
695 ), | |
696 error=True, | |
697 ) | |
604 self.quit(1) | 698 self.quit(1) |
605 xmpp_uri = links[0].get('href') | 699 xmpp_uri = links[0].get("href") |
606 return xmpp_uri | 700 return xmpp_uri |
607 | 701 |
608 def parse_pubsub_args(self): | 702 def parse_pubsub_args(self): |
609 if self.args.pubsub_url is not None: | 703 if self.args.pubsub_url is not None: |
610 url = self.args.pubsub_url | 704 url = self.args.pubsub_url |
611 | 705 |
612 if url.startswith('http'): | 706 if url.startswith("http"): |
613 # http(s) URL, we try to retrieve xmpp one from there | 707 # http(s) URL, we try to retrieve xmpp one from there |
614 url = self.get_xmpp_uri_from_http(url) | 708 url = self.get_xmpp_uri_from_http(url) |
615 | 709 |
616 try: | 710 try: |
617 uri_data = uri.parse_xmpp_uri(url) | 711 uri_data = uri.parse_xmpp_uri(url) |
618 except ValueError: | 712 except ValueError: |
619 self.parser.error(_('invalid XMPP URL: {url}').format(url=url)) | 713 self.parser.error(_("invalid XMPP URL: {url}").format(url=url)) |
620 else: | 714 else: |
621 if uri_data['type'] == 'pubsub': | 715 if uri_data["type"] == "pubsub": |
622 # URL is alright, we only set data not already set by other options | 716 # URL is alright, we only set data not already set by other options |
623 if not self.args.service: | 717 if not self.args.service: |
624 self.args.service = uri_data['path'] | 718 self.args.service = uri_data["path"] |
625 if not self.args.node: | 719 if not self.args.node: |
626 self.args.node = uri_data['node'] | 720 self.args.node = uri_data["node"] |
627 uri_item = uri_data.get('item') | 721 uri_item = uri_data.get("item") |
628 if uri_item: | 722 if uri_item: |
629 # there is an item in URI | 723 # there is an item in URI |
630 # we use it only if item is not already set | 724 # we use it only if item is not already set |
631 # and item_last is not used either | 725 # and item_last is not used either |
632 try: | 726 try: |
634 except AttributeError: | 728 except AttributeError: |
635 try: | 729 try: |
636 items = self.args.items | 730 items = self.args.items |
637 except AttributeError: | 731 except AttributeError: |
638 self.disp( | 732 self.disp( |
639 _("item specified in URL but not needed in command, " | 733 _( |
640 "ignoring it"), | 734 "item specified in URL but not needed in command, " |
641 error=True) | 735 "ignoring it" |
736 ), | |
737 error=True, | |
738 ) | |
642 else: | 739 else: |
643 if not items: | 740 if not items: |
644 self.args.items = [uri_item] | 741 self.args.items = [uri_item] |
645 else: | 742 else: |
646 if not item: | 743 if not item: |
650 item_last = False | 747 item_last = False |
651 if not item_last: | 748 if not item_last: |
652 self.args.item = uri_item | 749 self.args.item = uri_item |
653 else: | 750 else: |
654 self.parser.error( | 751 self.parser.error( |
655 _('XMPP URL is not a pubsub one: {url}').format(url=url) | 752 _("XMPP URL is not a pubsub one: {url}").format(url=url) |
656 ) | 753 ) |
657 flags = self.args._cmd._pubsub_flags | 754 flags = self.args._cmd._pubsub_flags |
658 # we check required arguments here instead of using add_arguments' required option | 755 # we check required arguments here instead of using add_arguments' required option |
659 # because the required argument can be set in URL | 756 # because the required argument can be set in URL |
660 if C.SERVICE in flags and not self.args.service: | 757 if C.SERVICE in flags and not self.args.service: |
667 # FIXME: mutually groups can't be nested in a group and don't support title | 764 # FIXME: mutually groups can't be nested in a group and don't support title |
668 # so we check conflict here. This may be fixed in Python 3, to be checked | 765 # so we check conflict here. This may be fixed in Python 3, to be checked |
669 try: | 766 try: |
670 if self.args.item and self.args.item_last: | 767 if self.args.item and self.args.item_last: |
671 self.parser.error( | 768 self.parser.error( |
672 _("--item and --item-last can't be used at the same time")) | 769 _("--item and --item-last can't be used at the same time") |
770 ) | |
673 except AttributeError: | 771 except AttributeError: |
674 pass | 772 pass |
675 | 773 |
676 try: | 774 try: |
677 max_items = self.args.max | 775 max_items = self.args.max |
681 else: | 779 else: |
682 # we need to set a default value for max, but we need to know if we want | 780 # we need to set a default value for max, but we need to know if we want |
683 # to use pubsub's max or RSM's max. The later is used if any RSM or MAM | 781 # to use pubsub's max or RSM's max. The later is used if any RSM or MAM |
684 # argument is set | 782 # argument is set |
685 if max_items is None and rsm_max is None: | 783 if max_items is None and rsm_max is None: |
686 to_check = ('mam_filters', 'rsm_max', 'rsm_after', 'rsm_before', | 784 to_check = ( |
687 'rsm_index') | 785 "mam_filters", |
786 "rsm_max", | |
787 "rsm_after", | |
788 "rsm_before", | |
789 "rsm_index", | |
790 ) | |
688 if any((getattr(self.args, name) for name in to_check)): | 791 if any((getattr(self.args, name) for name in to_check)): |
689 # we use RSM | 792 # we use RSM |
690 self.args.rsm_max = 10 | 793 self.args.rsm_max = 10 |
691 else: | 794 else: |
692 # we use pubsub without RSM | 795 # we use pubsub without RSM |
698 try: | 801 try: |
699 await self.bridge.bridge_connect() | 802 await self.bridge.bridge_connect() |
700 except Exception as e: | 803 except Exception as e: |
701 if isinstance(e, exceptions.BridgeExceptionNoService): | 804 if isinstance(e, exceptions.BridgeExceptionNoService): |
702 print( | 805 print( |
703 _("Can't connect to Libervia backend, are you sure that it's " | 806 _( |
704 "launched ?") | 807 "Can't connect to Libervia backend, are you sure that it's " |
808 "launched ?" | |
809 ) | |
705 ) | 810 ) |
706 self.quit(C.EXIT_BACKEND_NOT_FOUND, raise_exc=False) | 811 self.quit(C.EXIT_BACKEND_NOT_FOUND, raise_exc=False) |
707 elif isinstance(e, exceptions.BridgeInitError): | 812 elif isinstance(e, exceptions.BridgeInitError): |
708 print(_("Can't init bridge")) | 813 print(_("Can't init bridge")) |
709 self.quit(C.EXIT_BRIDGE_ERROR, raise_exc=False) | 814 self.quit(C.EXIT_BRIDGE_ERROR, raise_exc=False) |
710 else: | 815 else: |
711 print( | 816 print(_("Error while initialising bridge: {e}").format(e=e)) |
712 _("Error while initialising bridge: {e}").format(e=e) | |
713 ) | |
714 self.quit(C.EXIT_BRIDGE_ERROR, raise_exc=False) | 817 self.quit(C.EXIT_BRIDGE_ERROR, raise_exc=False) |
715 return | 818 return |
716 # we wait on init_pre_script instead of ready_get, so the CLI frontend can be used | 819 # we wait on init_pre_script instead of ready_get, so the CLI frontend can be used |
717 # in init script. | 820 # in init script. |
718 await self.bridge.init_pre_script() | 821 await self.bridge.init_pre_script() |
741 | 844 |
742 @classmethod | 845 @classmethod |
743 def run(cls): | 846 def run(cls): |
744 cls()._run() | 847 cls()._run() |
745 | 848 |
746 | |
747 def _read_stdin(self, stdin_fut): | 849 def _read_stdin(self, stdin_fut): |
748 """Callback called by ainput to read stdin""" | 850 """Callback called by ainput to read stdin""" |
749 line = sys.stdin.readline() | 851 line = sys.stdin.readline() |
750 if line: | 852 if line: |
751 stdin_fut.set_result(line.rstrip(os.linesep)) | 853 stdin_fut.set_result(line.rstrip(os.linesep)) |
752 else: | 854 else: |
753 stdin_fut.set_exception(EOFError()) | 855 stdin_fut.set_exception(EOFError()) |
754 | 856 |
755 async def ainput(self, msg=''): | 857 async def ainput(self, msg=""): |
756 """Asynchronous version of buildin "input" function""" | 858 """Asynchronous version of buildin "input" function""" |
757 self.disp(msg, end=' ') | 859 self.disp(msg, end=" ") |
758 sys.stdout.flush() | 860 sys.stdout.flush() |
759 loop = asyncio.get_running_loop() | 861 loop = asyncio.get_running_loop() |
760 stdin_fut = loop.create_future() | 862 stdin_fut = loop.create_future() |
761 loop.add_reader(sys.stdin, self._read_stdin, stdin_fut) | 863 loop.add_reader(sys.stdin, self._read_stdin, stdin_fut) |
762 return await stdin_fut | 864 return await stdin_fut |
764 async def confirm(self, message): | 866 async def confirm(self, message): |
765 """Request user to confirm action, return answer as boolean""" | 867 """Request user to confirm action, return answer as boolean""" |
766 res = await self.ainput(f"{message} (y/N)? ") | 868 res = await self.ainput(f"{message} (y/N)? ") |
767 return res in ("y", "Y") | 869 return res in ("y", "Y") |
768 | 870 |
769 async def confirm_or_quit(self, message, cancel_message=_("action cancelled by user")): | 871 async def confirm_or_quit( |
872 self, message, cancel_message=_("action cancelled by user") | |
873 ): | |
770 """Request user to confirm action, and quit if he doesn't""" | 874 """Request user to confirm action, and quit if he doesn't""" |
771 confirmed = await self.confirm(message) | 875 confirmed = await self.confirm(message) |
772 if not confirmed: | 876 if not confirmed: |
773 self.disp(cancel_message) | 877 self.disp(cancel_message) |
774 self.quit(C.EXIT_USER_CANCELLED) | 878 self.quit(C.EXIT_USER_CANCELLED) |
802 | 906 |
803 self.loop.quit(exit_code) | 907 self.loop.quit(exit_code) |
804 if raise_exc: | 908 if raise_exc: |
805 raise QuitException | 909 raise QuitException |
806 | 910 |
807 async def a_quit(self, exit_code: int=0, raise_exc=True): | 911 async def a_quit(self, exit_code: int = 0, raise_exc=True): |
808 """Execute async quit callback before actually quitting | 912 """Execute async quit callback before actually quitting |
809 | 913 |
810 This method should be prefered to ``quit``, as it executes async quit callbacks | 914 This method should be prefered to ``quit``, as it executes async quit callbacks |
811 which may be necessary for proper cleaning of session. | 915 which may be necessary for proper cleaning of session. |
812 """ | 916 """ |
862 expanded = jid | 966 expanded = jid |
863 return expanded | 967 return expanded |
864 | 968 |
865 def check(jid): | 969 def check(jid): |
866 if not jid.is_valid: | 970 if not jid.is_valid: |
867 log.error (_("%s is not a valid JID !"), jid) | 971 log.error(_("%s is not a valid JID !"), jid) |
868 self.quit(1) | 972 self.quit(1) |
869 | 973 |
870 dest_jids=[] | 974 dest_jids = [] |
871 try: | 975 try: |
872 for i in range(len(jids)): | 976 for i in range(len(jids)): |
873 dest_jids.append(expand_jid(jids[i])) | 977 dest_jids.append(expand_jid(jids[i])) |
874 check(dest_jids[i]) | 978 check(dest_jids[i]) |
875 except AttributeError: | 979 except AttributeError: |
876 pass | 980 pass |
877 | 981 |
878 return dest_jids | 982 return dest_jids |
879 | 983 |
880 async def a_pwd_input(self, msg=''): | 984 async def a_pwd_input(self, msg=""): |
881 """Like ainput but with echo disabled (useful for passwords)""" | 985 """Like ainput but with echo disabled (useful for passwords)""" |
882 # we disable echo, code adapted from getpass standard module which has been | 986 # we disable echo, code adapted from getpass standard module which has been |
883 # written by Piers Lauder (original), Guido van Rossum (Windows support and | 987 # written by Piers Lauder (original), Guido van Rossum (Windows support and |
884 # cleanup) and Gregory P. Smith (tty support & GetPassWarning), a big thanks | 988 # cleanup) and Gregory P. Smith (tty support & GetPassWarning), a big thanks |
885 # to them (and for all the amazing work on Python). | 989 # to them (and for all the amazing work on Python). |
886 stdin_fd = sys.stdin.fileno() | 990 stdin_fd = sys.stdin.fileno() |
887 old = termios.tcgetattr(sys.stdin) | 991 old = termios.tcgetattr(sys.stdin) |
888 new = old[:] | 992 new = old[:] |
889 new[3] &= ~termios.ECHO | 993 new[3] &= ~termios.ECHO |
890 tcsetattr_flags = termios.TCSAFLUSH | 994 tcsetattr_flags = termios.TCSAFLUSH |
891 if hasattr(termios, 'TCSASOFT'): | 995 if hasattr(termios, "TCSASOFT"): |
892 tcsetattr_flags |= termios.TCSASOFT | 996 tcsetattr_flags |= termios.TCSASOFT |
893 try: | 997 try: |
894 termios.tcsetattr(stdin_fd, tcsetattr_flags, new) | 998 termios.tcsetattr(stdin_fd, tcsetattr_flags, new) |
895 pwd = await self.ainput(msg=msg) | 999 pwd = await self.ainput(msg=msg) |
896 finally: | 1000 finally: |
897 termios.tcsetattr(stdin_fd, tcsetattr_flags, old) | 1001 termios.tcsetattr(stdin_fd, tcsetattr_flags, old) |
898 sys.stderr.flush() | 1002 sys.stderr.flush() |
899 self.disp('') | 1003 self.disp("") |
900 return pwd | 1004 return pwd |
901 | 1005 |
902 async def connect_or_prompt(self, method, err_msg=None): | 1006 async def connect_or_prompt(self, method, err_msg=None): |
903 """Try to connect/start profile session and prompt for password if needed | 1007 """Try to connect/start profile session and prompt for password if needed |
904 | 1008 |
908 @param err_msg(str): message to show if connection fail | 1012 @param err_msg(str): message to show if connection fail |
909 """ | 1013 """ |
910 password = self.args.pwd | 1014 password = self.args.pwd |
911 while True: | 1015 while True: |
912 try: | 1016 try: |
913 await method(password or '') | 1017 await method(password or "") |
914 except Exception as e: | 1018 except Exception as e: |
915 if ((isinstance(e, BridgeException) | 1019 if ( |
916 and e.classname == 'PasswordError' | 1020 isinstance(e, BridgeException) |
917 and self.args.pwd is None)): | 1021 and e.classname == "PasswordError" |
1022 and self.args.pwd is None | |
1023 ): | |
918 if password is not None: | 1024 if password is not None: |
919 self.disp(A.color(C.A_WARNING, _("invalid password"))) | 1025 self.disp(A.color(C.A_WARNING, _("invalid password"))) |
920 password = await self.a_pwd_input( | 1026 password = await self.a_pwd_input(_("please enter profile password:")) |
921 _("please enter profile password:")) | |
922 else: | 1027 else: |
923 self.disp(err_msg.format(profile=self.profile, e=e), error=True) | 1028 self.disp(err_msg.format(profile=self.profile, e=e), error=True) |
924 self.quit(C.EXIT_ERROR) | 1029 self.quit(C.EXIT_ERROR) |
925 else: | 1030 else: |
926 break | 1031 break |
936 | 1041 |
937 self.profile = await self.bridge.profile_name_get(self.args.profile) | 1042 self.profile = await self.bridge.profile_name_get(self.args.profile) |
938 | 1043 |
939 if not self.profile: | 1044 if not self.profile: |
940 log.error( | 1045 log.error( |
941 _("The profile [{profile}] doesn't exist") | 1046 _("The profile [{profile}] doesn't exist").format( |
942 .format(profile=self.args.profile) | 1047 profile=self.args.profile |
1048 ) | |
943 ) | 1049 ) |
944 self.quit(C.EXIT_ERROR) | 1050 self.quit(C.EXIT_ERROR) |
945 | 1051 |
946 try: | 1052 try: |
947 start_session = self.args.start_session | 1053 start_session = self.args.start_session |
949 pass | 1055 pass |
950 else: | 1056 else: |
951 if start_session: | 1057 if start_session: |
952 await self.connect_or_prompt( | 1058 await self.connect_or_prompt( |
953 lambda pwd: self.bridge.profile_start_session(pwd, self.profile), | 1059 lambda pwd: self.bridge.profile_start_session(pwd, self.profile), |
954 err_msg="Can't start {profile}'s session: {e}" | 1060 err_msg="Can't start {profile}'s session: {e}", |
955 ) | 1061 ) |
956 return | 1062 return |
957 elif not await self.bridge.profile_is_session_started(self.profile): | 1063 elif not await self.bridge.profile_is_session_started(self.profile): |
958 if not self.args.connect: | 1064 if not self.args.connect: |
959 self.disp(_( | 1065 self.disp( |
960 "Session for [{profile}] is not started, please start it " | 1066 _( |
961 "before using libervia-cli, or use either --start-session or " | 1067 "Session for [{profile}] is not started, please start it " |
962 "--connect option" | 1068 "before using libervia-cli, or use either --start-session or " |
963 .format(profile=self.profile) | 1069 "--connect option".format(profile=self.profile) |
964 ), error=True) | 1070 ), |
1071 error=True, | |
1072 ) | |
965 self.quit(1) | 1073 self.quit(1) |
966 elif not getattr(self.args, "connect", False): | 1074 elif not getattr(self.args, "connect", False): |
967 return | 1075 return |
968 | 1076 |
969 | 1077 if not hasattr(self.args, "connect"): |
970 if not hasattr(self.args, 'connect'): | |
971 # a profile can be present without connect option (e.g. on profile | 1078 # a profile can be present without connect option (e.g. on profile |
972 # creation/deletion) | 1079 # creation/deletion) |
973 return | 1080 return |
974 elif self.args.connect is True: # if connection is asked, we connect the profile | 1081 elif self.args.connect is True: # if connection is asked, we connect the profile |
975 await self.connect_or_prompt( | 1082 await self.connect_or_prompt( |
976 lambda pwd: self.bridge.connect(self.profile, pwd, {}), | 1083 lambda pwd: self.bridge.connect(self.profile, pwd, {}), |
977 err_msg = 'Can\'t connect profile "{profile!s}": {e}' | 1084 err_msg='Can\'t connect profile "{profile!s}": {e}', |
978 ) | 1085 ) |
979 return | 1086 return |
980 else: | 1087 else: |
981 if not await self.bridge.is_connected(self.profile): | 1088 if not await self.bridge.is_connected(self.profile): |
982 log.error( | 1089 log.error( |
983 _("Profile [{profile}] is not connected, please connect it " | 1090 _( |
984 "before using libervia-cli, or use --connect option") | 1091 "Profile [{profile}] is not connected, please connect it " |
985 .format(profile=self.profile) | 1092 "before using libervia-cli, or use --connect option" |
1093 ).format(profile=self.profile) | |
986 ) | 1094 ) |
987 self.quit(1) | 1095 self.quit(1) |
988 | 1096 |
989 async def get_full_jid(self, param_jid): | 1097 async def get_full_jid(self, param_jid): |
990 """Return the full jid if possible (add main resource when find a bare jid)""" | 1098 """Return the full jid if possible (add main resource when find a bare jid)""" |
991 # TODO: to be removed, bare jid should work with all commands, notably for file | 1099 # TODO: to be removed, bare jid should work with all commands, notably for file |
992 # as backend now handle jingles message initiation | 1100 # as backend now handle jingles message initiation |
993 _jid = JID(param_jid) | 1101 _jid = JID(param_jid) |
994 if not _jid.resource: | 1102 if not _jid.resource: |
995 #if the resource is not given, we try to add the main resource | 1103 # if the resource is not given, we try to add the main resource |
996 main_resource = await self.bridge.main_resource_get(param_jid, self.profile) | 1104 main_resource = await self.bridge.main_resource_get(param_jid, self.profile) |
997 if main_resource: | 1105 if main_resource: |
998 return f"{_jid.bare}/{main_resource}" | 1106 return f"{_jid.bare}/{main_resource}" |
999 return param_jid | 1107 return param_jid |
1000 | 1108 |
1015 use_profile: bool = True, | 1123 use_profile: bool = True, |
1016 use_output: Union[bool, str] = False, | 1124 use_output: Union[bool, str] = False, |
1017 extra_outputs: Optional[dict] = None, | 1125 extra_outputs: Optional[dict] = None, |
1018 need_connect: Optional[bool] = None, | 1126 need_connect: Optional[bool] = None, |
1019 help: Optional[str] = None, | 1127 help: Optional[str] = None, |
1020 **kwargs | 1128 **kwargs, |
1021 ): | 1129 ): |
1022 """Initialise CommandBase | 1130 """Initialise CommandBase |
1023 | 1131 |
1024 @param host: LiberviaCli instance | 1132 @param host: LiberviaCli instance |
1025 @param name: name of the new command | 1133 @param name: name of the new command |
1049 C.SERVICE: service is required | 1157 C.SERVICE: service is required |
1050 C.NODE: node is required | 1158 C.NODE: node is required |
1051 C.ITEM: item is required | 1159 C.ITEM: item is required |
1052 C.SINGLE_ITEM: only one item is allowed | 1160 C.SINGLE_ITEM: only one item is allowed |
1053 """ | 1161 """ |
1054 try: # If we have subcommands, host is a CommandBase and we need to use host.host | 1162 try: # If we have subcommands, host is a CommandBase and we need to use host.host |
1055 self.host = host.host | 1163 self.host = host.host |
1056 except AttributeError: | 1164 except AttributeError: |
1057 self.host = host | 1165 self.host = host |
1058 | 1166 |
1059 # --profile option | 1167 # --profile option |
1060 parents = kwargs.setdefault('parents', set()) | 1168 parents = kwargs.setdefault("parents", set()) |
1061 if use_profile: | 1169 if use_profile: |
1062 # self.host.parents['profile'] is an ArgumentParser with profile connection | 1170 # self.host.parents['profile'] is an ArgumentParser with profile connection |
1063 # arguments | 1171 # arguments |
1064 if need_connect is None: | 1172 if need_connect is None: |
1065 need_connect = True | 1173 need_connect = True |
1066 parents.add( | 1174 parents.add( |
1067 self.host.parents['profile' if need_connect else 'profile_session']) | 1175 self.host.parents["profile" if need_connect else "profile_session"] |
1176 ) | |
1068 else: | 1177 else: |
1069 assert need_connect is None | 1178 assert need_connect is None |
1070 self.need_connect = need_connect | 1179 self.need_connect = need_connect |
1071 # from this point, self.need_connect is None if connection is not needed at all | 1180 # from this point, self.need_connect is None if connection is not needed at all |
1072 # False if session starting is needed, and True if full connection is needed | 1181 # False if session starting is needed, and True if full connection is needed |
1083 output_parent = argparse.ArgumentParser(add_help=False) | 1192 output_parent = argparse.ArgumentParser(add_help=False) |
1084 choices = set(self.host.get_output_choices(use_output)) | 1193 choices = set(self.host.get_output_choices(use_output)) |
1085 choices.update(extra_outputs) | 1194 choices.update(extra_outputs) |
1086 if not choices: | 1195 if not choices: |
1087 raise exceptions.InternalError( | 1196 raise exceptions.InternalError( |
1088 "No choice found for {} output type".format(use_output)) | 1197 "No choice found for {} output type".format(use_output) |
1198 ) | |
1089 try: | 1199 try: |
1090 default = self.host.default_output[use_output] | 1200 default = self.host.default_output[use_output] |
1091 except KeyError: | 1201 except KeyError: |
1092 if 'default' in choices: | 1202 if "default" in choices: |
1093 default = 'default' | 1203 default = "default" |
1094 elif 'simple' in choices: | 1204 elif "simple" in choices: |
1095 default = 'simple' | 1205 default = "simple" |
1096 else: | 1206 else: |
1097 default = list(choices)[0] | 1207 default = list(choices)[0] |
1098 output_parent.add_argument( | 1208 output_parent.add_argument( |
1099 '--output', '-O', choices=sorted(choices), default=default, | 1209 "--output", |
1100 help=_("select output format (default: {})".format(default))) | 1210 "-O", |
1211 choices=sorted(choices), | |
1212 default=default, | |
1213 help=_("select output format (default: {})".format(default)), | |
1214 ) | |
1101 output_parent.add_argument( | 1215 output_parent.add_argument( |
1102 '--output-option', '--oo', action="append", dest='output_opts', | 1216 "--output-option", |
1103 default=[], help=_("output specific option")) | 1217 "--oo", |
1218 action="append", | |
1219 dest="output_opts", | |
1220 default=[], | |
1221 help=_("output specific option"), | |
1222 ) | |
1104 parents.add(output_parent) | 1223 parents.add(output_parent) |
1105 else: | 1224 else: |
1106 assert extra_outputs is None | 1225 assert extra_outputs is None |
1107 | 1226 |
1108 self._use_pubsub = kwargs.pop('use_pubsub', False) | 1227 self._use_pubsub = kwargs.pop("use_pubsub", False) |
1109 if self._use_pubsub: | 1228 if self._use_pubsub: |
1110 flags = kwargs.pop('pubsub_flags', []) | 1229 flags = kwargs.pop("pubsub_flags", []) |
1111 defaults = kwargs.pop('pubsub_defaults', {}) | 1230 defaults = kwargs.pop("pubsub_defaults", {}) |
1112 parents.add(self.host.make_pubsub_group(flags, defaults)) | 1231 parents.add(self.host.make_pubsub_group(flags, defaults)) |
1113 self._pubsub_flags = flags | 1232 self._pubsub_flags = flags |
1114 | 1233 |
1115 # other common options | 1234 # other common options |
1116 use_opts = {k:v for k,v in kwargs.items() if k.startswith('use_')} | 1235 use_opts = {k: v for k, v in kwargs.items() if k.startswith("use_")} |
1117 for param, do_use in use_opts.items(): | 1236 for param, do_use in use_opts.items(): |
1118 opt=param[4:] # if param is use_verbose, opt is verbose | 1237 opt = param[4:] # if param is use_verbose, opt is verbose |
1119 if opt not in self.host.parents: | 1238 if opt not in self.host.parents: |
1120 raise exceptions.InternalError("Unknown parent option {}".format(opt)) | 1239 raise exceptions.InternalError("Unknown parent option {}".format(opt)) |
1121 del kwargs[param] | 1240 del kwargs[param] |
1122 if do_use: | 1241 if do_use: |
1123 parents.add(self.host.parents[opt]) | 1242 parents.add(self.host.parents[opt]) |
1124 | 1243 |
1125 self.parser = host.subparsers.add_parser(name, help=help, **kwargs) | 1244 self.parser = host.subparsers.add_parser(name, help=help, **kwargs) |
1126 if hasattr(self, "subcommands"): | 1245 if hasattr(self, "subcommands"): |
1127 self.subparsers = self.parser.add_subparsers(dest='subcommand', required=True) | 1246 self.subparsers = self.parser.add_subparsers(dest="subcommand", required=True) |
1128 else: | 1247 else: |
1129 self.parser.set_defaults(_cmd=self) | 1248 self.parser.set_defaults(_cmd=self) |
1130 self.add_parser_options() | 1249 self.add_parser_options() |
1131 | 1250 |
1132 @property | 1251 @property |
1196 async def progress_error_handler(self, uid, message, profile): | 1315 async def progress_error_handler(self, uid, message, profile): |
1197 if profile != self.profile: | 1316 if profile != self.profile: |
1198 return | 1317 return |
1199 if uid == self.progress_id: | 1318 if uid == self.progress_id: |
1200 if self.args.progress: | 1319 if self.args.progress: |
1201 self.disp('') # progress is not finished, so we skip a line | 1320 self.disp("") # progress is not finished, so we skip a line |
1202 if self.host.quit_on_progress_end: | 1321 if self.host.quit_on_progress_end: |
1203 await self.on_progress_error(message) | 1322 await self.on_progress_error(message) |
1204 self.host.quit_from_signal(C.EXIT_ERROR) | 1323 self.host.quit_from_signal(C.EXIT_ERROR) |
1205 | 1324 |
1206 async def progress_update(self): | 1325 async def progress_update(self): |
1209 @return (bool): False to stop being called | 1328 @return (bool): False to stop being called |
1210 """ | 1329 """ |
1211 data = await self.host.bridge.progress_get(self.progress_id, self.profile) | 1330 data = await self.host.bridge.progress_get(self.progress_id, self.profile) |
1212 if data: | 1331 if data: |
1213 try: | 1332 try: |
1214 size = data['size'] | 1333 size = data["size"] |
1215 except KeyError: | 1334 except KeyError: |
1216 self.disp(_("file size is not known, we can't show a progress bar"), 1, | 1335 self.disp( |
1217 error=True) | 1336 _("file size is not known, we can't show a progress bar"), |
1337 1, | |
1338 error=True, | |
1339 ) | |
1218 return False | 1340 return False |
1219 if self.host.pbar is None: | 1341 if self.host.pbar is None: |
1220 #first answer, we must construct the bar | 1342 # first answer, we must construct the bar |
1221 | 1343 |
1222 # if the instance has a pbar_template attribute, it is used has model, | 1344 # if the instance has a pbar_template attribute, it is used has model, |
1223 # else default one is used | 1345 # else default one is used |
1224 # template is a list of part, where part can be either a str to show directly | 1346 # template is a list of part, where part can be either a str to show directly |
1225 # or a list where first argument is a name of a progressbar widget, and others | 1347 # or a list where first argument is a name of a progressbar widget, and others |
1226 # are used as widget arguments | 1348 # are used as widget arguments |
1227 try: | 1349 try: |
1228 template = self.pbar_template | 1350 template = self.pbar_template |
1229 except AttributeError: | 1351 except AttributeError: |
1230 template = [ | 1352 template = [ |
1231 _("Progress: "), ["Percentage"], " ", ["Bar"], " ", | 1353 _("Progress: "), |
1232 ["FileTransferSpeed"], " ", ["ETA"] | 1354 ["Percentage"], |
1355 " ", | |
1356 ["Bar"], | |
1357 " ", | |
1358 ["FileTransferSpeed"], | |
1359 " ", | |
1360 ["ETA"], | |
1233 ] | 1361 ] |
1234 | 1362 |
1235 widgets = [] | 1363 widgets = [] |
1236 for part in template: | 1364 for part in template: |
1237 if isinstance(part, str): | 1365 if isinstance(part, str): |
1238 widgets.append(part) | 1366 widgets.append(part) |
1239 else: | 1367 else: |
1240 widget = getattr(progressbar, part.pop(0)) | 1368 widget = getattr(progressbar, part.pop(0)) |
1241 widgets.append(widget(*part)) | 1369 widgets.append(widget(*part)) |
1242 | 1370 |
1243 self.host.pbar = progressbar.ProgressBar(max_value=int(size), widgets=widgets) | 1371 self.host.pbar = progressbar.ProgressBar( |
1372 max_value=int(size), widgets=widgets | |
1373 ) | |
1244 self.host.pbar.start() | 1374 self.host.pbar.start() |
1245 | 1375 |
1246 self.host.pbar.update(int(data['position'])) | 1376 self.host.pbar.update(int(data["position"])) |
1247 | 1377 |
1248 elif self.host.pbar is not None: | 1378 elif self.host.pbar is not None: |
1249 return False | 1379 return False |
1250 | 1380 |
1251 await self.on_progress_update(data) | 1381 await self.on_progress_update(data) |
1281 | 1411 |
1282 @param error_msg(unicode): error message as sent by bridge.progress_error | 1412 @param error_msg(unicode): error message as sent by bridge.progress_error |
1283 """ | 1413 """ |
1284 self.disp(_("Error while doing operation: {e}").format(e=e), error=True) | 1414 self.disp(_("Error while doing operation: {e}").format(e=e), error=True) |
1285 | 1415 |
1286 def disp(self, msg, verbosity=0, error=False, end='\n'): | 1416 def disp(self, msg, verbosity=0, error=False, end="\n"): |
1287 return self.host.disp(msg, verbosity, error, end) | 1417 return self.host.disp(msg, verbosity, error, end) |
1288 | 1418 |
1289 def output(self, data): | 1419 def output(self, data): |
1290 try: | 1420 try: |
1291 output_type = self._output_type | 1421 output_type = self._output_type |
1292 except AttributeError: | 1422 except AttributeError: |
1293 raise exceptions.InternalError( | 1423 raise exceptions.InternalError( |
1294 _('trying to use output when use_output has not been set')) | 1424 _("trying to use output when use_output has not been set") |
1425 ) | |
1295 return self.host.output(output_type, self.args.output, self.extra_outputs, data) | 1426 return self.host.output(output_type, self.args.output, self.extra_outputs, data) |
1296 | 1427 |
1297 def get_pubsub_extra(self, extra: Optional[dict] = None) -> str: | 1428 def get_pubsub_extra(self, extra: Optional[dict] = None) -> str: |
1298 """Helper method to compute extra data from pubsub arguments | 1429 """Helper method to compute extra data from pubsub arguments |
1299 | 1430 |
1305 else: | 1436 else: |
1306 intersection = {C.KEY_ORDER_BY}.intersection(list(extra.keys())) | 1437 intersection = {C.KEY_ORDER_BY}.intersection(list(extra.keys())) |
1307 if intersection: | 1438 if intersection: |
1308 raise exceptions.ConflictError( | 1439 raise exceptions.ConflictError( |
1309 "given extra dict has conflicting keys with pubsub keys " | 1440 "given extra dict has conflicting keys with pubsub keys " |
1310 "{intersection}".format(intersection=intersection)) | 1441 "{intersection}".format(intersection=intersection) |
1442 ) | |
1311 | 1443 |
1312 # RSM | 1444 # RSM |
1313 | 1445 |
1314 for attribute in ('max', 'after', 'before', 'index'): | 1446 for attribute in ("max", "after", "before", "index"): |
1315 key = 'rsm_' + attribute | 1447 key = "rsm_" + attribute |
1316 if key in extra: | 1448 if key in extra: |
1317 raise exceptions.ConflictError( | 1449 raise exceptions.ConflictError( |
1318 "This key already exists in extra: u{key}".format(key=key)) | 1450 "This key already exists in extra: u{key}".format(key=key) |
1451 ) | |
1319 value = getattr(self.args, key, None) | 1452 value = getattr(self.args, key, None) |
1320 if value is not None: | 1453 if value is not None: |
1321 extra[key] = str(value) | 1454 extra[key] = str(value) |
1322 | 1455 |
1323 # MAM | 1456 # MAM |
1324 | 1457 |
1325 if hasattr(self.args, 'mam_filters'): | 1458 if hasattr(self.args, "mam_filters"): |
1326 for key, value in self.args.mam_filters: | 1459 for key, value in self.args.mam_filters: |
1327 key = 'filter_' + key | 1460 key = "filter_" + key |
1328 if key in extra: | 1461 if key in extra: |
1329 raise exceptions.ConflictError( | 1462 raise exceptions.ConflictError( |
1330 "This key already exists in extra: u{key}".format(key=key)) | 1463 "This key already exists in extra: u{key}".format(key=key) |
1464 ) | |
1331 extra[key] = value | 1465 extra[key] = value |
1332 | 1466 |
1333 # Order-By | 1467 # Order-By |
1334 | 1468 |
1335 try: | 1469 try: |
1388 if show_progress: | 1522 if show_progress: |
1389 self.host.watch_progress = True | 1523 self.host.watch_progress = True |
1390 # we need to register the following signal even if we don't display the | 1524 # we need to register the following signal even if we don't display the |
1391 # progress bar | 1525 # progress bar |
1392 self.host.bridge.register_signal( | 1526 self.host.bridge.register_signal( |
1393 "progress_started", self.progress_started_handler) | 1527 "progress_started", self.progress_started_handler |
1528 ) | |
1394 self.host.bridge.register_signal( | 1529 self.host.bridge.register_signal( |
1395 "progress_finished", self.progress_finished_handler) | 1530 "progress_finished", self.progress_finished_handler |
1531 ) | |
1396 self.host.bridge.register_signal( | 1532 self.host.bridge.register_signal( |
1397 "progress_error", self.progress_error_handler) | 1533 "progress_error", self.progress_error_handler |
1534 ) | |
1398 | 1535 |
1399 if self.need_connect is not None: | 1536 if self.need_connect is not None: |
1400 await self.host.connect_profile() | 1537 await self.host.connect_profile() |
1401 await self.start() | 1538 await self.start() |
1402 | 1539 |
1411 class CommandAnswering(CommandBase): | 1548 class CommandAnswering(CommandBase): |
1412 """Specialised commands which answer to specific actions | 1549 """Specialised commands which answer to specific actions |
1413 | 1550 |
1414 to manage action_types answer, | 1551 to manage action_types answer, |
1415 """ | 1552 """ |
1416 action_callbacks = {} # XXX: set managed action types in a dict here: | 1553 |
1417 # key is the action_type, value is the callable | 1554 action_callbacks = {} # XXX: set managed action types in a dict here: |
1418 # which will manage the answer. profile filtering is | 1555 # key is the action_type, value is the callable |
1419 # already managed when callback is called | 1556 # which will manage the answer. profile filtering is |
1557 # already managed when callback is called | |
1420 | 1558 |
1421 def __init__(self, *args, **kwargs): | 1559 def __init__(self, *args, **kwargs): |
1422 super(CommandAnswering, self).__init__(*args, **kwargs) | 1560 super(CommandAnswering, self).__init__(*args, **kwargs) |
1423 | 1561 |
1424 async def on_action_new( | 1562 async def on_action_new( |
1425 self, | 1563 self, action_data_s: str, action_id: str, security_limit: int, profile: str |
1426 action_data_s: str, | |
1427 action_id: str, | |
1428 security_limit: int, | |
1429 profile: str | |
1430 ) -> None: | 1564 ) -> None: |
1431 if profile != self.profile: | 1565 if profile != self.profile: |
1432 return | 1566 return |
1433 action_data = data_format.deserialise(action_data_s) | 1567 action_data = data_format.deserialise(action_data_s) |
1434 try: | 1568 try: |
1435 action_type = action_data['type'] | 1569 action_type = action_data["type"] |
1436 except KeyError: | 1570 except KeyError: |
1437 try: | 1571 try: |
1438 xml_ui = action_data["xmlui"] | 1572 xml_ui = action_data["xmlui"] |
1439 except KeyError: | 1573 except KeyError: |
1440 pass | 1574 pass |
1454 @param xml_ui (unicode): dialog XML representation | 1588 @param xml_ui (unicode): dialog XML representation |
1455 """ | 1589 """ |
1456 # FIXME: we temporarily use ElementTree, but a real XMLUI managing module | 1590 # FIXME: we temporarily use ElementTree, but a real XMLUI managing module |
1457 # should be available in the future | 1591 # should be available in the future |
1458 # TODO: XMLUI module | 1592 # TODO: XMLUI module |
1459 ui = ET.fromstring(xml_ui.encode('utf-8')) | 1593 ui = ET.fromstring(xml_ui.encode("utf-8")) |
1460 dialog = ui.find("dialog") | 1594 dialog = ui.find("dialog") |
1461 if dialog is not None: | 1595 if dialog is not None: |
1462 self.disp(dialog.findtext("message"), error=dialog.get("level") == "error") | 1596 self.disp(dialog.findtext("message"), error=dialog.get("level") == "error") |
1463 | 1597 |
1464 async def start_answering(self): | 1598 async def start_answering(self): |
1465 """Auto reply to confirmation requests""" | 1599 """Auto reply to confirmation requests""" |
1466 self.host.bridge.register_signal("action_new", self.on_action_new) | 1600 self.host.bridge.register_signal("action_new", self.on_action_new) |
1467 actions = await self.host.bridge.actions_get(self.profile) | 1601 actions = await self.host.bridge.actions_get(self.profile) |
1468 for action_data_s, action_id, security_limit in actions: | 1602 for action_data_s, action_id, security_limit in actions: |
1469 await self.on_action_new(action_data_s, action_id, security_limit, self.profile) | 1603 await self.on_action_new( |
1604 action_data_s, action_id, security_limit, self.profile | |
1605 ) |