Refactor Presence

- Remove option to hide self contacts for now. This makes the code less
complicated.
- Move as much code as possible into the presence module
- Use nbxmpp properties
This commit is contained in:
Philipp Hörist 2019-01-01 16:19:35 +01:00
parent 608607b721
commit a289ad5f60
9 changed files with 260 additions and 313 deletions

View File

@ -268,7 +268,6 @@ class Config:
'check_idle_every_foo_seconds': [opt_int, 2, _('Choose interval between 2 checks of idleness.')],
'uri_schemes': [opt_str, 'aaa:// aaas:// acap:// cap:// cid: crid:// data: dav: dict:// dns: fax: file:/ ftp:// geo: go: gopher:// h323: http:// https:// iax: icap:// im: imap:// info: ipp:// iris: iris.beep: iris.xpc: iris.xpcs: iris.lwz: ldap:// mid: modem: msrp:// msrps:// mtqp:// mupdate:// news: nfs:// nntp:// opaquelocktoken: pop:// pres: prospero:// rtsp:// service: shttp:// sip: sips: sms: snmp:// soap.beep:// soap.beeps:// tag: tel: telnet:// tftp:// thismessage:/ tip:// tv: urn:// vemmi:// xmlrpc.beep:// xmlrpc.beeps:// z39.50r:// z39.50s:// about: apt: cvs:// daap:// ed2k:// feed: fish:// git:// iax2: irc:// ircs:// ldaps:// magnet: mms:// rsync:// ssh:// svn:// sftp:// smb:// webcal:// aesgcm://', _('Valid uri schemes. Only schemes in this list will be accepted as "real" uri. (mailto and xmpp are handled separately)'), True],
'shell_like_completion': [opt_bool, False, _('If true, completion in groupchats will be like a shell auto-completion')],
'show_self_contact': [opt_str, 'when_other_resource', _('When is self contact row displayed. Can be "always", "when_other_resource" or "never"'), True],
'audio_input_device': [opt_str, 'autoaudiosrc ! volume name=gajim_vol'],
'audio_output_device': [opt_str, 'autoaudiosink'],
'video_input_device': [opt_str, 'autovideosrc'],

View File

@ -67,136 +67,13 @@ class ConnectionHandlersBase:
# We decrypt GPG messages one after the other. Keep queue in mem
self.gpg_messages_to_decrypt = []
app.ged.register_event_handler('presence-received', ged.CORE,
self._nec_presence_received)
app.ged.register_event_handler('gc-message-received', ged.CORE,
self._nec_gc_message_received)
def cleanup(self):
app.ged.remove_event_handler('presence-received', ged.CORE,
self._nec_presence_received)
app.ged.remove_event_handler('gc-message-received', ged.CORE,
self._nec_gc_message_received)
def _nec_presence_received(self, obj):
account = obj.conn.name
if account != self.name:
return
jid = obj.jid
resource = obj.resource or ''
statuss = ['offline', 'error', 'online', 'chat', 'away', 'xa', 'dnd',
'invisible']
obj.old_show = 0
obj.new_show = statuss.index(obj.show)
obj.contact_list = []
highest = app.contacts.get_contact_with_highest_priority(account, jid)
obj.was_highest = (highest and highest.resource == resource)
# Update contact
obj.contact_list = app.contacts.get_contacts(account, jid)
obj.contact = None
resources = []
for c in obj.contact_list:
resources.append(c.resource)
if c.resource == resource:
obj.contact = c
break
if obj.contact:
if obj.contact.show in statuss:
obj.old_show = statuss.index(obj.contact.show)
# nick changed
if obj.contact_nickname is not None and \
obj.contact.contact_name != obj.contact_nickname:
obj.contact.contact_name = obj.contact_nickname
obj.need_redraw = True
elif obj.old_show != obj.new_show or obj.contact.status != \
obj.status:
obj.need_redraw = True
else:
obj.contact = app.contacts.get_first_contact_from_jid(account,
jid)
if not obj.contact:
# Presence of another resource of our jid
# Create self contact and add to roster
if resource == obj.conn.server_resource:
return
# Ignore offline presence of unknown self resource
if obj.new_show < 2:
return
obj.contact = app.contacts.create_self_contact(jid=jid,
account=account, show=obj.show, status=obj.status,
priority=obj.prio, keyID=obj.keyID,
resource=obj.resource)
app.contacts.add_contact(account, obj.contact)
obj.contact_list.append(obj.contact)
elif obj.contact.show in statuss:
obj.old_show = statuss.index(obj.contact.show)
if (resources != [''] and (len(obj.contact_list) != 1 or \
obj.contact_list[0].show not in ('not in roster', 'offline'))) and \
not app.jid_is_transport(jid):
# Another resource of an existing contact connected
obj.old_show = 0
obj.contact = app.contacts.copy_contact(obj.contact)
obj.contact_list.append(obj.contact)
obj.contact.resource = resource
obj.need_redraw = True
obj.need_add_in_roster = True
if not app.jid_is_transport(jid) and len(obj.contact_list) == 1:
# It's not an agent
if obj.old_show == 0 and obj.new_show > 1:
if not jid in app.newly_added[account]:
app.newly_added[account].append(jid)
if jid in app.to_be_removed[account]:
app.to_be_removed[account].remove(jid)
elif obj.old_show > 1 and obj.new_show == 0 and \
obj.conn.connected > 1:
if not jid in app.to_be_removed[account]:
app.to_be_removed[account].append(jid)
if jid in app.newly_added[account]:
app.newly_added[account].remove(jid)
obj.need_redraw = True
obj.contact.show = obj.show
obj.contact.status = obj.status
obj.contact.priority = obj.prio
attached_keys = app.config.get_per('accounts', account,
'attached_gpg_keys').split()
if jid in attached_keys:
obj.contact.keyID = attached_keys[attached_keys.index(jid) + 1]
else:
# Do not override assigned key
obj.contact.keyID = obj.keyID
obj.contact.contact_nickname = obj.contact_nickname
obj.contact.idle_time = obj.idle_time
if app.jid_is_transport(jid):
return
# It isn't an agent
# (when contact signs out or has errors)
if obj.show in ('offline', 'error'):
# TODO: This causes problems when another
# resource signs off!
self.stop_all_active_file_transfers(obj.contact)
if app.config.get('log_contact_status_changes') and \
app.config.should_log(self.name, obj.jid):
show = app.logger.convert_show_values_to_db_api_values(obj.show)
if show is not None:
app.logger.insert_into_logs(self.name,
nbxmpp.JID(obj.jid).getStripped(),
time.time(),
KindConstant.STATUS,
message=obj.status,
show=show)
def _check_for_mam_compliance(self, room_jid, stanza_id):
namespace = muc_caps_cache.get_mam_namespace(room_jid)
if stanza_id is None and namespace == nbxmpp.NS_MAM_2:

View File

@ -144,105 +144,8 @@ class StreamConflictReceivedEvent(nec.NetworkIncomingEvent):
self.conn = self.base_event.conn
return True
class PresenceHelperEvent:
def _generate_show(self):
self.show = self.stanza.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'
def _generate_ptype(self):
self.ptype = self.stanza.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
class PresenceReceivedEvent(nec.NetworkIncomingEvent, HelperEvent,
PresenceHelperEvent):
class PresenceReceivedEvent(nec.NetworkIncomingEvent):
name = 'presence-received'
base_network_events = ['raw-pres-received']
def _generate_keyID(self, sig_tag):
self.keyID = ''
if sig_tag and self.conn.USE_GPG and self.ptype != 'error':
# error presences contain our own signature
# verify
sig_msg = sig_tag.getData()
self.keyID = self.conn.gpg.verify(self.status, sig_msg)
self.keyID = helpers.prepare_and_validate_gpg_keyID(self.conn.name,
self.jid,
self.keyID)
def _generate_prio(self):
self.prio = self.stanza.getPriority()
try:
self.prio = int(self.prio)
except Exception:
self.prio = 0
def generate(self):
self.conn = self.base_event.conn
self.stanza = self.base_event.stanza
self.need_add_in_roster = False
self.need_redraw = False
self.popup = False # Do we want to open chat window ?
if not self.conn or self.conn.connected < 2:
log.debug('account is no more connected')
return
self._generate_ptype()
try:
self.get_jid_resource()
except Exception:
log.warning('Invalid JID: %s, ignoring it', self.stanza.getFrom())
return
jid_list = app.contacts.get_jid_list(self.conn.name)
self.timestamp = None
self.get_id()
self.avatar_sha = None
# XEP-0172 User Nickname
self.user_nick = self.stanza.getTagData('nick') or ''
self.contact_nickname = None
self.transport_auto_auth = False
# XEP-0203
self.timestamp = parse_delay(self.stanza)
if self.timestamp is None:
self.timestamp = time_time()
# XEP-0319
self.idle_time = parse_idle(self.stanza)
sig_tag = self.stanza.getTag('x', namespace=nbxmpp.NS_SIGNED)
self.status = self.stanza.getStatus() or ''
self._generate_show()
self._generate_prio()
self._generate_keyID(sig_tag)
self.errcode = self.stanza.getErrorCode()
self.errmsg = self.stanza.getErrorMsg()
if self.ptype == 'error':
return
if not self.ptype or self.ptype == 'unavailable':
our_jid = app.get_jid_from_account(self.conn.name)
if self.jid == our_jid and self.resource == self.conn.server_resource:
# We got our own presence
app.nec.push_incoming_event(OurShowEvent(None, conn=self.conn,
show=self.show))
elif self.jid in jid_list or self.jid == our_jid:
return True
class ZeroconfPresenceReceivedEvent(nec.NetworkIncomingEvent):
name = 'presence-received'
@ -254,16 +157,13 @@ class ZeroconfPresenceReceivedEvent(nec.NetworkIncomingEvent):
self.keyID = None
self.idle_time = None
self.timestamp = 0
self.contact_nickname = None
self.avatar_sha = None
self.need_add_in_roster = False
self.need_redraw = False
if self.show == 'offline':
self.ptype = 'unavailable'
else:
self.ptype = None
self.user_nick = ''
self.transport_auto_auth = False
self.errcode = None
self.errmsg = ''
self.popup = False # Do we want to open chat window ?

View File

@ -368,6 +368,9 @@ class LegacyContactsAPI:
def get_contact(self, account, jid, resource=None):
return self._accounts[account].contacts.get_contact(jid, resource=resource)
def get_contact_strict(self, account, jid, resource):
return self._accounts[account].contacts.get_contact_strict(jid, resource)
def get_avatar(self, account, *args, **kwargs):
return self._accounts[account].contacts.get_avatar(*args, **kwargs)
@ -553,7 +556,7 @@ class Contacts():
"""
Return the list of contact instances for this jid
"""
return self._contacts.get(jid, [])
return list(self._contacts.get(jid, []))
def get_contact(self, jid, resource=None):
### WARNING ###

View File

@ -15,13 +15,17 @@
# Presence handler
import logging
import time
import nbxmpp
from nbxmpp.structs import StanzaHandler
from nbxmpp.const import PresenceType
from gajim.common import app
from gajim.common.i18n import _
from gajim.common.nec import NetworkEvent
from gajim.common.modules.user_nickname import parse_nickname
from gajim.common.const import KindConstant
from gajim.common.helpers import prepare_and_validate_gpg_keyID
log = logging.getLogger('gajim.c.m.presence')
@ -32,11 +36,25 @@ class Presence:
self._account = con.name
self.handlers = [
('presence', self._presence_received),
('presence', self._subscribe_received, 'subscribe'),
('presence', self._subscribed_received, 'subscribed'),
('presence', self._unsubscribe_received, 'unsubscribe'),
('presence', self._unsubscribed_received, 'unsubscribed'),
StanzaHandler(name='presence',
callback=self._presence_received,
priority=50),
StanzaHandler(name='presence',
callback=self._subscribe_received,
typ='subscribe',
priority=49),
StanzaHandler(name='presence',
callback=self._subscribed_received,
typ='subscribed',
priority=49),
StanzaHandler(name='presence',
callback=self._unsubscribe_received,
typ='unsubscribe',
priority=49),
StanzaHandler(name='presence',
callback=self._unsubscribed_received,
typ='unsubscribed',
priority=49),
]
# keep the jids we auto added (transports contacts) to not send the
@ -46,32 +64,198 @@ class Presence:
# list of jid to auto-authorize
self.jids_for_auto_auth = []
def _presence_received(self, _con, stanza):
if stanza.getType() in ('subscribe', 'subscribed',
'unsubscribe', 'unsubscribed'):
# Dont handle that here
return
def _presence_received(self, _con, stanza, properties):
log.info('Received from %s', properties.jid)
log.info('Received from %s', stanza.getFrom())
if nbxmpp.isErrorNode(stanza):
log.info('Error:\n%s', stanza)
if properties.type == PresenceType.ERROR:
log.info('Error: %s %s', properties.jid, properties.error)
raise nbxmpp.NodeProcessed
if self._account == 'Local':
app.nec.push_incoming_event(
NetworkEvent('raw-pres-received',
conn=self._con,
stanza=stanza))
raise nbxmpp.NodeProcessed
if properties.is_self_presence:
app.nec.push_incoming_event(
NetworkEvent('our-show',
conn=self._con,
show=properties.show.value))
raise nbxmpp.NodeProcessed
contacts = app.contacts.get_jid_list(self._account)
if properties.jid.getBare() not in contacts and not properties.is_self_bare:
# Handle only presence from roster contacts
log.warning('Unkown presence received')
log.warning(stanza)
return
key_id = ''
if properties.signed is not None and self._con.USE_GPG:
key_id = self._con.gpg.verify(properties.status, properties.signed)
key_id = prepare_and_validate_gpg_keyID(
self._account, properties.jid.getBare(), key_id)
show = properties.show.value
if properties.type.is_unavailable:
show = 'offline'
event_attrs = {
'conn': self._con,
'stanza': stanza,
'keyID': key_id,
'prio': properties.priority,
'need_add_in_roster': False,
'popup': False,
'ptype': properties.type.value,
'jid': properties.jid.getBare(),
'resource': properties.jid.getResource(),
'id_': properties.id,
'fjid': str(properties.jid),
'timestamp': properties.timestamp,
'avatar_sha': properties.avatar_sha,
'user_nick': properties.nickname,
'idle_time': properties.idle_timestamp,
'show': show,
'new_show': show,
'old_show': 0,
'status': properties.status,
'contact_list': [],
'contact': None,
'need_add_in_roster': False,
}
event_ = NetworkEvent('presence-received', **event_attrs)
# TODO: Refactor
self._update_contact(event_, properties)
app.nec.push_incoming_event(event_)
raise nbxmpp.NodeProcessed
def _update_contact(self, event, properties):
jid = properties.jid.getBare()
resource = properties.jid.getResource()
status_strings = ['offline', 'error', 'online', 'chat', 'away',
'xa', 'dnd', 'invisible']
event.new_show = status_strings.index(event.show)
# Update contact
contact_list = app.contacts.get_contacts(self._account, jid)
if not contact_list:
log.warning('No contact found')
return
event.contact_list = contact_list
contact = app.contacts.get_contact_strict(self._account,
properties.jid.getBare(),
properties.jid.getResource())
if contact is None:
contact = app.contacts.get_first_contact_from_jid(self._account, jid)
if contact is None:
log.warning('First contact not found')
return
if self._is_resource_known(contact_list) and not app.jid_is_transport(jid):
# Another resource of an existing contact connected
# Add new contact
event.old_show = 0
contact = app.contacts.copy_contact(contact)
contact.resource = resource
app.contacts.add_contact(self._account, contact)
else:
# Convert the inital roster contact to a contact with resource
contact.resource = resource
event.old_show = status_strings.index(contact.show)
event.need_add_in_roster = True
elif contact.show in status_strings:
event.old_show = status_strings.index(contact.show)
# Update contact with presence data
contact.show = event.show
contact.status = properties.status
contact.priority = properties.priority
attached_keys = app.config.get_per('accounts', self._account,
'attached_gpg_keys').split()
if jid in attached_keys:
contact.keyID = attached_keys[attached_keys.index(jid) + 1]
else:
# Do not override assigned key
contact.keyID = event.keyID
contact.idle_time = properties.idle_timestamp
event.contact = contact
if not app.jid_is_transport(jid) and len(contact_list) == 1:
# It's not an agent
if event.old_show == 0 and event.new_show > 1:
if not jid in app.newly_added[self._account]:
app.newly_added[self._account].append(jid)
if jid in app.to_be_removed[self._account]:
app.to_be_removed[self._account].remove(jid)
elif event.old_show > 1 and event.new_show == 0 and \
self._con.connected > 1:
if not jid in app.to_be_removed[self._account]:
app.to_be_removed[self._account].append(jid)
if jid in app.newly_added[self._account]:
app.newly_added[self._account].remove(jid)
if app.jid_is_transport(jid):
return
if properties.type.is_unavailable:
# TODO: This causes problems when another
# resource signs off!
self._con.stop_all_active_file_transfers(contact)
self._log_presence(properties)
@staticmethod
def _is_resource_known(contact_list):
if len(contact_list) > 1:
return True
if contact_list[0].resource == '':
return False
return contact_list[0].show not in ('not in roster', 'offline')
def _log_presence(self, properties):
if not app.config.get('log_contact_status_changes'):
return
if not app.config.should_log(self._account, properties.jid.getBare()):
return
# TODO: Refactor
if properties.type.is_unavailable:
show = 'offline'
else:
show = properties.show.value
app.logger.insert_into_logs(self._account,
properties.jid.getBare(),
time.time(),
KindConstant.STATUS,
message=properties.status,
show=show)
def _subscribe_received(self, _con, _stanza, properties):
jid = properties.jid.getBare()
fjid = str(properties.jid)
def _subscribe_received(self, _con, stanza):
from_ = stanza.getFrom()
jid = from_.getStripped()
fjid = str(from_)
status = stanza.getStatus()
is_transport = app.jid_is_transport(fjid)
auto_auth = app.config.get_per('accounts', self._account, 'autoauth')
user_nick = parse_nickname(stanza)
log.info('Received Subscribe: %s, transport: %s, auto_auth: %s, '
'user_nick: %s', from_, is_transport, auto_auth, user_nick)
log.info('Received Subscribe: %s, transport: %s, '
'auto_auth: %s, user_nick: %s',
properties.jid, is_transport, auto_auth, properties.nickname)
if is_transport and fjid in self._con.agent_registrations:
self._con.agent_registrations[fjid]['sub_received'] = True
if not self._con.agent_registrations[fjid]['roster_push']:
@ -81,8 +265,8 @@ class Presence:
if auto_auth or is_transport or jid in self.jids_for_auto_auth:
self.send_presence(fjid, 'subscribed')
if not status:
status = _('I would like to add you to my roster.')
status = (properties.status or
_('I would like to add you to my roster.'))
app.nec.push_incoming_event(NetworkEvent(
'subscribe-presence-received',
@ -90,16 +274,15 @@ class Presence:
jid=jid,
fjid=fjid,
status=status,
user_nick=user_nick,
user_nick=properties.nickname,
is_transport=is_transport))
raise nbxmpp.NodeProcessed
def _subscribed_received(self, _con, stanza):
from_ = stanza.getFrom()
jid = from_.getStripped()
resource = from_.getResource()
log.info('Received Subscribed: %s', from_)
def _subscribed_received(self, _con, _stanza, properties):
jid = properties.jid.getBare()
resource = properties.jid.getResource()
log.info('Received Subscribed: %s', properties.jid)
if jid in self.automatically_added:
self.automatically_added.remove(jid)
raise nbxmpp.NodeProcessed
@ -110,17 +293,15 @@ class Presence:
raise nbxmpp.NodeProcessed
@staticmethod
def _unsubscribe_received(_con, stanza):
log.info('Received Unsubscribe: %s', stanza.getFrom())
def _unsubscribe_received(_con, _stanza, properties):
log.info('Received Unsubscribe: %s', properties.jid)
raise nbxmpp.NodeProcessed
def _unsubscribed_received(self, _con, stanza):
from_ = stanza.getFrom()
jid = from_.getStripped()
log.info('Received Unsubscribed: %s', from_)
def _unsubscribed_received(self, _con, _stanza, properties):
log.info('Received Unsubscribed: %s', properties.jid)
app.nec.push_incoming_event(NetworkEvent(
'unsubscribed-presence-received',
conn=self._con, jid=jid))
conn=self._con, jid=properties.jid.getBare()))
raise nbxmpp.NodeProcessed
def subscribed(self, jid):

View File

@ -152,7 +152,7 @@ class ConnectionZeroconf(CommonConnection, ConnectionHandlersZeroconf):
def _on_remove_service(self, jid):
self.roster.delItem(jid)
# 'NOTIFY' (account, (jid, status, status message, resource, priority,
# keyID, timestamp, contact_nickname))
# keyID, timestamp))
app.nec.push_incoming_event(ZeroconfPresenceReceivedEvent(
None, conn=self, fjid=jid, show='offline', status=''))

View File

@ -331,7 +331,7 @@ class Interface:
def handle_event_presence(self, obj):
# 'NOTIFY' (account, (jid, status, status message, resource,
# priority, # keyID, timestamp, contact_nickname))
# priority, # keyID, timestamp))
#
# Contact changed show
account = obj.conn.name

View File

@ -366,8 +366,7 @@ class GajimRemote(Server):
else:
return
self.raise_signal(event, (obj.conn.name, [obj.jid, obj.show,
obj.status, obj.resource, obj.prio, obj.keyID, obj.timestamp,
obj.contact_nickname]))
obj.status, obj.resource, obj.prio, obj.keyID, obj.timestamp]))
def on_subscribe_presence_received(self, obj):
self.raise_signal('Subscribe', (obj.conn.name, [obj.jid, obj.status,

View File

@ -720,14 +720,7 @@ class RosterWindow:
return
if jid == app.get_jid_from_account(account):
show_self_contact = app.config.get('show_self_contact')
if show_self_contact == 'never':
return
if (contact.resource != app.connections[account].server_resource \
and show_self_contact == 'when_other_resource') or \
show_self_contact == 'always':
return self._add_self_contact(account)
return
is_observer = contact.is_observer()
if is_observer:
@ -1871,7 +1864,7 @@ class RosterWindow:
'contacts': {}}
if account not in app.groups:
app.groups[account] = {}
if app.config.get('show_self_contact') == 'always':
self_jid = app.get_jid_from_account(account)
if app.connections[account].server_resource:
self_jid += '/' + app.connections[account].server_resource
@ -2212,9 +2205,6 @@ class RosterWindow:
elif contact.jid == app.get_jid_from_account(account) and \
show in ('offline', 'error'):
if app.config.get('show_self_contact') != 'never':
# SelfContact went offline. Remove him when last pending
# message was read
self.remove_contact(contact.jid, account, backend=True)
uf_show = helpers.get_uf_show(show)
@ -2249,7 +2239,6 @@ class RosterWindow:
if account not in app.contacts.get_accounts():
return
child_iterA = self._get_account_iter(account, self.model)
if app.config.get('show_self_contact') == 'always':
self_resource = app.connections[account].server_resource
self_contact = app.contacts.get_contact(account,
app.get_jid_from_account(account), resource=self_resource)
@ -2570,7 +2559,6 @@ class RosterWindow:
GLib.timeout_add_seconds(5, self.remove_to_be_removed,
jid, account)
if obj.need_redraw:
self.draw_contact(jid, account)
if app.jid_is_transport(jid) and jid in jid_list:
@ -2579,7 +2567,7 @@ class RosterWindow:
self.draw_contact(jid, account)
self.draw_group(_('Transports'), account)
if obj.contact and obj.need_redraw:
if obj.contact:
self.chg_contact_status(obj.contact, obj.show, obj.status, account)
if obj.popup:
@ -2598,7 +2586,6 @@ class RosterWindow:
self.fire_up_unread_messages_events(obj.conn.name)
else:
# add self contact
if app.config.get('show_self_contact') == 'always':
account = obj.conn.name
self_jid = app.get_jid_from_account(account)
if self_jid not in app.contacts.get_jid_list(account):
@ -2612,6 +2599,7 @@ class RosterWindow:
ask='none', resource=resource, avatar_sha=sha)
app.contacts.add_contact(account, contact)
self.add_contact(self_jid, account)
if app.config.get('remember_opened_chat_controls'):
account = obj.conn.name
controls = app.config.get_per(