diff --git a/src/common/connection_handlers.py b/src/common/connection_handlers.py index df2511e73..b66be496a 100644 --- a/src/common/connection_handlers.py +++ b/src/common/connection_handlers.py @@ -1089,6 +1089,7 @@ ConnectionJingle, ConnectionIBBytestream): PrivateStorageRosternotesReceivedEvent) gajim.nec.register_incoming_event(RosternotesReceivedEvent) gajim.nec.register_incoming_event(StreamConflictReceivedEvent) + gajim.nec.register_incoming_event(PresenceReceivedEvent) gajim.ged.register_event_handler('http-auth-received', ged.CORE, self._nec_http_auth_received) @@ -1738,347 +1739,9 @@ ConnectionJingle, ConnectionIBBytestream): """ Called when we receive a presence """ + log.debug('PresenceCB') gajim.nec.push_incoming_event(NetworkEvent('raw-pres-received', - conn=con, xmpp_pres=prs)) - ptype = prs.getType() - if ptype == 'available': - ptype = None - rfc_types = ('unavailable', 'error', 'subscribe', 'subscribed', - 'unsubscribe', 'unsubscribed') - if ptype and not ptype in rfc_types: - ptype = None - log.debug('PresenceCB: %s' % ptype) - if not self.connection or self.connected < 2: - log.debug('account is no more connected') - return - try: - who = helpers.get_full_jid_from_iq(prs) - except Exception: - if prs.getTag('error') and prs.getTag('error').getTag('jid-malformed'): - # wrong jid, we probably tried to change our nick in a room to a non - # valid one - who = str(prs.getFrom()) - jid_stripped, resource = gajim.get_room_and_nick_from_fjid(who) - self.dispatch('GC_MSG', (jid_stripped, - _('Nickname not allowed: %s') % resource, None, False, None, [])) - return - jid_stripped, resource = gajim.get_room_and_nick_from_fjid(who) - timestamp = None - id_ = prs.getID() - is_gc = False # is it a GC presence ? - sigTag = None - ns_muc_user_x = None - avatar_sha = None - # XEP-0172 User Nickname - user_nick = prs.getTagData('nick') - if not user_nick: - user_nick = '' - contact_nickname = None - transport_auto_auth = False - # XEP-0203 - delay_tag = prs.getTag('delay', namespace=common.xmpp.NS_DELAY2) - if delay_tag: - tim = prs.getTimestamp2() - tim = helpers.datetime_tuple(tim) - timestamp = localtime(timegm(tim)) - xtags = prs.getTags('x') - for x in xtags: - namespace = x.getNamespace() - if namespace.startswith(common.xmpp.NS_MUC): - is_gc = True - if namespace == common.xmpp.NS_MUC_USER and x.getTag('destroy'): - ns_muc_user_x = x - elif namespace == common.xmpp.NS_SIGNED: - sigTag = x - elif namespace == common.xmpp.NS_VCARD_UPDATE: - avatar_sha = x.getTagData('photo') - contact_nickname = x.getTagData('nickname') - elif namespace == common.xmpp.NS_DELAY and not timestamp: - # XEP-0091 - tim = prs.getTimestamp() - tim = helpers.datetime_tuple(tim) - timestamp = localtime(timegm(tim)) - elif namespace == 'http://delx.cjb.net/protocol/roster-subsync': - # see http://trac.gajim.org/ticket/326 - agent = gajim.get_server_from_jid(jid_stripped) - if self.connection.getRoster().getItem(agent): # to be sure it's a transport contact - transport_auto_auth = True - - if not is_gc and id_ and id_.startswith('gajim_muc_') and \ - ptype == 'error': - # Error presences may not include sent stanza, so we don't detect it's - # a muc preence. So detect it by ID - h = hmac.new(self.secret_hmac, jid_stripped).hexdigest()[:6] - if id_.split('_')[-1] == h: - is_gc = True - status = prs.getStatus() or '' - show = prs.getShow() - if show not in ('chat', 'away', 'xa', 'dnd'): - show = '' # We ignore unknown show - if not ptype and not show: - show = 'online' - elif ptype == 'unavailable': - show = 'offline' - - prio = prs.getPriority() - try: - prio = int(prio) - except Exception: - prio = 0 - keyID = '' - if sigTag and self.USE_GPG and ptype != 'error': - # error presences contain our own signature - # verify - sigmsg = sigTag.getData() - keyID = self.gpg.verify(status, sigmsg) - - if is_gc: - if ptype == 'error': - errcon = prs.getError() - errmsg = prs.getErrorMsg() - errcode = prs.getErrorCode() - room_jid, nick = gajim.get_room_and_nick_from_fjid(who) - - gc_control = gajim.interface.msg_win_mgr.get_gc_control(room_jid, - self.name) - - # If gc_control is missing - it may be minimized. Try to get it from - # there. If it's not there - then it's missing anyway and will - # remain set to None. - if gc_control is None: - minimized = gajim.interface.minimized_controls[self.name] - gc_control = minimized.get(room_jid) - - if errcode == '502': - # Internal Timeout: - self.dispatch('NOTIFY', (jid_stripped, 'error', errmsg, resource, - prio, keyID, timestamp, None)) - elif (errcode == '503'): - if gc_control is None or gc_control.autorejoin is None: - # maximum user number reached - self.dispatch('GC_ERROR', (gc_control, - _('Unable to join group chat'), - _('Maximum number of users for %s has been ' - 'reached') % room_jid)) - elif (errcode == '401') or (errcon == 'not-authorized'): - # password required to join - self.dispatch('GC_PASSWORD_REQUIRED', (room_jid, nick)) - elif (errcode == '403') or (errcon == 'forbidden'): - # we are banned - self.dispatch('GC_ERROR', (gc_control, - _('Unable to join group chat'), - _('You are banned from group chat %s.') % room_jid)) - elif (errcode == '404') or (errcon in ('item-not-found', - 'remote-server-not-found')): - if gc_control is None or gc_control.autorejoin is None: - # group chat does not exist - self.dispatch('GC_ERROR', (gc_control, - _('Unable to join group chat'), - _('Group chat %s does not exist.') % room_jid)) - elif (errcode == '405') or (errcon == 'not-allowed'): - self.dispatch('GC_ERROR', (gc_control, - _('Unable to join group chat'), - _('Group chat creation is restricted.'))) - elif (errcode == '406') or (errcon == 'not-acceptable'): - self.dispatch('GC_ERROR', (gc_control, - _('Unable to join group chat'), - _('Your registered nickname must be used in group chat ' - '%s.') % room_jid)) - elif (errcode == '407') or (errcon == 'registration-required'): - self.dispatch('GC_ERROR', (gc_control, - _('Unable to join group chat'), - _('You are not in the members list in groupchat %s.') %\ - room_jid)) - elif (errcode == '409') or (errcon == 'conflict'): - # nick conflict - room_jid = gajim.get_room_from_fjid(who) - self.dispatch('ASK_NEW_NICK', (room_jid,)) - else: # print in the window the error - self.dispatch('ERROR_ANSWER', ('', jid_stripped, - errmsg, errcode)) - if not ptype or ptype == 'unavailable': - if gajim.config.get('log_contact_status_changes') and \ - gajim.config.should_log(self.name, jid_stripped): - gc_c = gajim.contacts.get_gc_contact(self.name, jid_stripped, - resource) - st = status or '' - if gc_c: - jid = gc_c.jid - else: - jid = prs.getJid() - if jid: - # we know real jid, save it in db - st += ' (%s)' % jid - try: - gajim.logger.write('gcstatus', who, st, show) - except exceptions.PysqliteOperationalError, e: - self.dispatch('DB_ERROR', (_('Disk Write Error'), - str(e))) - except exceptions.DatabaseMalformed: - pritext = _('Database Error') - sectext = _('The database file (%s) cannot be read. Try to ' - 'repair it (see http://trac.gajim.org/wiki/DatabaseBackup)' - ' or remove it (all history will be lost).') % \ - common.logger.LOG_DB_PATH - self.dispatch('DB_ERROR', (pritext, sectext)) - if avatar_sha or avatar_sha == '': - if avatar_sha == '': - # contact has no avatar - puny_nick = helpers.sanitize_filename(resource) - gajim.interface.remove_avatar_files(jid_stripped, puny_nick) - # if it's a gc presence, don't ask vcard here. We may ask it to - # real jid in gui part. - if ns_muc_user_x: - # Room has been destroyed. see - # http://www.xmpp.org/extensions/xep-0045.html#destroyroom - reason = _('Room has been destroyed') - destroy = ns_muc_user_x.getTag('destroy') - r = destroy.getTagData('reason') - if r: - reason += ' (%s)' % r - if destroy.getAttr('jid'): - try: - jid = helpers.parse_jid(destroy.getAttr('jid')) - reason += '\n' + _('You can join this room instead: %s') \ - % jid - except common.helpers.InvalidFormat: - pass - statusCode = ['destroyed'] - else: - reason = prs.getReason() - statusCode = prs.getStatusCode() - self.dispatch('GC_NOTIFY', (jid_stripped, show, status, resource, - prs.getRole(), prs.getAffiliation(), prs.getJid(), - reason, prs.getActor(), statusCode, prs.getNewNick(), - avatar_sha)) - return - - if ptype == 'subscribe': - log.debug('subscribe request from %s' % who) - if who.find('@') <= 0 and who in self.agent_registrations: - self.agent_registrations[who]['sub_received'] = True - if not self.agent_registrations[who]['roster_push']: - # We'll reply after roster push result - return - if gajim.config.get_per('accounts', self.name, 'autoauth') or \ - who.find('@') <= 0 or jid_stripped in self.jids_for_auto_auth or \ - transport_auto_auth: - if self.connection: - p = common.xmpp.Presence(who, 'subscribed') - p = self.add_sha(p) - self.connection.send(p) - if who.find('@') <= 0 or transport_auto_auth: - self.dispatch('NOTIFY', (jid_stripped, 'offline', 'offline', - resource, prio, keyID, timestamp, None)) - if transport_auto_auth: - self.automatically_added.append(jid_stripped) - self.request_subscription(jid_stripped, name = user_nick) - else: - if not status: - status = _('I would like to add you to my roster.') - self.dispatch('SUBSCRIBE', (jid_stripped, status, user_nick)) - elif ptype == 'subscribed': - if jid_stripped in self.automatically_added: - self.automatically_added.remove(jid_stripped) - else: - # detect a subscription loop - if jid_stripped not in self.subscribed_events: - self.subscribed_events[jid_stripped] = [] - self.subscribed_events[jid_stripped].append(time_time()) - block = False - if len(self.subscribed_events[jid_stripped]) > 5: - if time_time() - self.subscribed_events[jid_stripped][0] < 5: - block = True - self.subscribed_events[jid_stripped] = self.subscribed_events[jid_stripped][1:] - if block: - gajim.config.set_per('account', self.name, - 'dont_ack_subscription', True) - else: - self.dispatch('SUBSCRIBED', (jid_stripped, resource)) - # BE CAREFUL: no con.updateRosterItem() in a callback - log.debug(_('we are now subscribed to %s') % who) - elif ptype == 'unsubscribe': - log.debug(_('unsubscribe request from %s') % who) - elif ptype == 'unsubscribed': - log.debug(_('we are now unsubscribed from %s') % who) - # detect a unsubscription loop - if jid_stripped not in self.subscribed_events: - self.subscribed_events[jid_stripped] = [] - self.subscribed_events[jid_stripped].append(time_time()) - block = False - if len(self.subscribed_events[jid_stripped]) > 5: - if time_time() - self.subscribed_events[jid_stripped][0] < 5: - block = True - self.subscribed_events[jid_stripped] = self.subscribed_events[jid_stripped][1:] - if block: - gajim.config.set_per('account', self.name, 'dont_ack_subscription', - True) - else: - self.dispatch('UNSUBSCRIBED', jid_stripped) - elif ptype == 'error': - errmsg = prs.getError() - errcode = prs.getErrorCode() - if errcode != '502': # Internal Timeout: - # print in the window the error - self.dispatch('ERROR_ANSWER', ('', jid_stripped, - errmsg, errcode)) - if errcode != '409': # conflict # See #5120 - self.dispatch('NOTIFY', (jid_stripped, 'error', errmsg, resource, - prio, keyID, timestamp, None)) - - if ptype == 'unavailable': - for jid in [jid_stripped, who]: - if jid not in self.sessions: - continue - # automatically terminate sessions that they haven't sent a thread - # ID in, only if other part support thread ID - for sess in self.sessions[jid].values(): - if not sess.received_thread_id: - contact = gajim.contacts.get_contact(self.name, jid) - # FIXME: I don't know if this is the correct behavior here. - # Anyway, it is the old behavior when we assumed that - # not-existing contacts don't support anything - contact_exists = bool(contact) - session_supported = contact_exists and ( - contact.supports(common.xmpp.NS_SSN) or - contact.supports(common.xmpp.NS_ESESSION)) - if session_supported: - sess.terminate() - del self.sessions[jid][sess.thread_id] - - if avatar_sha is not None and ptype != 'error': - if jid_stripped not in self.vcard_shas: - cached_vcard = self.get_cached_vcard(jid_stripped) - if cached_vcard and 'PHOTO' in cached_vcard and \ - 'SHA' in cached_vcard['PHOTO']: - self.vcard_shas[jid_stripped] = cached_vcard['PHOTO']['SHA'] - else: - self.vcard_shas[jid_stripped] = '' - if avatar_sha != self.vcard_shas[jid_stripped]: - # avatar has been updated - self.request_vcard(jid_stripped) - if not ptype or ptype == 'unavailable': - if gajim.config.get('log_contact_status_changes') and \ - gajim.config.should_log(self.name, jid_stripped): - try: - gajim.logger.write('status', jid_stripped, status, show) - except exceptions.PysqliteOperationalError, e: - self.dispatch('DB_ERROR', (_('Disk Write Error'), str(e))) - except exceptions.DatabaseMalformed: - pritext = _('Database Error') - sectext = _('The database file (%s) cannot be read. Try to ' - 'repair it (see http://trac.gajim.org/wiki/DatabaseBackup) ' - 'or remove it (all history will be lost).') % \ - common.logger.LOG_DB_PATH - self.dispatch('DB_ERROR', (pritext, sectext)) - our_jid = gajim.get_jid_from_account(self.name) - if jid_stripped == our_jid and resource == self.server_resource: - # We got our own presence - self.dispatch('STATUS', show) - else: - self.dispatch('NOTIFY', (jid_stripped, show, status, resource, prio, - keyID, timestamp, contact_nickname)) - # END presenceCB + conn=self, iq_obj=prs)) def _StanzaArrivedCB(self, con, obj): self.last_io = gajim.idlequeue.current_time() diff --git a/src/common/connection_handlers_events.py b/src/common/connection_handlers_events.py index ea4d554ad..71964fe22 100644 --- a/src/common/connection_handlers_events.py +++ b/src/common/connection_handlers_events.py @@ -20,12 +20,16 @@ import datetime import sys +from time import (localtime, time as time_time) +from calendar import timegm from common import nec from common import helpers from common import gajim from common import xmpp from common import dataforms +from common import exceptions +from common.logger import LOG_DB_PATH import logging log = logging.getLogger('gajim.c.connection_handlers_events') @@ -592,3 +596,367 @@ class StreamConflictReceivedEvent(nec.NetworkIncomingEvent): if self.base_event.iq_obj.getTag('conflict'): self.conn = self.base_event.conn return True + +class PresenceReceivedEvent(nec.NetworkIncomingEvent, HelperEvent): + name = 'presence-received' + base_network_events = ['raw-pres-received'] + + def generate(self): + self.conn = self.base_event.conn + self.iq_obj = self.base_event.iq_obj + self.ptype = self.iq_obj.getType() + if self.ptype == 'available': + self.ptype = None + rfc_types = ('unavailable', 'error', 'subscribe', 'subscribed', + 'unsubscribe', 'unsubscribed') + if self.ptype and not self.ptype in rfc_types: + self.ptype = None + log.debug('PresenceCB: %s' % self.ptype) + if not self.conn or self.conn.connected < 2: + log.debug('account is no more connected') + return + try: + self.get_jid_resource() + except Exception: + if self.iq_obj.getTag('error') and self.iq_obj.getTag('error').\ + getTag('jid-malformed'): + # wrong jid, we probably tried to change our nick in a room to a non + # valid one + who = str(self.iq_obj.getFrom()) + jid_stripped, resource = gajim.get_room_and_nick_from_fjid(who) + self.conn.dispatch('GC_MSG', (jid_stripped, + _('Nickname not allowed: %s') % resource, None, False, None, + [])) + return + self.timestamp = None + self.get_id() + self.is_gc = False # is it a GC presence ? + sigTag = None + ns_muc_user_x = None + avatar_sha = None + # XEP-0172 User Nickname + self.user_nick = self.iq_obj.getTagData('nick') or '' + self.contact_nickname = None + transport_auto_auth = False + # XEP-0203 + delay_tag = self.iq_obj.getTag('delay', namespace=xmpp.NS_DELAY2) + if delay_tag: + tim = self.iq_obj.getTimestamp2() + tim = helpers.datetime_tuple(tim) + self.timestamp = localtime(timegm(tim)) + xtags = self.iq_obj.getTags('x') + for x in xtags: + namespace = x.getNamespace() + if namespace.startswith(xmpp.NS_MUC): + self.is_gc = True + if namespace == xmpp.NS_MUC_USER and x.getTag('destroy'): + ns_muc_user_x = x + elif namespace == xmpp.NS_SIGNED: + sigTag = x + elif namespace == xmpp.NS_VCARD_UPDATE: + avatar_sha = x.getTagData('photo') + self.contact_nickname = x.getTagData('nickname') + elif namespace == xmpp.NS_DELAY and not self.timestamp: + # XEP-0091 + tim = self.iq_obj.getTimestamp() + tim = helpers.datetime_tuple(tim) + self.timestamp = localtime(timegm(tim)) + elif namespace == 'http://delx.cjb.net/protocol/roster-subsync': + # see http://trac.gajim.org/ticket/326 + agent = gajim.get_server_from_jid(self.jid) + if self.conn.connection.getRoster().getItem(agent): + # to be sure it's a transport contact + transport_auto_auth = True + + if not self.is_gc and self.id_ and self.id_.startswith('gajim_muc_') \ + and self.ptype == 'error': + # Error presences may not include sent stanza, so we don't detect it's + # a muc preence. So detect it by ID + h = hmac.new(self.conn.secret_hmac, self.jid).hexdigest()[:6] + if self.id_.split('_')[-1] == h: + self.is_gc = True + self.status = self.iq_obj.getStatus() or '' + self.show = self.iq_obj.getShow() + if self.show not in ('chat', 'away', 'xa', 'dnd'): + self.show = '' # We ignore unknown show + if not self.ptype and not self.show: + self.show = 'online' + elif self.ptype == 'unavailable': + self.show = 'offline' + + self.prio = self.iq_obj.getPriority() + try: + self.prio = int(self.prio) + except Exception: + self.prio = 0 + self.keyID = '' + if sigTag and self.conn.USE_GPG and self.ptype != 'error': + # error presences contain our own signature + # verify + sigmsg = sigTag.getData() + self.keyID = self.conn.gpg.verify(self.status, sigmsg) + self.keyID = helpers.prepare_and_validate_gpg_keyID(self.conn.name, + self.jid, self.keyID) + + if self.is_gc: + if self.ptype == 'error': + errcon = self.iq_obj.getError() + errmsg = self.iq_obj.getErrorMsg() + errcode = self.iq_obj.getErrorCode() + + gc_control = gajim.interface.msg_win_mgr.get_gc_control( + self.jid, self.conn.name) + + # If gc_control is missing - it may be minimized. Try to get it + # from there. If it's not there - then it's missing anyway and + # will remain set to None. + if gc_control is None: + minimized = gajim.interface.minimized_controls[ + self.conn.name] + gc_control = minimized.get(self.jid) + + if errcode == '502': + # Internal Timeout: + self.show = 'error' + self.status = errmsg + return True + elif errcode == '503': + if gc_control is None or gc_control.autorejoin is None: + # maximum user number reached + self.conn.dispatch('GC_ERROR', (gc_control, + _('Unable to join group chat'), + _('Maximum number of users for %s has been ' + 'reached') % self.jid)) + elif (errcode == '401') or (errcon == 'not-authorized'): + # password required to join + self.conn.dispatch('GC_PASSWORD_REQUIRED', (self.jid, + self.resource)) + elif (errcode == '403') or (errcon == 'forbidden'): + # we are banned + self.conn.dispatch('GC_ERROR', (gc_control, + _('Unable to join group chat'), + _('You are banned from group chat %s.') % self.jid)) + elif (errcode == '404') or (errcon in ('item-not-found', + 'remote-server-not-found')): + if gc_control is None or gc_control.autorejoin is None: + # group chat does not exist + self.conn.dispatch('GC_ERROR', (gc_control, + _('Unable to join group chat'), + _('Group chat %s does not exist.') % self.jid)) + elif (errcode == '405') or (errcon == 'not-allowed'): + self.conn.dispatch('GC_ERROR', (gc_control, + _('Unable to join group chat'), + _('Group chat creation is restricted.'))) + elif (errcode == '406') or (errcon == 'not-acceptable'): + self.conn.dispatch('GC_ERROR', (gc_control, + _('Unable to join group chat'), + _('Your registered nickname must be used in group chat ' + '%s.') % self.jid)) + elif (errcode == '407') or (errcon == 'registration-required'): + self.conn.dispatch('GC_ERROR', (gc_control, + _('Unable to join group chat'), + _('You are not in the members list in groupchat %s.') %\ + self.jid)) + elif (errcode == '409') or (errcon == 'conflict'): + # nick conflict + self.conn.dispatch('ASK_NEW_NICK', (self.jid,)) + else: # print in the window the error + self.conn.dispatch('ERROR_ANSWER', ('', self.jid, + errmsg, errcode)) + elif not self.ptype or self.ptype == 'unavailable': + if gajim.config.get('log_contact_status_changes') and \ + gajim.config.should_log(self.conn.name, self.jid): + gc_c = gajim.contacts.get_gc_contact(self.conn.name, + self.jid, self.resource) + st = status or '' + if gc_c: + jid = gc_c.jid + else: + jid = self.iq_obj.getJid() + if jid: + # we know real jid, save it in db + st += ' (%s)' % jid + try: + gajim.logger.write('gcstatus', self.fjid, st, self.show) + except exceptions.PysqliteOperationalError, e: + self.conn.dispatch('DB_ERROR', (_('Disk Write Error'), + str(e))) + except exceptions.DatabaseMalformed: + pritext = _('Database Error') + sectext = _('The database file (%s) cannot be read. ' + 'Try to repair it (see ' + 'http://trac.gajim.org/wiki/DatabaseBackup) or ' + 'remove it (all history will be lost).') % \ + LOG_DB_PATH + self.conn.dispatch('DB_ERROR', (pritext, sectext)) + if avatar_sha == '': + # contact has no avatar + puny_nick = helpers.sanitize_filename(self.resource) + gajim.interface.remove_avatar_files(self.jid, puny_nick) + # NOTE: if it's a gc presence, don't ask vcard here. + # We may ask it to real jid in gui part. + if ns_muc_user_x: + # Room has been destroyed. see + # http://www.xmpp.org/extensions/xep-0045.html#destroyroom + reason = _('Room has been destroyed') + destroy = ns_muc_user_x.getTag('destroy') + r = destroy.getTagData('reason') + if r: + reason += ' (%s)' % r + if destroy.getAttr('jid'): + try: + jid = helpers.parse_jid(destroy.getAttr('jid')) + reason += '\n' + \ + _('You can join this room instead: %s') % jid + except common.helpers.InvalidFormat: + pass + statusCode = ['destroyed'] + else: + reason = self.iq_obj.getReason() + statusCode = self.iq_obj.getStatusCode() + role = self.iq_obj.getRole() + affiliation = self.iq_obj.getAffiliation() + prs_jid = self.iq_obj.getJid() + actor = self.iq_obj.getActor() + new_nick = self.iq_obj.getNewNick() + self.conn.dispatch('GC_NOTIFY', (self.jid, self.show, + self.status, self.resource, role, affiliation, prs_jid, + reason, actor, statusCode, new_nick, avatar_sha)) + return + + if self.ptype == 'subscribe': + log.debug('subscribe request from %s' % self.jfid) + if self.fjid.find('@') <= 0 and self.fjid in \ + self.agent_registrations: + self.agent_registrations[self.fjid]['sub_received'] = True + if not self.agent_registrations[self.fjid]['roster_push']: + # We'll reply after roster push result + return + if gajim.config.get_per('accounts', self.conn.name, 'autoauth') or \ + self.fjid.find('@') <= 0 or self.jid in self.jids_for_auto_auth or \ + transport_auto_auth: + if self.conn.connection: + p = xmpp.Presence(self.fjid, 'subscribed') + p = self.conn.add_sha(p) + self.conn.connection.send(p) + if self.fjid.find('@') <= 0 or transport_auto_auth: + self.show = 'offline' + self.status = 'offline' + return True + + if transport_auto_auth: + self.conn.automatically_added.append(self.jid) + self.conn.request_subscription(self.jid, + name=self.user_nick) + else: + if not self.status: + self.status = _('I would like to add you to my roster.') + self.conn.dispatch('SUBSCRIBE', (self.jid, self.status, + self.user_nick)) + elif self.ptype == 'subscribed': + if self.jid in self.conn.automatically_added: + self.conn.automatically_added.remove(self.jid) + else: + # detect a subscription loop + if self.jid not in self.conn.subscribed_events: + self.conn.subscribed_events[self.jid] = [] + self.conn.subscribed_events[self.jid].append(time_time()) + block = False + if len(self.conn.subscribed_events[self.jid]) > 5: + if time_time() - self.subscribed_events[self.jid][0] < 5: + block = True + self.conn.subscribed_events[self.jid] = \ + self.conn.subscribed_events[self.jid][1:] + if block: + gajim.config.set_per('account', self.conn.name, + 'dont_ack_subscription', True) + else: + self.conn.dispatch('SUBSCRIBED', (self.jid, self.resource)) + # BE CAREFUL: no con.updateRosterItem() in a callback + log.debug(_('we are now subscribed to %s') % self.jid) + elif self.ptype == 'unsubscribe': + log.debug(_('unsubscribe request from %s') % self.jid) + elif self.ptype == 'unsubscribed': + log.debug(_('we are now unsubscribed from %s') % self.jid) + # detect a unsubscription loop + if self.jid not in self.conn.subscribed_events: + self.conn.subscribed_events[self.jid] = [] + self.conn.subscribed_events[self.jid].append(time_time()) + block = False + if len(self.conn.subscribed_events[self.jid]) > 5: + if time_time() - self.conn.subscribed_events[self.jid][0] < 5: + block = True + self.conn.subscribed_events[self.jid] = \ + self.conn.subscribed_events[self.jid][1:] + if block: + gajim.config.set_per('account', self.conn.name, + 'dont_ack_subscription', True) + else: + self.dispatch('UNSUBSCRIBED', self.jid) + elif self.ptype == 'error': + errmsg = self.iq_obj.getError() + errcode = self.iq_obj.getErrorCode() + if errcode != '502': # Internal Timeout: + # print in the window the error + self.conn.dispatch('ERROR_ANSWER', ('', self.jid, errmsg, errcode)) + if errcode != '409': # conflict # See #5120 + self.show = 'error' + self.status = errmsg + return True + + elif self.ptype == 'unavailable': + for jid in [self.jid, self.fjid]: + if jid not in self.conn.sessions: + continue + # automatically terminate sessions that they haven't sent a thread + # ID in, only if other part support thread ID + for sess in self.conn.sessions[jid].values(): + if not sess.received_thread_id: + contact = gajim.contacts.get_contact(self.conn.name, + jid) + # FIXME: I don't know if this is the correct behavior here. + # Anyway, it is the old behavior when we assumed that + # not-existing contacts don't support anything + contact_exists = bool(contact) + session_supported = contact_exists and ( + contact.supports(xmpp.NS_SSN) or + contact.supports(xmpp.NS_ESESSION)) + if session_supported: + sess.terminate() + del self.conn.sessions[jid][sess.thread_id] + + if avatar_sha is not None and self.ptype != 'error': + if self.jid not in self.conn.vcard_shas: + cached_vcard = self.conn.get_cached_vcard(self.jid) + if cached_vcard and 'PHOTO' in cached_vcard and \ + 'SHA' in cached_vcard['PHOTO']: + self.conn.vcard_shas[self.jid] = \ + cached_vcard['PHOTO']['SHA'] + else: + self.conn.vcard_shas[self.jid] = '' + if avatar_sha != self.conn.vcard_shas[self.jid]: + # avatar has been updated + self.conn.request_vcard(self.jid) + + if not self.ptype or self.ptype == 'unavailable': + if gajim.config.get('log_contact_status_changes') and \ + gajim.config.should_log(self.conn.name, self.jid): + try: + gajim.logger.write('status', self.jid, self.status, + self.show) + except exceptions.PysqliteOperationalError, e: + self.conn.dispatch('DB_ERROR', (_('Disk Write Error'), str(e))) + except exceptions.DatabaseMalformed: + pritext = _('Database Error') + sectext = _('The database file (%s) cannot be read. Try to ' + 'repair it (see ' + 'http://trac.gajim.org/wiki/DatabaseBackup) or remove ' + 'it (all history will be lost).') % LOG_DB_PATH + self.conn.dispatch('DB_ERROR', (pritext, sectext)) + our_jid = gajim.get_jid_from_account(self.conn.name) + if self.jid == our_jid and self.resource == \ + self.conn.server_resource: + # We got our own presence + self.conn.dispatch('STATUS', self.show) + else: + return True diff --git a/src/gui_interface.py b/src/gui_interface.py index e41778d90..6b1f8af4a 100644 --- a/src/gui_interface.py +++ b/src/gui_interface.py @@ -284,7 +284,7 @@ class Interface: profile_window.ProfileWindow(account) gajim.connections[account].request_vcard(jid) - def handle_event_notify(self, account, array): + def handle_event_presence(self, obj): # 'NOTIFY' (account, (jid, status, status message, resource, # priority, # keyID, timestamp, contact_nickname)) # @@ -294,39 +294,29 @@ class Interface: statuss = ['offline', 'error', 'online', 'chat', 'away', 'xa', 'dnd', 'invisible'] - # Ignore invalid show - if array[1] not in statuss: - return - old_show = 0 - new_show = statuss.index(array[1]) - status_message = array[2] - jid = array[0].split('/')[0] - keyID = array[5] - contact_nickname = array[7] + + account = obj.conn.name + jid = obj.jid + show = obj.show + status = obj.status + resource = obj.resource or '' + priority = obj.prio + keyID = obj.keyID + timestamp = obj.timestamp + contact_nickname = obj.contact_nickname + + obj.old_show = 0 + obj.new_show = statuss.index(show) + lcontact = [] - # Get the proper keyID - keyID = helpers.prepare_and_validate_gpg_keyID(account, jid, keyID) - - resource = array[3] - if not resource: - resource = '' - priority = array[4] - if gajim.jid_is_transport(jid): - # It must be an agent - ji = jid.replace('@', '') - else: - ji = jid - highest = gajim.contacts.get_contact_with_highest_priority(account, jid) was_highest = (highest and highest.resource == resource) - conn = gajim.connections[account] - # Update contact jid_list = gajim.contacts.get_jid_list(account) - if ji in jid_list or jid == gajim.get_jid_from_account(account): - lcontact = gajim.contacts.get_contacts(account, ji) + if jid in jid_list or jid == gajim.get_jid_from_account(account): + lcontact = gajim.contacts.get_contacts(account, jid) contact1 = None resources = [] for c in lcontact: @@ -337,56 +327,58 @@ class Interface: if contact1: if contact1.show in statuss: - old_show = statuss.index(contact1.show) + obj.old_show = statuss.index(contact1.show) # nick changed if contact_nickname is not None and \ contact1.contact_name != contact_nickname: contact1.contact_name = contact_nickname self.roster.draw_contact(jid, account) - if old_show == new_show and contact1.status == status_message \ + if obj.old_show == obj.new_show and contact1.status == status \ and contact1.priority == priority: # no change return else: contact1 = gajim.contacts.get_first_contact_from_jid(account, - ji) + jid) if not contact1: # Presence of another resource of our # jid # Create self contact and add to roster - if resource == conn.server_resource: + if resource == obj.conn.server_resource: return # Ignore offline presence of unknown self resource - if new_show < 2: + if obj.new_show < 2: return - contact1 = gajim.contacts.create_self_contact(jid=ji, - account=account, show=array[1], status=status_message, + contact1 = gajim.contacts.create_self_contact(jid=jid, + account=account, show=show, status=status, priority=priority, keyID=keyID, resource=resource) - old_show = 0 + obj.old_show = 0 gajim.contacts.add_contact(account, contact1) lcontact.append(contact1) elif contact1.show in statuss: - old_show = statuss.index(contact1.show) + obj.old_show = statuss.index(contact1.show) if (resources != [''] and (len(lcontact) != 1 or \ - lcontact[0].show != 'offline')) and jid.find('@') > 0: + lcontact[0].show != 'offline')) and \ + not gajim.jid_is_transport(jid): # Another resource of an existing contact connected - old_show = 0 + obj.old_show = 0 contact1 = gajim.contacts.copy_contact(contact1) lcontact.append(contact1) contact1.resource = resource self.roster.add_contact(contact1.jid, account) - if contact1.jid.find('@') > 0 and len(lcontact) == 1: + if not gajim.jid_is_transport(contact1.jid) and len(lcontact) == 1: # It's not an agent - if old_show == 0 and new_show > 1: + if obj.old_show == 0 and obj.new_show > 1: if not contact1.jid in gajim.newly_added[account]: gajim.newly_added[account].append(contact1.jid) if contact1.jid in gajim.to_be_removed[account]: gajim.to_be_removed[account].remove(contact1.jid) gobject.timeout_add_seconds(5, self.roster.remove_newly_added, contact1.jid, account) - elif old_show > 1 and new_show == 0 and conn.connected > 1: + elif obj.old_show > 1 and obj.new_show == 0 and \ + obj.conn.connected > 1: if not contact1.jid in gajim.to_be_removed[account]: gajim.to_be_removed[account].append(contact1.jid) if contact1.jid in gajim.newly_added[account]: @@ -396,17 +388,16 @@ class Interface: self.roster.remove_to_be_removed, contact1.jid, account) # unset custom status - if (old_show == 0 and new_show > 1) or \ - (old_show > 1 and new_show == 0 and conn.connected > 1): + if (obj.old_show == 0 and obj.new_show > 1) or \ + (obj.old_show > 1 and obj.new_show == 0 and obj.conn.connected > 1): if account in self.status_sent_to_users and \ jid in self.status_sent_to_users[account]: del self.status_sent_to_users[account][jid] - contact1.show = array[1] - contact1.status = status_message + contact1.show = show + contact1.status = status contact1.priority = priority contact1.keyID = keyID - timestamp = array[6] if timestamp: contact1.last_status_time = timestamp elif not gajim.block_signed_in_notifications[account]: @@ -416,41 +407,41 @@ class Interface: if gajim.jid_is_transport(jid): # It must be an agent - if ji in jid_list: + if jid in jid_list: # Update existing iter and group counting - self.roster.draw_contact(ji, account) + self.roster.draw_contact(jid, account) self.roster.draw_group(_('Transports'), account) - if new_show > 1 and ji in gajim.transport_avatar[account]: + if obj.new_show > 1 and jid in gajim.transport_avatar[account]: # transport just signed in. # request avatars - for jid_ in gajim.transport_avatar[account][ji]: - conn.request_vcard(jid_) + for jid_ in gajim.transport_avatar[account][jid]: + obj.conn.request_vcard(jid_) # transport just signed in/out, don't show # popup notifications for 30s - account_ji = account + '/' + ji - gajim.block_signed_in_notifications[account_ji] = True + account_jid = account + '/' + jid + gajim.block_signed_in_notifications[account_jid] = True gobject.timeout_add_seconds(30, - self.unblock_signed_in_notifications, account_ji) + self.unblock_signed_in_notifications, account_jid) locations = (self.instances, self.instances[account]) for location in locations: if 'add_contact' in location: - if old_show == 0 and new_show > 1: + if obj.old_show == 0 and obj.new_show > 1: location['add_contact'].transport_signed_in(jid) break - elif old_show > 1 and new_show == 0: + elif obj.old_show > 1 and obj.new_show == 0: location['add_contact'].transport_signed_out(jid) break - elif ji in jid_list: + elif jid in jid_list: # It isn't an agent # reset chatstate if needed: # (when contact signs out or has errors) - if array[1] in ('offline', 'error'): + if show in ('offline', 'error'): contact1.our_chatstate = contact1.chatstate = \ contact1.composing_xep = None # TODO: This causes problems when another - # resource signs off! - conn.stop_all_active_file_transfers(contact1) + # resource signs off! + obj.conn.stop_all_active_file_transfers(contact1) # disable encryption, since if any messages are # lost they'll be not decryptable (note that @@ -459,19 +450,18 @@ class Interface: # there won't be any sessions here if the contact terminated # their sessions before going offline (which we do) - for sess in conn.get_sessions(ji): - if (ji + '/' + resource) != str(sess.jid): + for sess in obj.conn.get_sessions(jid): + if obj.fjid != str(sess.jid): continue if sess.control: sess.control.no_autonegotiation = False if sess.enable_encryption: sess.terminate_e2e() - conn.delete_session(jid, sess.thread_id) + obj.conn.delete_session(jid, sess.thread_id) - self.roster.chg_contact_status(contact1, array[1], status_message, - account) + self.roster.chg_contact_status(contact1, show, status, account) # Notifications - if old_show < 2 and new_show > 1: + if obj.old_show < 2 and obj.new_show > 1: show_notif = True for c in lcontact: if c.resource == resource: @@ -482,7 +472,8 @@ class Interface: break if show_notif: # no other resource is connected, let's look in metacontacts - family = gajim.contacts.get_metacontacts_family(account, ji) + family = gajim.contacts.get_metacontacts_family(account, + jid) for info in family: acct_ = info['account'] jid_ = info['jid'] @@ -495,12 +486,9 @@ class Interface: break if show_notif: notify.notify('contact_connected', jid, account, - status_message) - if self.remote_ctrl: - self.remote_ctrl.raise_signal('ContactPresence', (account, - array)) + status) - elif old_show > 1 and new_show < 2: + elif obj.old_show > 1 and obj.new_show < 2: show_notif = True for c in lcontact: if c.resource == resource: @@ -511,7 +499,8 @@ class Interface: break if show_notif: # no other resource is connected, let's look in metacontacts - family = gajim.contacts.get_metacontacts_family(account, ji) + family = gajim.contacts.get_metacontacts_family(account, + jid) for info in family: acct_ = info['account'] jid_ = info['jid'] @@ -523,26 +512,18 @@ class Interface: show_notif = False break if show_notif: - notify.notify('contact_disconnected', jid, account, - status_message) - if self.remote_ctrl: - self.remote_ctrl.raise_signal('ContactAbsence', (account, - array)) - # FIXME: stop non active file transfers + notify.notify('contact_disconnected', jid, account, status) # Status change (not connected/disconnected or # error (<1)) - elif new_show > 1: - notify.notify('status_change', jid, account, [new_show, - status_message]) - if self.remote_ctrl: - self.remote_ctrl.raise_signal('ContactStatus', (account, - array)) + elif obj.new_show > 1: + notify.notify('status_change', jid, account, [obj.new_show, + status]) else: # FIXME: MSN transport (CMSN1.2.1 and PyMSN) don't # follow the XEP, still the case in 2008. # It's maybe a GC_NOTIFY (specialy for MSN gc) - self.handle_event_gc_notify(account, (jid, array[1], status_message, - array[3], None, None, None, None, None, [], None, None)) + self.handle_event_gc_notify(account, (jid, show, status, + resource, None, None, None, None, None, [], None, None)) highest = gajim.contacts.get_contact_with_highest_priority(account, jid) is_highest = (highest and highest.resource == resource) @@ -2067,7 +2048,6 @@ class Interface: 'INFORMATION': [self.handle_event_information], 'STATUS': [self.handle_event_status], 'NEW_JID': [self.handle_event_new_jid], - 'NOTIFY': [self.handle_event_notify], 'MSGERROR': [self.handle_event_msgerror], 'MSGSENT': [self.handle_event_msgsent], 'MSGNOTSENT': [self.handle_event_msgnotsent], @@ -2142,6 +2122,7 @@ class Interface: 'last-result-received': [self.handle_event_last_status_time], 'muc-admin-received': [self.handle_event_gc_affiliation], 'muc-owner-received': [self.handle_event_gc_config], + 'presence-received': [self.handle_event_presence], 'roster-info': [self.handle_event_roster_info], 'roster-item-exchange-received': \ [self.handle_event_roster_item_exchange], diff --git a/src/remote_control.py b/src/remote_control.py index 7c6c93537..e6d7a723d 100644 --- a/src/remote_control.py +++ b/src/remote_control.py @@ -104,7 +104,7 @@ class Remote: bus_name = dbus.service.BusName(SERVICE, bus=session_bus) self.signal_object = SignalObject(bus_name) - + gajim.ged.register_event_handler('last-result-received', ged.POSTGUI, self.on_last_status_time) gajim.ged.register_event_handler('version-result-received', ged.POSTGUI, @@ -115,6 +115,8 @@ class Remote: self.on_gmail_notify) gajim.ged.register_event_handler('roster-info', ged.POSTGUI, self.on_roster_info) + gajim.ged.register_event_handler('presence-received', ged.POSTGUI, + self.on_presence_received) def on_last_status_time(self, obj): self.raise_signal('LastStatusTime', (obj.conn.name, [ @@ -136,6 +138,19 @@ class Remote: self.raise_signal('RosterInfo', (obj.conn.name, [obj.jid, obj.nickname, obj.sub, obj.ask, obj.groups])) + def on_presence_received(self, obj): + event = None + if obj.old_show < 2 and obj.new_show > 1: + event = 'ContactPresence' + elif obj.old_show > 1 and obj.new_show < 2: + event = 'ContactAbsence' + elif obj.new_show > 1: + event = 'ContactStatus' + if event: + self.raise_signal(event, (obj.conn.name, [obj.jid, obj.show, + obj.status, obj.resource, obj.prio, obj.keyID, obj.timestamp, + obj.contact_nickname])) + def raise_signal(self, signal, arg): if self.signal_object: try: