diff --git a/gajim/common/connection_handlers.py b/gajim/common/connection_handlers.py index 13cf4e916..4ad542b03 100644 --- a/gajim/common/connection_handlers.py +++ b/gajim/common/connection_handlers.py @@ -291,6 +291,8 @@ class ConnectionVcard: self._vcard_presence_received) app.ged.register_event_handler('gc-presence-received', ged.GUI2, self._vcard_gc_presence_received) + app.ged.register_event_handler('room-avatar-received', ged.GUI2, + self._vcard_presence_received) def _vcard_presence_received(self, obj): if obj.conn.name != self.name: @@ -300,6 +302,10 @@ class ConnectionVcard: # No Avatar is advertised return + room_avatar = False + if isinstance(obj, RoomAvatarReceivedEvent): + room_avatar = True + if self.get_own_jid().bareMatch(obj.jid): app.log('avatar').info('Update (vCard): %s %s', obj.jid, obj.avatar_sha) @@ -324,16 +330,33 @@ class ConnectionVcard: app.log('avatar').debug('Remove: %s', obj.jid) app.contacts.set_avatar(self.name, obj.jid, None) own_jid = self.get_own_jid().getStripped() - app.logger.set_avatar_sha(own_jid, obj.jid, None) - app.interface.update_avatar(self.name, obj.jid) + if not room_avatar: + app.logger.set_avatar_sha(own_jid, obj.jid, None) + app.interface.update_avatar( + self.name, obj.jid, room_avatar=room_avatar) else: app.log('avatar').info( 'Update (vCard): %s %s', obj.jid, obj.avatar_sha) current_sha = app.contacts.get_avatar_sha(self.name, obj.jid) + if obj.avatar_sha != current_sha: - app.log('avatar').info( - 'Request (vCard): %s', obj.jid) - self.request_vcard(self._on_avatar_received, obj.jid) + if room_avatar: + # We dont save the room avatar hash in our DB, so check + # if we previously downloaded it + if app.interface.avatar_exists(obj.avatar_sha): + app.contacts.set_avatar(self.name, obj.jid, obj.avatar_sha) + app.interface.update_avatar( + self.name, obj.jid, room_avatar=room_avatar) + else: + app.log('avatar').info( + 'Request (vCard): %s', obj.jid) + self.request_vcard(self._on_room_avatar_received, obj.jid) + + else: + app.log('avatar').info( + 'Request (vCard): %s', obj.jid) + self.request_vcard(self._on_avatar_received, obj.jid) + else: app.log('avatar').info( 'Avatar already known (vCard): %s %s', @@ -568,6 +591,14 @@ class ConnectionVcard: self.send_avatar_presence() self.avatar_presence_sent = True + def _on_room_avatar_received(self, jid, resource, room, vcard): + avatar_sha, photo_decoded = self._get_vcard_photo(vcard, jid) + app.interface.save_avatar(photo_decoded) + + app.log('avatar').info('Received (vCard): %s %s', jid, avatar_sha) + app.contacts.set_avatar(self.name, jid, avatar_sha) + app.interface.update_avatar(self.name, jid, room_avatar=True) + def _on_avatar_received(self, jid, resource, room, vcard): """ Called when we receive a vCard Parse the vCard and trigger Events diff --git a/gajim/common/connection_handlers_events.py b/gajim/common/connection_handlers_events.py index 8fd1ff72b..231b18738 100644 --- a/gajim/common/connection_handlers_events.py +++ b/gajim/common/connection_handlers_events.py @@ -797,6 +797,17 @@ PresenceHelperEvent): time_str = idle_tag.getAttr('since') tim = helpers.datetime_tuple(time_str) self.idle_time = timegm(tim) + + # Check if presence is from the room itself, used when the room + # sends a avatar hash + contact = app.contacts.get_groupchat_contact(self.conn.name, self.fjid) + if contact: + app.nec.push_incoming_event( + RoomAvatarReceivedEvent( + None, conn=self.conn, stanza=self.stanza, + contact=contact, jid=self.jid)) + return + xtags = self.stanza.getTags('x') for x in xtags: namespace = x.getNamespace() @@ -2229,6 +2240,25 @@ class UpdateRosterAvatarEvent(nec.NetworkIncomingEvent): def generate(self): return True +class UpdateRoomAvatarEvent(nec.NetworkIncomingEvent): + name = 'update-room-avatar' + base_network_events = [] + + def generate(self): + return True + +class RoomAvatarReceivedEvent(nec.NetworkIncomingEvent): + name = 'room-avatar-received' + base_network_events = [] + + def generate(self): + vcard = self.stanza.getTag('x', namespace=nbxmpp.NS_VCARD_UPDATE) + if vcard is None: + log.warning('Invalid room self presence:\n%s', self.stanza) + return + self.avatar_sha = vcard.getTagData('photo') + return True + class PEPConfigReceivedEvent(nec.NetworkIncomingEvent): name = 'pep-config-received' base_network_events = [] diff --git a/gajim/common/contacts.py b/gajim/common/contacts.py index 074e1d498..e3ed7458a 100644 --- a/gajim/common/contacts.py +++ b/gajim/common/contacts.py @@ -29,6 +29,7 @@ ## try: + from gajim.common import app from gajim.common import caps_cache from gajim.common.account import Account from gajim import common @@ -92,7 +93,7 @@ class Contact(CommonContact): """ def __init__(self, jid, account, name='', groups=None, show='', status='', sub='', ask='', resource='', priority=0, keyID='', client_caps=None, - our_chatstate=None, chatstate=None, idle_time=None, avatar_sha=None): + our_chatstate=None, chatstate=None, idle_time=None, avatar_sha=None, groupchat=False): if not isinstance(jid, str): print('no str') if groups is None: @@ -104,6 +105,7 @@ class Contact(CommonContact): self.contact_name = '' # nick choosen by contact self.groups = [i if i else _('General') for i in set(groups)] # filter duplicate values self.avatar_sha = avatar_sha + self._is_groupchat = groupchat self.sub = sub self.ask = ask @@ -164,10 +166,7 @@ class Contact(CommonContact): return is_observer def is_groupchat(self): - for account in common.app.gc_connected: - if self.jid in common.app.gc_connected[account]: - return True - return False + return self._is_groupchat def is_transport(self): # if not '@' or '@' starts the jid then contact is transport @@ -249,7 +248,7 @@ class LegacyContactsAPI: def create_contact(self, jid, account, name='', groups=None, show='', status='', sub='', ask='', resource='', priority=0, keyID='', client_caps=None, our_chatstate=None, chatstate=None, idle_time=None, - avatar_sha=None): + avatar_sha=None, groupchat=False): if groups is None: groups = [] # Use Account object if available @@ -258,7 +257,7 @@ class LegacyContactsAPI: show=show, status=status, sub=sub, ask=ask, resource=resource, priority=priority, keyID=keyID, client_caps=client_caps, our_chatstate=our_chatstate, chatstate=chatstate, - idle_time=idle_time, avatar_sha=avatar_sha) + idle_time=idle_time, avatar_sha=avatar_sha, groupchat=groupchat) def create_self_contact(self, jid, account, resource, show, status, priority, name='', keyID=''): @@ -304,6 +303,9 @@ class LegacyContactsAPI: if remove_meta: self._metacontact_manager.remove_metacontact(account, jid) + def get_groupchat_contact(self, account, jid): + return self._accounts[account].contacts.get_groupchat_contact(jid) + def get_contacts(self, account, jid): return self._accounts[account].contacts.get_contacts(jid) @@ -518,6 +520,15 @@ class Contacts(): if c.resource == resource: return c + def get_groupchat_contact(self, jid): + if jid in self._contacts: + contacts = self._contacts[jid] + if len(contacts) > 1: + app.log('contacts').warning( + 'Groupchat Contact found more than once') + if contacts[0].is_groupchat(): + return contacts[0] + def get_avatar(self, jid, size=None, scale=None): if jid not in self._contacts: return None diff --git a/gajim/groupchat_control.py b/gajim/groupchat_control.py index dcb47917c..d99c2bf85 100644 --- a/gajim/groupchat_control.py +++ b/gajim/groupchat_control.py @@ -301,7 +301,7 @@ class GroupchatControl(ChatControlBase): # will be processed with this command host. COMMAND_HOST = GroupChatCommands - def __init__(self, parent_win, contact, acct, is_continued=False): + def __init__(self, parent_win, contact, nick, acct, is_continued=False): ChatControlBase.__init__(self, self.TYPE_ID, parent_win, 'groupchat_control', contact, acct) @@ -362,7 +362,7 @@ class GroupchatControl(ChatControlBase): self.handlers[id_] = widget self.room_jid = self.contact.jid - self.nick = contact.name + self.nick = nick self.new_nick = '' self.name = '' for bm in app.connections[self.account].bookmarks: @@ -371,6 +371,7 @@ class GroupchatControl(ChatControlBase): break if not self.name: self.name = self.room_jid.split('@')[0] + self.contact.name = self.name self.widget_set_visible(self.xml.get_object('banner_eventbox'), app.config.get('hide_groupchat_banner')) @@ -519,6 +520,8 @@ class GroupchatControl(ChatControlBase): self._nec_vcard_published) app.ged.register_event_handler('update-gc-avatar', ged.GUI1, self._nec_update_avatar) + app.ged.register_event_handler('update-room-avatar', ged.GUI1, + self._nec_update_room_avatar) app.ged.register_event_handler('gc-subject-received', ged.GUI1, self._nec_gc_subject_received) app.ged.register_event_handler('gc-config-changed-received', ged.GUI1, @@ -1091,6 +1094,12 @@ class GroupchatControl(ChatControlBase): banner_status_img = self.xml.get_object('gc_banner_status_image') if self.room_jid in app.gc_connected[self.account] and \ app.gc_connected[self.account][self.room_jid]: + if self.contact.avatar_sha: + surface = app.interface.get_avatar(self.contact.avatar_sha, + AvatarSize.ROSTER, + self.scale_factor) + banner_status_img.set_from_surface(surface) + return icon = gtkgui_helpers.get_iconset_name_for('muc-active') else: icon = gtkgui_helpers.get_iconset_name_for('muc-inactive') @@ -1147,6 +1156,11 @@ class GroupchatControl(ChatControlBase): obj.contact.name, obj.contact.avatar_sha) self.draw_avatar(obj.contact) + def _nec_update_room_avatar(self, obj): + if obj.jid != self.room_jid: + return + self._update_banner_state_image() + def _nec_mam_decrypted_message_received(self, obj): if not obj.groupchat: return @@ -2161,8 +2175,8 @@ class GroupchatControl(ChatControlBase): ctrl.parent_win = None self.send_chatstate('inactive', self.contact) - app.interface.roster.add_groupchat(self.contact.jid, self.account, - status = self.subject) + app.interface.roster.minimize_groupchat( + self.account, self.contact.jid, status=self.subject) del win._controls[self.account][self.contact.jid] @@ -2233,6 +2247,8 @@ class GroupchatControl(ChatControlBase): self._nec_vcard_published) app.ged.remove_event_handler('update-gc-avatar', ged.GUI1, self._nec_update_avatar) + app.ged.remove_event_handler('update-room-avatar', ged.GUI1, + self._nec_update_room_avatar) app.ged.remove_event_handler('gc-subject-received', ged.GUI1, self._nec_gc_subject_received) app.ged.remove_event_handler('gc-config-changed-received', ged.GUI1, diff --git a/gajim/gui_interface.py b/gajim/gui_interface.py index ed7fc2990..6502cfa94 100644 --- a/gajim/gui_interface.py +++ b/gajim/gui_interface.py @@ -92,7 +92,8 @@ from gajim.common import passwords from gajim.common import logging_helpers from gajim.common.connection_handlers_events import ( OurShowEvent, FileRequestErrorEvent, FileTransferCompletedEvent, - UpdateRosterAvatarEvent, UpdateGCAvatarEvent, HTTPUploadProgressEvent) + UpdateRosterAvatarEvent, UpdateGCAvatarEvent, UpdateRoomAvatarEvent, + HTTPUploadProgressEvent) from gajim.common.connection import Connection from gajim.common.file_props import FilesProp from gajim.common import pep @@ -2071,8 +2072,10 @@ class Interface: if minimize: # GCMIN contact = app.contacts.create_contact(jid=room_jid, - account=account, name=nick) - gc_control = GroupchatControl(None, contact, account) + account=account, groups=[_('Groupchats')], sub='none', + groupchat=True) + app.contacts.add_contact(account, contact) + gc_control = GroupchatControl(None, contact, nick, account) app.interface.minimized_controls[account][room_jid] = \ gc_control self.roster.add_groupchat(room_jid, account) @@ -2094,12 +2097,13 @@ class Interface: # Get target window, create a control, and associate it with the window # GCMIN contact = app.contacts.create_contact(jid=room_jid, account=account, - name=nick) + groups=[_('Groupchats')], sub='none', groupchat=True) + app.contacts.add_contact(account, contact) mw = self.msg_win_mgr.get_window(contact.jid, account) if not mw: mw = self.msg_win_mgr.create_window(contact, account, GroupchatControl.TYPE_ID) - gc_control = GroupchatControl(mw, contact, account, + gc_control = GroupchatControl(mw, contact, nick, account, is_continued=is_continued) mw.new_tab(gc_control) mw.set_active_tab(gc_control) @@ -2421,8 +2425,11 @@ class Interface: sys.exit() @staticmethod - def update_avatar(account=None, jid=None, contact=None): - if contact is None: + def update_avatar(account=None, jid=None, contact=None, room_avatar=False): + if room_avatar: + app.nec.push_incoming_event( + UpdateRoomAvatarEvent(None, account=account, jid=jid)) + elif contact is None: app.nec.push_incoming_event( UpdateRosterAvatarEvent(None, account=account, jid=jid)) else: @@ -2524,6 +2531,13 @@ class Interface: return pixbuf return Gdk.cairo_surface_create_from_pixbuf(pixbuf, scale) + @staticmethod + def avatar_exists(filename): + path = os.path.join(app.AVATAR_PATH, filename) + if not os.path.isfile(path): + return False + return True + def auto_join_bookmarks(self, account): """ Autojoin bookmarked GCs that have 'auto join' on for this account @@ -2538,12 +2552,6 @@ class Interface: minimize = bm['minimize'] in ('1', 'true') self.join_gc_room(account, jid, bm['nick'], bm['password'], minimize = minimize) - elif jid in self.minimized_controls[account]: - # more or less a hack: - # On disconnect the minimized gc contact instances - # were set to offline. Reconnect them to show up in the - # roster. - self.roster.add_groupchat(jid, account) def add_gc_bookmark(self, account, name, jid, autojoin, minimize, password, nick): diff --git a/gajim/roster_window.py b/gajim/roster_window.py index ca4f3c018..4bce35d74 100644 --- a/gajim/roster_window.py +++ b/gajim/roster_window.py @@ -743,7 +743,7 @@ class RosterWindow: return contacts[0][0] # it's contact/big brother with highest priority - def remove_contact(self, jid, account, force=False, backend=False): + def remove_contact(self, jid, account, force=False, backend=False, maximize=False): """ Remove contact from roster @@ -785,7 +785,9 @@ class RosterWindow: # If a window is still opened: don't remove contact instance # Remove contact before redrawing, otherwise the old # numbers will still be show - app.contacts.remove_jid(account, jid, remove_meta=True) + if not maximize: + # Dont remove contact when we maximize a room + app.contacts.remove_jid(account, jid, remove_meta=True) if iters: rest_of_family = [data for data in family if account != data['account'] or jid != data['jid']] @@ -829,49 +831,26 @@ class RosterWindow: self.model[self_iter][Column.JID] = new_jid self.draw_contact(new_jid, account) - def add_groupchat(self, jid, account, status=''): + def minimize_groupchat(self, account, jid, status=''): + gc_control = app.interface.msg_win_mgr.get_gc_control(jid, account) + app.interface.minimized_controls[account][jid] = gc_control + self.add_groupchat(jid, account) + + def add_groupchat(self, jid, account): """ Add groupchat to roster and draw it. Return the added contact instance """ - contact = app.contacts.get_contact_with_highest_priority(account, jid) - # Do not show gc if we are disconnected and minimize it + contact = app.contacts.get_groupchat_contact(account, jid) + show = 'offline' if app.account_is_connected(account): show = 'online' - else: - show = 'offline' - status = '' - if contact is None: - gc_control = app.interface.msg_win_mgr.get_gc_control(jid, - account) - if gc_control: - # there is a window that we can minimize - app.interface.minimized_controls[account][jid] = gc_control - name = gc_control.name - elif jid in app.interface.minimized_controls[account]: - name = app.interface.minimized_controls[account][jid].name - else: - name = jid.split('@')[0] - # New groupchat - contact = app.contacts.create_contact(jid=jid, account=account, - name=name, groups=[_('Groupchats')], show=show, status=status, - sub='none') - app.contacts.add_contact(account, contact) - self.add_contact(jid, account) - else: - if jid not in app.interface.minimized_controls[account]: - # there is a window that we can minimize - gc_control = app.interface.msg_win_mgr.get_gc_control(jid, - account) - app.interface.minimized_controls[account][jid] = gc_control - contact.show = show - contact.status = status - self.adjust_and_draw_contact_context(jid, account) + contact.show = show + self.add_contact(jid, account) return contact - - def remove_groupchat(self, jid, account): + def remove_groupchat(self, jid, account, maximize=False): """ Remove groupchat from roster and redraw account and group """ @@ -879,7 +858,7 @@ class RosterWindow: if contact.is_groupchat(): if jid in app.interface.minimized_controls[account]: del app.interface.minimized_controls[account][jid] - self.remove_contact(jid, account, force=True, backend=True) + self.remove_contact(jid, account, force=True, backend=True, maximize=maximize) return True else: return False @@ -3137,7 +3116,7 @@ class RosterWindow: ctrl.on_groupchat_maximize() mw.new_tab(ctrl) mw.set_active_tab(ctrl) - self.remove_groupchat(jid, account) + self.remove_groupchat(jid, account, maximize=True) def on_edit_account(self, widget, account): if 'accounts' in app.interface.instances: @@ -5912,6 +5891,8 @@ class RosterWindow: self._nec_pep_received) app.ged.register_event_handler('update-roster-avatar', ged.GUI1, self._nec_update_avatar) + app.ged.register_event_handler('update-room-avatar', ged.GUI1, + self._nec_update_avatar) app.ged.register_event_handler('gc-subject-received', ged.GUI1, self._nec_gc_subject_received) app.ged.register_event_handler('metacontacts-received', ged.GUI2,