From dcdc970acd64d1c3925a2c8c5690b58e209e001c Mon Sep 17 00:00:00 2001 From: mathieui Date: Tue, 10 Oct 2017 00:22:09 +0200 Subject: Micro-optimize MUC presence handling Keep all presence before status code=110 in a buffer, then batch-process them when receiving our initial join. --- poezio/tabs/muctab.py | 309 +++++++++++++++++++++++++++++--------------------- 1 file changed, 179 insertions(+), 130 deletions(-) diff --git a/poezio/tabs/muctab.py b/poezio/tabs/muctab.py index 5382d15a..f95a5fe3 100644 --- a/poezio/tabs/muctab.py +++ b/poezio/tabs/muctab.py @@ -36,6 +36,7 @@ from poezio.core.structs import Completion, Status NS_MUC_USER = 'http://jabber.org/protocol/muc#user' +STATUS_XPATH = '{%s}x/{%s}status' % (NS_MUC_USER, NS_MUC_USER) class MucTab(ChatTab): @@ -55,6 +56,7 @@ class MucTab(ChatTab): self.own_user = None self.name = jid self.password = password + self.presence_buffer = [] self.users = [] self.privates = [] # private conversations self.topic = '' @@ -566,6 +568,7 @@ class MucTab(ChatTab): current_status.show) def leave_room(self, message): + self.presence_buffer = [] if self.joined: info_col = dump_tuple(get_theme().COLOR_INFORMATION_TEXT) char_quit = get_theme().CHAR_QUIT @@ -1072,139 +1075,23 @@ class MucTab(ChatTab): 0) def handle_presence(self, presence): - from_nick = presence['from'].resource - from_room = presence['from'].bare - xpath = '{%s}x/{%s}status' % (NS_MUC_USER, NS_MUC_USER) + """ + Handle MUC presence + """ status_codes = set() - for status_code in presence.xml.findall(xpath): + for status_code in presence.xml.findall(STATUS_XPATH): status_codes.add(status_code.attrib['code']) - - # Check if it's not an error presence. - if presence['type'] == 'error': - return self.core.room_error(presence, from_room) - affiliation = presence['muc']['affiliation'] - show = presence['show'] - status = presence['status'] - role = presence['muc']['role'] - jid = presence['muc']['jid'] - typ = presence['type'] - deterministic = config.get_by_tabname('deterministic_nick_colors', self.name) - if not self.joined: # user in the room BEFORE us. - # ignore redondant presence message, see bug #1509 - if (from_nick not in [user.nick for user in self.users] - and typ != "unavailable"): - user_color = self.search_for_color(from_nick) - new_user = User(from_nick, affiliation, show, - status, role, jid, deterministic, user_color) - bisect.insort_left(self.users, new_user) - self.core.events.trigger('muc_join', presence, self) - if '110' in status_codes or self.own_nick == from_nick: - # second part of the condition is a workaround for old - # ejabberd or every gateway in the world that just do - # not send a 110 status code with the presence - self.own_nick = from_nick - self.own_user = new_user - self.joined = True - if self.name in self.core.initial_joins: - self.core.initial_joins.remove(self.name) - self._state = 'normal' - elif self != self.core.current_tab(): - self._state = 'joined' - if (self.core.current_tab() is self - and self.core.status.show not in ('xa', 'away')): - self.send_chat_state('active') - new_user.color = get_theme().COLOR_OWN_NICK - - if config.get_by_tabname('display_user_color_in_join_part', - self.general_jid): - color = dump_tuple(new_user.color) - else: - color = 3 - - info_col = dump_tuple(get_theme().COLOR_INFORMATION_TEXT) - warn_col = dump_tuple(get_theme().COLOR_WARNING_TEXT) - spec_col = dump_tuple(get_theme().COLOR_JOIN_CHAR) - enable_message = ( - '\x19%(color_spec)s}%(spec)s\x19%(info_col)s} You ' - '(\x19%(nick_col)s}%(nick)s\x19%(info_col)s}) joined' - ' the room') % { - 'nick': from_nick, - 'spec': get_theme().CHAR_JOIN, - 'color_spec': spec_col, - 'nick_col': color, - 'info_col': info_col, - } - self.add_message(enable_message, typ=2) - if '201' in status_codes: - self.add_message( - '\x19%(info_col)s}Info: The room ' - 'has been created' % - {'info_col': info_col}, - typ=0) - if '170' in status_codes: - self.add_message( - '\x19%(warn_col)s}Warning:\x19%(info_col)s}' - ' This room is publicly logged' % - {'info_col': info_col, - 'warn_col': warn_col}, - typ=0) - if '100' in status_codes: - self.add_message( - '\x19%(warn_col)s}Warning:\x19%(info_col)s}' - ' This room is not anonymous.' % - {'info_col': info_col, - 'warn_col': warn_col}, - typ=0) - if self.core.current_tab() is not self: - self.refresh_tab_win() - self.core.current_tab().input.refresh() - self.core.doupdate() - self.core.enable_private_tabs(self.name, enable_message) - # Enable the self ping event, to regularly check if we - # are still in the room. - self.enable_self_ping_event() - else: - change_nick = '303' in status_codes - kick = '307' in status_codes and typ == 'unavailable' - ban = '301' in status_codes and typ == 'unavailable' - shutdown = '332' in status_codes and typ == 'unavailable' - non_member = '322' in status_codes and typ == 'unavailable' - user = self.get_user_by_name(from_nick) - # New user - if not user and typ != "unavailable": - user_color = self.search_for_color(from_nick) - self.core.events.trigger('muc_join', presence, self) - self.on_user_join(from_nick, affiliation, show, status, role, - jid, user_color) - # nick change - elif change_nick: - self.core.events.trigger('muc_nickchange', presence, self) - self.on_user_nick_change(presence, user, from_nick, from_room) - elif ban: - self.core.events.trigger('muc_ban', presence, self) - self.core.on_user_left_private_conversation(from_room, - user, status) - self.on_user_banned(presence, user, from_nick) - # kick - elif kick: - self.core.events.trigger('muc_kick', presence, self) - self.core.on_user_left_private_conversation(from_room, - user, status) - self.on_user_kicked(presence, user, from_nick) - elif shutdown: - self.core.events.trigger('muc_shutdown', presence, self) - self.on_muc_shutdown() - elif non_member: - self.core.events.trigger('muc_shutdown', presence, self) - self.on_non_member_kicked() - # user quit - elif typ == 'unavailable': - self.on_user_leave_groupchat(user, jid, status, - from_nick, from_room) - # status change + if not self.joined: + if '110' in status_codes: + self.process_presence_buffer(presence) else: - self.on_user_change_status(user, from_nick, from_room, - affiliation, role, show, status) + self.presence_buffer.append(presence) + return + else: + try: + self.handle_presence_joined(presence, status_codes) + except PresenceError: + self.core.room_error(presence, presence['from'].bare) if self.core.current_tab() is self: self.text_win.refresh() self.user_win.refresh_if_changed(self.users) @@ -1212,6 +1099,149 @@ class MucTab(ChatTab): self.input.refresh() self.core.doupdate() + def process_presence_buffer(self, last_presence): + """ + Batch-process all the initial presences + """ + deterministic = config.get_by_tabname('deterministic_nick_colors', self.name) + + for stanza in self.presence_buffer: + try: + self.handle_presence_unjoined(stanza, deterministic) + except PresenceError as e: + self.core.room_error(e.presence, e.presence['from'].bare) + self.handle_presence_unjoined(last_presence, deterministic, own=True) + self.users.sort() + # Enable the self ping event, to regularly check if we + # are still in the room. + self.enable_self_ping_event() + if self.core.current_tab() is not self: + self.refresh_tab_win() + self.core.current_tab().input.refresh() + self.core.doupdate() + + def handle_presence_unjoined(self, presence, deterministic, own=False): + """ + Presence received while we are not in the room (before code=110) + """ + from_nick, from_room, affiliation, show, status, role, jid, typ = dissect_presence(presence) + user_color = self.search_for_color(from_nick) + new_user = User(from_nick, affiliation, show, + status, role, jid, deterministic, user_color) + self.users.append(new_user) + self.core.events.trigger('muc_join', presence, self) + if own: + status_codes = set() + for status_code in presence.xml.findall(STATUS_XPATH): + status_codes.add(status_code.attrib['code']) + self.own_join(from_nick, new_user, status_codes) + + def own_join(self, from_nick, new_user, status_codes): + """ + Handle the last presence we received, entering the room + """ + self.own_nick = from_nick + self.own_user = new_user + self.joined = True + if self.name in self.core.initial_joins: + self.core.initial_joins.remove(self.name) + self._state = 'normal' + elif self != self.core.current_tab(): + self._state = 'joined' + if (self.core.current_tab() is self + and self.core.status.show not in ('xa', 'away')): + self.send_chat_state('active') + new_user.color = get_theme().COLOR_OWN_NICK + + if config.get_by_tabname('display_user_color_in_join_part', + self.general_jid): + color = dump_tuple(new_user.color) + else: + color = 3 + + info_col = dump_tuple(get_theme().COLOR_INFORMATION_TEXT) + warn_col = dump_tuple(get_theme().COLOR_WARNING_TEXT) + spec_col = dump_tuple(get_theme().COLOR_JOIN_CHAR) + enable_message = ( + '\x19%(color_spec)s}%(spec)s\x19%(info_col)s} You ' + '(\x19%(nick_col)s}%(nick)s\x19%(info_col)s}) joined' + ' the room') % { + 'nick': from_nick, + 'spec': get_theme().CHAR_JOIN, + 'color_spec': spec_col, + 'nick_col': color, + 'info_col': info_col, + } + self.add_message(enable_message, typ=2) + self.core.enable_private_tabs(self.name, enable_message) + if '201' in status_codes: + self.add_message( + '\x19%(info_col)s}Info: The room ' + 'has been created' % + {'info_col': info_col}, + typ=0) + if '170' in status_codes: + self.add_message( + '\x19%(warn_col)s}Warning:\x19%(info_col)s}' + ' This room is publicly logged' % + {'info_col': info_col, + 'warn_col': warn_col}, + typ=0) + if '100' in status_codes: + self.add_message( + '\x19%(warn_col)s}Warning:\x19%(info_col)s}' + ' This room is not anonymous.' % + {'info_col': info_col, + 'warn_col': warn_col}, + typ=0) + + def handle_presence_joined(self, presence, status_codes): + """ + Handle new presences when we are already in the room + """ + from_nick, from_room, affiliation, show, status, role, jid, typ = dissect_presence(presence) + change_nick = '303' in status_codes + kick = '307' in status_codes and typ == 'unavailable' + ban = '301' in status_codes and typ == 'unavailable' + shutdown = '332' in status_codes and typ == 'unavailable' + non_member = '322' in status_codes and typ == 'unavailable' + user = self.get_user_by_name(from_nick) + # New user + if not user and typ != "unavailable": + user_color = self.search_for_color(from_nick) + self.core.events.trigger('muc_join', presence, self) + self.on_user_join(from_nick, affiliation, show, status, role, + jid, user_color) + # nick change + elif change_nick: + self.core.events.trigger('muc_nickchange', presence, self) + self.on_user_nick_change(presence, user, from_nick, from_room) + elif ban: + self.core.events.trigger('muc_ban', presence, self) + self.core.on_user_left_private_conversation(from_room, + user, status) + self.on_user_banned(presence, user, from_nick) + # kick + elif kick: + self.core.events.trigger('muc_kick', presence, self) + self.core.on_user_left_private_conversation(from_room, + user, status) + self.on_user_kicked(presence, user, from_nick) + elif shutdown: + self.core.events.trigger('muc_shutdown', presence, self) + self.on_muc_shutdown() + elif non_member: + self.core.events.trigger('muc_shutdown', presence, self) + self.on_non_member_kicked() + # user quit + elif typ == 'unavailable': + self.on_user_leave_groupchat(user, jid, status, + from_nick, from_room) + # status change + else: + self.on_user_change_status(user, from_nick, from_room, + affiliation, role, show, status) + def on_non_member_kicked(self): """We have been kicked because the MUC is members-only""" self.add_message( @@ -1727,3 +1757,22 @@ class MucTab(ChatTab): def on_self_ping_failed(self, iq): self.command_cycle("the MUC server is not responding") self.core.refresh_window() + +class PresenceError(Exception): pass + +def dissect_presence(presence): + """ + Extract relevant information from a presence + """ + from_nick = presence['from'].resource + from_room = presence['from'].bare + # Check if it's not an error presence. + if presence['type'] == 'error': + raise PresenceError(presence) + affiliation = presence['muc']['affiliation'] + show = presence['show'] + status = presence['status'] + role = presence['muc']['role'] + jid = presence['muc']['jid'] + typ = presence['type'] + return from_nick, from_room, affiliation, show, status, role, jid, typ -- cgit v1.2.3