Mercurial > libervia-backend
diff sat_frontends/jp/cmd_roster.py @ 3040:fee60f17ebac
jp: jp asyncio port:
/!\ this commit is huge. Jp is temporarily not working with `dbus` bridge /!\
This patch implements the port of jp to asyncio, so it is now correctly using the bridge
asynchronously, and it can be used with bridges like `pb`. This also simplify the code,
notably for things which were previously implemented with many callbacks (like pagination
with RSM).
During the process, some behaviours have been modified/fixed, in jp and backends, check
diff for details.
author | Goffi <goffi@goffi.org> |
---|---|
date | Wed, 25 Sep 2019 08:56:41 +0200 |
parents | ab2696e34d29 |
children | 9d0df638c8b4 |
line wrap: on
line diff
--- a/sat_frontends/jp/cmd_roster.py Wed Sep 25 08:53:38 2019 +0200 +++ b/sat_frontends/jp/cmd_roster.py Wed Sep 25 08:56:41 2019 +0200 @@ -20,77 +20,67 @@ from . import base from collections import OrderedDict -from functools import partial from sat.core.i18n import _ from sat_frontends.jp.constants import Const as C -from twisted.words.protocols.jabber import jid +from sat_frontends.tools import jid +from sat.tools.common.ansi import ANSI as A __commands__ = ["Roster"] - -class Purge(base.CommandBase): +class Get(base.CommandBase): def __init__(self, host): - super(Purge, self).__init__(host, 'purge', help=_('Purge the roster from its contacts with no subscription')) - self.need_loop = True + super().__init__( + host, 'get', use_output=C.OUTPUT_DICT, use_verbose=True, + extra_outputs = {"default": self.default_output}, + help=_('retrieve the roster entities')) def add_parser_options(self): - self.parser.add_argument("--no_from", action="store_true", help=_("Also purge contacts with no 'from' subscription")) - self.parser.add_argument("--no_to", action="store_true", help=_("Also purge contacts with no 'to' subscription")) - - def start(self): - self.host.bridge.getContacts(profile_key=self.host.profile, callback=self.gotContacts, errback=self.error) + pass - def error(self, failure): - print((_("Error while retrieving the contacts [%s]") % failure)) - self.host.quit(1) + def default_output(self, data): + for contact_jid, contact_data in data.items(): + all_keys = list(contact_data.keys()) + keys_to_show = [] + name = contact_data.get('name', contact_jid.node) - def ask_confirmation(self, no_sub, no_from, no_to): - """Ask the confirmation before removing contacts. + if self.verbosity >= 1: + keys_to_show.append('groups') + all_keys.remove('groups') + if self.verbosity >= 2: + keys_to_show.extend(all_keys) - @param no_sub (list[unicode]): list of contacts with no subscription - @param no_from (list[unicode]): list of contacts with no 'from' subscription - @param no_to (list[unicode]): list of contacts with no 'to' subscription - @return bool - """ - if no_sub: - print("There's no subscription between profile [%s] and the following contacts:" % self.host.profile) - print(" " + "\n ".join(no_sub)) - if no_from: - print("There's no 'from' subscription between profile [%s] and the following contacts:" % self.host.profile) - print(" " + "\n ".join(no_from)) - if no_to: - print("There's no 'to' subscription between profile [%s] and the following contacts:" % self.host.profile) - print(" " + "\n ".join(no_to)) - message = "REMOVE them from profile [%s]'s roster" % self.host.profile - while True: - res = input("%s (y/N)? " % message) - if not res or res.lower() == 'n': - return False - if res.lower() == 'y': - return True + if name is None: + self.disp(A.color(C.A_HEADER, contact_jid)) + else: + self.disp(A.color(C.A_HEADER, name, A.RESET, f" ({contact_jid})")) + for k in keys_to_show: + value = contact_data[k] + if value: + if isinstance(value, list): + value = ', '.join(value) + self.disp(A.color( + " ", C.A_SUBHEADER, f"{k}: ", A.RESET, str(value))) - def gotContacts(self, contacts): - """Process the list of contacts. + async def start(self): + try: + contacts = await self.host.bridge.getContacts(profile_key=self.host.profile) + except Exception as e: + self.disp(f"error while retrieving the contacts: {e}", error=True) + self.host.quit(C.EXIT_BRIDGE_ERRBACK) - @param contacts(list[tuple]): list of contacts with their attributes and groups - """ - no_sub, no_from, no_to = [], [], [] - for contact, attrs, groups in contacts: - from_, to = C.bool(attrs["from"]), C.bool(attrs["to"]) - if not from_: - if not to: - no_sub.append(contact) - elif self.args.no_from: - no_from.append(contact) - elif not to and self.args.no_to: - no_to.append(contact) - if not no_sub and not no_from and not no_to: - print("Nothing to do - there's a from and/or to subscription(s) between profile [%s] and each of its contacts" % self.host.profile) - elif self.ask_confirmation(no_sub, no_from, no_to): - for contact in no_sub + no_from + no_to: - self.host.bridge.delContact(contact, profile_key=self.host.profile, callback=lambda __: None, errback=lambda failure: None) + contacts_dict = {} + for contact_jid_s, data, groups in contacts: + # FIXME: we have to convert string to bool here for historical reason + # getContacts format should be changed and serialised properly + for key in ('from', 'to', 'ask'): + if key in data: + data[key] = C.bool(data[key]) + data['groups'] = list(groups) + contacts_dict[jid.JID(contact_jid_s)] = data + + await self.output(contacts_dict) self.host.quit() @@ -98,23 +88,17 @@ def __init__(self, host): super(Stats, self).__init__(host, 'stats', help=_('Show statistics about a roster')) - self.need_loop = True def add_parser_options(self): pass - def start(self): - self.host.bridge.getContacts(profile_key=self.host.profile, callback=self.gotContacts, errback=self.error) + async def start(self): + try: + contacts = await self.host.bridge.getContacts(profile_key=self.host.profile) + except Exception as e: + self.disp(f"error while retrieving the contacts: {e}", error=True) + self.host.quit(C.EXIT_BRIDGE_ERRBACK) - def error(self, failure): - print((_("Error while retrieving the contacts [%s]") % failure)) - self.host.quit(1) - - def gotContacts(self, contacts): - """Process the list of contacts. - - @param contacts(list[tuple]): list of contacts with their attributes and groups - """ hosts = {} unique_groups = set() no_sub, no_from, no_to, no_group, total_group_subscription = 0, 0, 0, 0, 0 @@ -127,7 +111,9 @@ no_from += 1 elif not to: no_to += 1 - host = jid.JID(contact).host + + host = jid.JID(contact).domain + hosts.setdefault(host, 0) hosts[host] += 1 if groups: @@ -142,7 +128,8 @@ print("Number of different hosts: %d" % len(hosts)) print() for host, count in hosts.items(): - print("Contacts on {host}: {count} ({rate:.1f}%)".format(host=host, count=count, rate=100 * float(count) / len(contacts))) + print("Contacts on {host}: {count} ({rate:.1f}%)".format( + host=host, count=count, rate=100 * float(count) / len(contacts))) print() print("Contacts with no 'from' subscription: %d" % no_from) print("Contacts with no 'to' subscription: %d" % no_to) @@ -158,53 +145,90 @@ groups_per_contact = float(total_group_subscription) / len(contacts) except ZeroDivisionError: groups_per_contact = 0 - print("Average groups' subscriptions per contact: {:.1f}".format(groups_per_contact)) + print(f"Average groups' subscriptions per contact: {groups_per_contact:.1f}") print("Contacts not assigned to any group: %d" % no_group) self.host.quit() -class Get(base.CommandBase): +class Purge(base.CommandBase): def __init__(self, host): - super(Get, self).__init__(host, 'get', help=_('Retrieve the roster contacts')) - self.need_loop = True + super(Purge, self).__init__( + host, 'purge', + help=_('purge the roster from its contacts with no subscription')) def add_parser_options(self): - self.parser.add_argument("--subscriptions", action="store_true", help=_("Show the contacts' subscriptions")) - self.parser.add_argument("--groups", action="store_true", help=_("Show the contacts' groups")) - self.parser.add_argument("--name", action="store_true", help=_("Show the contacts' names")) + self.parser.add_argument( + "--no_from", action="store_true", + help=_("also purge contacts with no 'from' subscription")) + self.parser.add_argument( + "--no_to", action="store_true", + help=_("also purge contacts with no 'to' subscription")) - def start(self): - self.host.bridge.getContacts(profile_key=self.host.profile, callback=self.gotContacts, errback=self.error) + async def start(self): + try: + contacts = await self.host.bridge.getContacts(self.host.profile) + except Exception as e: + self.disp(f"error while retrieving the contacts: {e}", error=True) + self.host.quit(C.EXIT_BRIDGE_ERRBACK) - def error(self, failure): - print((_("Error while retrieving the contacts [%s]") % failure)) - self.host.quit(1) + no_sub, no_from, no_to = [], [], [] + for contact, attrs, groups in contacts: + from_, to = C.bool(attrs["from"]), C.bool(attrs["to"]) + if not from_: + if not to: + no_sub.append(contact) + elif self.args.no_from: + no_from.append(contact) + elif not to and self.args.no_to: + no_to.append(contact) + if not no_sub and not no_from and not no_to: + self.disp( + f"Nothing to do - there's a from and/or to subscription(s) between " + f"profile {self.host.profile!r} and each of its contacts" + ) + elif await self.ask_confirmation(no_sub, no_from, no_to): + for contact in no_sub + no_from + no_to: + try: + await self.host.bridge.delContact( + contact, profile_key=self.host.profile) + except Exception as e: + self.disp(f"can't delete contact {contact!r}: {e}", error=True) + else: + self.disp(f"contact {contact!r} has been removed") - def gotContacts(self, contacts): - """Process the list of contacts. + self.host.quit() + + async def ask_confirmation(self, no_sub, no_from, no_to): + """Ask the confirmation before removing contacts. - @param contacts(list[tuple]): list of contacts with their attributes and groups + @param no_sub (list[unicode]): list of contacts with no subscription + @param no_from (list[unicode]): list of contacts with no 'from' subscription + @param no_to (list[unicode]): list of contacts with no 'to' subscription + @return bool """ - field_count = 1 # only display the contact by default - if self.args.subscriptions: - field_count += 3 # ask, from, to - if self.args.name: - field_count += 1 - if self.args.groups: - field_count += 1 - for contact, attrs, groups in contacts: - args = [contact] - if self.args.subscriptions: - args.append("ask" if C.bool(attrs["ask"]) else "") - args.append("from" if C.bool(attrs["from"]) else "") - args.append("to" if C.bool(attrs["to"]) else "") - if self.args.name: - args.append(str(attrs.get("name", ""))) - if self.args.groups: - args.append("\t".join(groups) if groups else "") - print(";".join(["{}"] * field_count).format(*args).encode("utf-8")) - self.host.quit() + if no_sub: + self.disp( + f"There's no subscription between profile {self.host.profile!r} and the " + f"following contacts:") + self.disp(" " + "\n ".join(no_sub)) + if no_from: + self.disp( + f"There's no 'from' subscription between profile {self.host.profile!r} " + f"and the following contacts:") + self.disp(" " + "\n ".join(no_from)) + if no_to: + self.disp( + f"There's no 'to' subscription between profile {self.host.profile!r} and " + f"the following contacts:") + self.disp(" " + "\n ".join(no_to)) + message = f"REMOVE them from profile {self.host.profile}'s roster" + while True: + res = await self.host.ainput(f"{message} (y/N)? ") + if not res or res.lower() == 'n': + return False + if res.lower() == 'y': + return True class Resync(base.CommandBase): @@ -212,27 +236,24 @@ def __init__(self, host): super(Resync, self).__init__( host, 'resync', help=_('do a full resynchronisation of roster with server')) - self.need_loop = True def add_parser_options(self): pass - def rosterResyncCb(self): - self.disp(_("Roster resynchronized")) - self.host.quit(C.EXIT_OK) - - def start(self): - self.host.bridge.rosterResync(profile_key=self.host.profile, - callback=self.rosterResyncCb, - errback=partial( - self.errback, - msg=_("can't resynchronise roster: {}"), - exit_code=C.EXIT_BRIDGE_ERRBACK, - )) + async def start(self): + try: + await self.host.bridge.rosterResync(profile_key=self.host.profile) + except Exception as e: + self.disp(f"can't resynchronise roster: {e}", error=True) + self.host.quit(C.EXIT_BRIDGE_ERRBACK) + else: + self.disp(_("Roster resynchronized")) + self.host.quit(C.EXIT_OK) class Roster(base.CommandBase): subcommands = (Get, Stats, Purge, Resync) def __init__(self, host): - super(Roster, self).__init__(host, 'roster', use_profile=True, help=_("Manage an entity's roster")) + super(Roster, self).__init__( + host, 'roster', use_profile=True, help=_("Manage an entity's roster"))