Refactor VCard code into own modules

This commit is contained in:
Philipp Hörist 2018-06-30 19:23:10 +02:00
parent 71a82b5c3e
commit 8b800f4646
14 changed files with 537 additions and 463 deletions

View File

@ -397,6 +397,9 @@ def account_is_connected(account):
else: else:
return False return False
def is_invisible(account):
return SHOW_LIST[connections[account].connected] == 'invisible'
def account_is_disconnected(account): def account_is_disconnected(account):
return not account_is_connected(account) return not account_is_connected(account)

View File

@ -71,6 +71,8 @@ from gajim.common.modules.annotations import Annotations
from gajim.common.modules.roster_item_exchange import RosterItemExchange from gajim.common.modules.roster_item_exchange import RosterItemExchange
from gajim.common.modules.last_activity import LastActivity from gajim.common.modules.last_activity import LastActivity
from gajim.common.modules.http_auth import HTTPAuth from gajim.common.modules.http_auth import HTTPAuth
from gajim.common.modules.vcard_temp import VCardTemp
from gajim.common.modules.vcard_avatars import VCardAvatars
from gajim.common.connection_handlers import * from gajim.common.connection_handlers import *
from gajim.common.contacts import GC_Contact from gajim.common.contacts import GC_Contact
from gajim.gtkgui_helpers import get_action from gajim.gtkgui_helpers import get_action
@ -666,6 +668,8 @@ class Connection(CommonConnection, ConnectionHandlers):
self.register_module('RosterItemExchange', RosterItemExchange, self) self.register_module('RosterItemExchange', RosterItemExchange, self)
self.register_module('LastActivity', LastActivity, self) self.register_module('LastActivity', LastActivity, self)
self.register_module('HTTPAuth', HTTPAuth, self) self.register_module('HTTPAuth', HTTPAuth, self)
self.register_module('VCardTemp', VCardTemp, self)
self.register_module('VCardAvatars', VCardAvatars, self)
app.ged.register_event_handler('privacy-list-received', ged.CORE, app.ged.register_event_handler('privacy-list-received', ged.CORE,
self._nec_privacy_list_received) self._nec_privacy_list_received)
@ -756,7 +760,7 @@ class Connection(CommonConnection, ConnectionHandlers):
self.connected = 0 self.connected = 0
self.time_to_reconnect = None self.time_to_reconnect = None
self.privacy_rules_supported = False self.privacy_rules_supported = False
self.avatar_presence_sent = False self.get_module('VCardAvatars').avatar_advertised = False
if on_purpose: if on_purpose:
self.sm = Smacks(self) self.sm = Smacks(self)
if self.connection: if self.connection:
@ -1768,7 +1772,7 @@ class Connection(CommonConnection, ConnectionHandlers):
show='invisible')) show='invisible'))
if initial: if initial:
# ask our VCard # ask our VCard
self.request_vcard(self._on_own_avatar_received) self.get_module('VCardTemp').request_vcard()
# Get bookmarks from private namespace # Get bookmarks from private namespace
self.get_bookmarks() self.get_bookmarks()

View File

@ -283,388 +283,6 @@ class ConnectionDisco:
app.nec.push_incoming_event(AgentInfoReceivedEvent(None, conn=self, app.nec.push_incoming_event(AgentInfoReceivedEvent(None, conn=self,
stanza=iq_obj)) stanza=iq_obj))
class ConnectionVcard:
def __init__(self):
self.own_vcard = None
self.room_jids = []
self.avatar_presence_sent = False
self._requested_shas = {}
app.ged.register_event_handler('presence-received', ged.GUI2,
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:
return
if obj.avatar_sha is None:
# 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)
current_sha = app.config.get_per(
'accounts', self.name, 'avatar_sha')
if obj.avatar_sha != current_sha:
app.log('avatar').info(
'Request (vCard): %s', obj.jid)
self.request_vcard(self._on_own_avatar_received)
else:
app.log('avatar').info(
'Avatar already known (vCard): %s %s',
obj.jid, obj.avatar_sha)
return
if obj.avatar_sha == '':
# Empty <photo/> tag, means no avatar is advertised
app.log('avatar').info(
'%s has no avatar published (vCard)', obj.jid)
# Remove avatar
app.log('avatar').debug('Remove: %s', obj.jid)
app.contacts.set_avatar(self.name, obj.jid, None)
own_jid = self.get_own_jid().getStripped()
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(
'Avatar already known (vCard): %s %s',
obj.jid, obj.avatar_sha)
return
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)
elif obj.jid not in self._requested_shas:
app.log('avatar').info(
'Request (vCard): %s', obj.jid)
self._requested_shas[obj.jid] = obj.avatar_sha
self.request_vcard(self._on_room_avatar_received, obj.jid)
return
if obj.jid not in self._requested_shas:
app.log('avatar').info(
'Request (vCard): %s', obj.jid)
self._requested_shas[obj.jid] = obj.avatar_sha
self.request_vcard(self._on_avatar_received, obj.jid)
def _vcard_gc_presence_received(self, obj):
if obj.conn.name != self.name:
return
server = app.get_server_from_jid(obj.room_jid)
if server.startswith('irc') or obj.avatar_sha is None:
return
if obj.show == 'offline':
return
gc_contact = app.contacts.get_gc_contact(
self.name, obj.room_jid, obj.nick)
if gc_contact is None:
app.log('avatar').error('no gc contact found: %s', obj.nick)
return
if obj.avatar_sha == '':
# Empty <photo/> tag, means no avatar is advertised, remove avatar
app.log('avatar').info(
'%s has no avatar published (vCard)', obj.nick)
app.log('avatar').debug('Remove: %s', obj.nick)
gc_contact.avatar_sha = None
app.interface.update_avatar(contact=gc_contact)
else:
app.log('avatar').info(
'Update (vCard): %s %s', obj.nick, obj.avatar_sha)
path = os.path.join(configpaths.get('AVATAR'), obj.avatar_sha)
if not os.path.isfile(path):
if obj.fjid not in self._requested_shas:
app.log('avatar').info(
'Request (vCard): %s', obj.nick)
self._requested_shas[obj.fjid] = obj.avatar_sha
obj.conn.request_vcard(
self._on_avatar_received, obj.fjid, room=True)
return
if gc_contact.avatar_sha != obj.avatar_sha:
app.log('avatar').info(
'%s changed his Avatar (vCard): %s',
obj.nick, obj.avatar_sha)
gc_contact.avatar_sha = obj.avatar_sha
app.interface.update_avatar(contact=gc_contact)
else:
app.log('avatar').info(
'Avatar already known (vCard): %s', obj.nick)
def send_avatar_presence(self):
show = helpers.get_xmpp_show(app.SHOW_LIST[self.connected])
p = nbxmpp.Presence(typ=None, priority=self.priority,
show=show, status=self.status)
p = self.add_sha(p)
self.connection.send(p)
app.interface.update_avatar(self.name, self.get_own_jid().getStripped())
def _node_to_dict(self, node):
dict_ = {}
for info in node.getChildren():
name = info.getName()
if name in ('ADR', 'TEL', 'EMAIL'): # we can have several
dict_.setdefault(name, [])
entry = {}
for c in info.getChildren():
entry[c.getName()] = c.getData()
dict_[name].append(entry)
elif info.getChildren() == []:
dict_[name] = info.getData()
else:
dict_[name] = {}
for c in info.getChildren():
dict_[name][c.getName()] = c.getData()
return dict_
def request_vcard(self, callback, jid=None, room=False):
"""
Request the VCARD
"""
if not self.connection or self.connected < 2:
return
if room:
room_jid = app.get_room_from_fjid(jid)
if room_jid not in self.room_jids:
self.room_jids.append(room_jid)
iq = nbxmpp.Iq(typ='get')
if jid:
iq.setTo(jid)
iq.setQuery('vCard').setNamespace(nbxmpp.NS_VCARD)
self.connection.SendAndCallForResponse(
iq, self._parse_vcard, {'callback': callback})
def send_vcard(self, vcard, sha):
if not self.connection or self.connected < 2:
return
iq = nbxmpp.Iq(typ='set')
iq2 = iq.setTag(nbxmpp.NS_VCARD + ' vCard')
for i in vcard:
if i == 'jid':
continue
if isinstance(vcard[i], dict):
iq3 = iq2.addChild(i)
for j in vcard[i]:
iq3.addChild(j).setData(vcard[i][j])
elif isinstance(vcard[i], list):
for j in vcard[i]:
iq3 = iq2.addChild(i)
for k in j:
iq3.addChild(k).setData(j[k])
else:
iq2.addChild(i).setData(vcard[i])
self.connection.SendAndCallForResponse(
iq, self._avatar_publish_result, {'sha': sha})
def upload_room_avatar(self, room_jid, data):
iq = nbxmpp.Iq(typ='set', to=room_jid)
vcard = iq.addChild('vCard', namespace=nbxmpp.NS_VCARD)
photo = vcard.addChild('PHOTO')
photo.addChild('TYPE', payload='image/png')
photo.addChild('BINVAL', payload=data)
self.connection.SendAndCallForResponse(
iq, self._upload_room_avatar_result)
def _upload_room_avatar_result(self, stanza):
if not nbxmpp.isResultNode(stanza):
reason = stanza.getErrorMsg() or stanza.getError()
app.nec.push_incoming_event(InformationEvent(
None, dialog_name='avatar-upload-error', args=reason))
def _avatar_publish_result(self, con, stanza, sha):
if stanza.getType() == 'result':
current_sha = app.config.get_per(
'accounts', self.name, 'avatar_sha')
if (current_sha != sha and
app.SHOW_LIST[self.connected] != 'invisible'):
if not self.connection or self.connected < 2:
return
app.config.set_per(
'accounts', self.name, 'avatar_sha', sha or '')
own_jid = self.get_own_jid().getStripped()
app.contacts.set_avatar(self.name, own_jid, sha)
self.send_avatar_presence()
app.log('avatar').info('%s: Published: %s', self.name, sha)
app.nec.push_incoming_event(
VcardPublishedEvent(None, conn=self))
elif stanza.getType() == 'error':
app.nec.push_incoming_event(
VcardNotPublishedEvent(None, conn=self))
def _get_vcard_photo(self, vcard, jid):
try:
photo = vcard['PHOTO']['BINVAL']
except (KeyError, AttributeError, TypeError):
avatar_sha = None
photo_decoded = None
else:
if photo == '':
avatar_sha = None
photo_decoded = None
else:
try:
photo_decoded = base64.b64decode(photo.encode('utf-8'))
except binascii.Error as error:
app.log('avatar').warning('Invalid avatar for %s: %s',
jid, error)
return None, None
avatar_sha = hashlib.sha1(photo_decoded).hexdigest()
return avatar_sha, photo_decoded
def _parse_vcard(self, con, stanza, callback):
frm_jid = stanza.getFrom()
room = False
if frm_jid is None:
frm_jid = self.get_own_jid()
elif frm_jid.getStripped() in self.room_jids:
room = True
resource = frm_jid.getResource()
jid = frm_jid.getStripped()
stanza_error = stanza.getError()
if stanza_error in ('service-unavailable', 'item-not-found',
'not-allowed'):
app.log('avatar').info('vCard not available: %s %s',
frm_jid, stanza_error)
callback(jid, resource, room, {})
return
vcard_node = stanza.getTag('vCard', namespace=nbxmpp.NS_VCARD)
if vcard_node is None:
app.log('avatar').info('vCard not available: %s', frm_jid)
app.log('avatar').debug(stanza)
return
vcard = self._node_to_dict(vcard_node)
if self.get_own_jid().bareMatch(jid):
if 'NICKNAME' in vcard:
app.nicks[self.name] = vcard['NICKNAME']
elif 'FN' in vcard:
app.nicks[self.name] = vcard['FN']
app.nec.push_incoming_event(
VcardReceivedEvent(None, conn=self, vcard_dict=vcard))
callback(jid, resource, room, vcard)
def _on_own_avatar_received(self, jid, resource, room, vcard):
avatar_sha, photo_decoded = self._get_vcard_photo(vcard, jid)
app.log('avatar').info(
'Received own (vCard): %s', avatar_sha)
self.own_vcard = vcard
if avatar_sha is None:
app.log('avatar').info('No avatar found (vCard)')
app.config.set_per('accounts', self.name, 'avatar_sha', '')
self.send_avatar_presence()
return
current_sha = app.config.get_per('accounts', self.name, 'avatar_sha')
if current_sha == avatar_sha:
path = os.path.join(configpaths.get('AVATAR'), current_sha)
if not os.path.isfile(path):
app.log('avatar').info(
'Caching (vCard): %s', current_sha)
app.interface.save_avatar(photo_decoded)
if self.avatar_presence_sent:
app.log('avatar').debug('Avatar already advertised')
return
else:
app.interface.save_avatar(photo_decoded)
app.config.set_per('accounts', self.name, 'avatar_sha', avatar_sha)
if app.SHOW_LIST[self.connected] == 'invisible':
app.log('avatar').info(
'We are invisible, not publishing avatar')
return
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)
expected_avatar_sha = self._requested_shas[jid]
if expected_avatar_sha != avatar_sha:
app.log('avatar').warning(
'Avatar mismatch (vCard): %s %s', jid, avatar_sha)
return
app.interface.save_avatar(photo_decoded)
self._requested_shas.pop(jid)
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
"""
request_jid = jid
if room:
request_jid = '%s/%s' % (jid, resource)
avatar_sha, photo_decoded = self._get_vcard_photo(vcard, request_jid)
expected_avatar_sha = self._requested_shas[request_jid]
if expected_avatar_sha != avatar_sha:
app.log('avatar').warning(
'Avatar mismatch (vCard): %s %s', request_jid, avatar_sha)
return
app.interface.save_avatar(photo_decoded)
self._requested_shas.pop(request_jid)
# Received vCard from a contact
if room:
app.log('avatar').info(
'Received (vCard): %s %s', resource, avatar_sha)
contact = app.contacts.get_gc_contact(self.name, jid, resource)
if contact is not None:
contact.avatar_sha = avatar_sha
app.interface.update_avatar(contact=contact)
else:
app.log('avatar').info('Received (vCard): %s %s', jid, avatar_sha)
own_jid = self.get_own_jid().getStripped()
app.logger.set_avatar_sha(own_jid, jid, avatar_sha)
app.contacts.set_avatar(self.name, jid, avatar_sha)
app.interface.update_avatar(self.name, jid)
class ConnectionPEP(object): class ConnectionPEP(object):
@ -1296,13 +914,12 @@ class ConnectionHandlersBase:
return sess return sess
class ConnectionHandlers(ConnectionArchive313, class ConnectionHandlers(ConnectionArchive313,
ConnectionVcard, ConnectionSocks5Bytestream, ConnectionDisco, ConnectionSocks5Bytestream, ConnectionDisco,
ConnectionCommands, ConnectionPubSub, ConnectionPEP, ConnectionCaps, ConnectionCommands, ConnectionPubSub, ConnectionPEP, ConnectionCaps,
ConnectionHandlersBase, ConnectionJingle, ConnectionIBBytestream, ConnectionHandlersBase, ConnectionJingle, ConnectionIBBytestream,
ConnectionHTTPUpload): ConnectionHTTPUpload):
def __init__(self): def __init__(self):
ConnectionArchive313.__init__(self) ConnectionArchive313.__init__(self)
ConnectionVcard.__init__(self)
ConnectionSocks5Bytestream.__init__(self) ConnectionSocks5Bytestream.__init__(self)
ConnectionIBBytestream.__init__(self) ConnectionIBBytestream.__init__(self)
ConnectionCommands.__init__(self) ConnectionCommands.__init__(self)
@ -1398,11 +1015,7 @@ ConnectionHTTPUpload):
app.ged.remove_event_handler('blocking', ged.CORE, self._nec_blocking) app.ged.remove_event_handler('blocking', ged.CORE, self._nec_blocking)
def add_sha(self, p, send_caps=True): def add_sha(self, p, send_caps=True):
c = p.setTag('x', namespace=nbxmpp.NS_VCARD_UPDATE) p = self.get_module('VCardAvatars').add_update_node(p)
sha = app.config.get_per('accounts', self.name, 'avatar_sha')
app.log('avatar').info(
'%s: Send avatar presence: %s', self.name, sha or 'empty')
c.setTagData('photo', sha)
if send_caps: if send_caps:
return self._add_caps(p) return self._add_caps(p)
return p return p
@ -1925,7 +1538,7 @@ ConnectionHTTPUpload):
show=show)) show=show))
if self.vcard_supported: if self.vcard_supported:
# ask our VCard # ask our VCard
self.request_vcard(self._on_own_avatar_received) self.get_module('VCardTemp').request_vcard()
# Get bookmarks from private namespace # Get bookmarks from private namespace
self.get_bookmarks() self.get_bookmarks()

View File

@ -550,16 +550,6 @@ PresenceHelperEvent):
tim = helpers.datetime_tuple(time_str) tim = helpers.datetime_tuple(time_str)
self.idle_time = timegm(tim) 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') xtags = self.stanza.getTags('x')
for x in xtags: for x in xtags:
namespace = x.getNamespace() namespace = x.getNamespace()
@ -567,9 +557,6 @@ PresenceHelperEvent):
self.is_gc = True self.is_gc = True
elif namespace == nbxmpp.NS_SIGNED: elif namespace == nbxmpp.NS_SIGNED:
sig_tag = x sig_tag = x
elif namespace == nbxmpp.NS_VCARD_UPDATE:
self.avatar_sha = x.getTagData('photo')
self.contact_nickname = x.getTagData('nickname')
elif namespace == nbxmpp.NS_DELAY and not self.timestamp: elif namespace == nbxmpp.NS_DELAY and not self.timestamp:
# XEP-0091 # XEP-0091
self._generate_timestamp(self.stanza.timestamp) self._generate_timestamp(self.stanza.timestamp)
@ -1770,14 +1757,6 @@ class ConnectionTypeEvent(nec.NetworkIncomingEvent):
name = 'connection-type' name = 'connection-type'
base_network_events = [] base_network_events = []
class VcardPublishedEvent(nec.NetworkIncomingEvent):
name = 'vcard-published'
base_network_events = []
class VcardNotPublishedEvent(nec.NetworkIncomingEvent):
name = 'vcard-not-published'
base_network_events = []
class StanzaReceivedEvent(nec.NetworkIncomingEvent): class StanzaReceivedEvent(nec.NetworkIncomingEvent):
name = 'stanza-received' name = 'stanza-received'
base_network_events = [] base_network_events = []
@ -1967,13 +1946,6 @@ class NonAnonymousServerErrorEvent(nec.NetworkIncomingEvent):
name = 'non-anonymous-server-error' name = 'non-anonymous-server-error'
base_network_events = [] base_network_events = []
class VcardReceivedEvent(nec.NetworkIncomingEvent):
name = 'vcard-received'
base_network_events = []
def generate(self):
return True
class UpdateGCAvatarEvent(nec.NetworkIncomingEvent): class UpdateGCAvatarEvent(nec.NetworkIncomingEvent):
name = 'update-gc-avatar' name = 'update-gc-avatar'
base_network_events = [] base_network_events = []
@ -1995,19 +1967,6 @@ class UpdateRoomAvatarEvent(nec.NetworkIncomingEvent):
def generate(self): def generate(self):
return True 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:
app.log('avatar').info(
'%s has no avatar published (vCard)', self.jid)
return
self.avatar_sha = vcard.getTagData('photo')
return True
class PEPConfigReceivedEvent(nec.NetworkIncomingEvent): class PEPConfigReceivedEvent(nec.NetworkIncomingEvent):
name = 'pep-config-received' name = 'pep-config-received'
base_network_events = [] base_network_events = []

View File

@ -113,6 +113,12 @@ class IdleState(IntEnum):
AWAY = 2 AWAY = 2
AWAKE = 3 AWAKE = 3
@unique
class RequestAvatar(IntEnum):
SELF = 0
ROOM = 1
USER = 2
SSLError = { SSLError = {
2: _("Unable to get issuer certificate"), 2: _("Unable to get issuer certificate"),
3: _("Unable to get certificate CRL"), 3: _("Unable to get certificate CRL"),

View File

@ -1398,15 +1398,7 @@ def get_subscription_request_msg(account=None):
s = _('I would like to add you to my contact list.') s = _('I would like to add you to my contact list.')
if account: if account:
s = _('Hello, I am $name.') + ' ' + s s = _('Hello, I am $name.') + ' ' + s
our_jid = app.get_jid_from_account(account) name = app.connections[account].get_module('VCardTemp').get_vard_name()
vcard = app.connections[account].own_vcard
name = ''
if vcard:
if 'N' in vcard:
if 'GIVEN' in vcard['N'] and 'FAMILY' in vcard['N']:
name = vcard['N']['GIVEN'] + ' ' + vcard['N']['FAMILY']
if not name and 'FN' in vcard:
name = vcard['FN']
nick = app.nicks[account] nick = app.nicks[account]
if name and nick: if name and nick:
name += ' (%s)' % nick name += ' (%s)' % nick

View File

@ -0,0 +1,192 @@
# This file is part of Gajim.
#
# Gajim is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published
# by the Free Software Foundation; version 3 only.
#
# Gajim is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
# XEP-0153: vCard-Based Avatars
import os
import logging
import nbxmpp
from gajim.common import app
from gajim.common import helpers
from gajim.common import configpaths
from gajim.common.const import RequestAvatar
log = logging.getLogger('gajim.c.m.vcard.avatars')
class VCardAvatars:
def __init__(self, con):
self._con = con
self._account = con.name
self._requested_shas = []
self.handlers = [
('presence', self._presence_received, '', nbxmpp.NS_VCARD_UPDATE),
]
self.avatar_advertised = False
def _presence_received(self, con, stanza):
update = stanza.getTag('x', namespace=nbxmpp.NS_VCARD_UPDATE)
if update is None:
return
jid = stanza.getFrom()
avatar_sha = update.getTagData('photo')
if avatar_sha is None:
log.info('%s is not ready to promote an avatar', jid)
# Empty update element, ignore
return
if self._con.get_own_jid().bareMatch(jid):
if self._con.get_own_jid() == jid:
# Reflection of our own presence
return
self._self_update_received(jid, avatar_sha)
return
# Check if presence is from a MUC service
contact = app.contacts.get_groupchat_contact(self._account, str(jid))
if contact is not None:
self._update_received(jid, avatar_sha)
elif stanza.getTag('x', namespace=nbxmpp.NS_MUC_USER):
show = stanza.getShow()
type_ = stanza.getType()
self._gc_update_received(jid, avatar_sha, show, type_)
else:
self._update_received(jid, avatar_sha)
def _self_update_received(self, jid, avatar_sha):
jid = jid.getStripped()
full_jid = jid
if avatar_sha == '':
# Empty <photo/> tag, means no avatar is advertised
log.info('%s has no avatar published', full_jid)
return
log.info('Update: %s %s', jid, avatar_sha)
current_sha = app.config.get_per(
'accounts', self._account, 'avatar_sha')
if avatar_sha != current_sha:
log.info('Request : %s', jid)
self._con.get_module('VCardTemp').request_vcard(RequestAvatar.SELF)
else:
log.info('Avatar already known: %s %s',
jid, avatar_sha)
def _update_received(self, jid, avatar_sha, room=False):
jid = jid.getStripped()
full_jid = jid
if avatar_sha == '':
# Empty <photo/> tag, means no avatar is advertised
log.info('%s has no avatar published', full_jid)
# Remove avatar
log.debug('Remove: %s', jid)
app.contacts.set_avatar(self._account, jid, None)
acc_jid = self._con.get_own_jid().getStripped()
if not room:
app.logger.set_avatar_sha(acc_jid, jid, None)
app.interface.update_avatar(
self._account, jid, room_avatar=room)
else:
log.info('Update: %s %s', full_jid, avatar_sha)
current_sha = app.contacts.get_avatar_sha(self._account, jid)
if avatar_sha == current_sha:
log.info('Avatar already known: %s %s', jid, avatar_sha)
return
if room:
# We dont save the room avatar hash in our DB, so check
# if we previously downloaded it
if app.interface.avatar_exists(avatar_sha):
app.contacts.set_avatar(self._account, jid, avatar_sha)
app.interface.update_avatar(
self._account, jid, room_avatar=room)
return
if avatar_sha not in self._requested_shas:
self._requested_shas.append(avatar_sha)
if room:
self._con.get_module('VCardTemp').request_vcard(
RequestAvatar.ROOM, jid, sha=avatar_sha)
else:
self._con.get_module('VCardTemp').request_vcard(
RequestAvatar.USER, jid, sha=avatar_sha)
def _gc_update_received(self, jid, avatar_sha, show, type_):
if show == 'offline' or type_ == 'unavailable':
return
nick = jid.getResource()
gc_contact = app.contacts.get_gc_contact(
self._account, jid.getStripped(), nick)
if gc_contact is None:
log.error('no gc contact found: %s', nick)
return
if avatar_sha == '':
# Empty <photo/> tag, means no avatar is advertised, remove avatar
log.info('%s has no avatar published', nick)
log.debug('Remove: %s', nick)
gc_contact.avatar_sha = None
app.interface.update_avatar(contact=gc_contact)
else:
log.info('Update: %s %s', nick, avatar_sha)
path = os.path.join(configpaths.get('AVATAR'), avatar_sha)
if not os.path.isfile(path):
if avatar_sha not in self._requested_shas:
app.log('avatar').info('Request: %s', nick)
self._requested_shas.append(avatar_sha)
self._con.get_module('VCardTemp').request_vcard(
RequestAvatar.USER, str(jid),
room=True, sha=avatar_sha)
return
if gc_contact.avatar_sha != avatar_sha:
log.info('%s changed his Avatar: %s', nick, avatar_sha)
gc_contact.avatar_sha = avatar_sha
app.interface.update_avatar(contact=gc_contact)
else:
log.info('Avatar already known: %s', nick)
def send_avatar_presence(self, force=False):
if self.avatar_advertised and not force:
log.debug('Avatar already advertised')
return
show = helpers.get_xmpp_show(app.SHOW_LIST[self._con.connected])
pres = nbxmpp.Presence(typ=None, priority=self._con.priority,
show=show, status=self._con.status)
pres = self._con.add_sha(pres)
self._con.connection.send(pres)
self.avatar_advertised = True
app.interface.update_avatar(self._account,
self._con.get_own_jid().getStripped())
def add_update_node(self, node):
update = node.setTag('x', namespace=nbxmpp.NS_VCARD_UPDATE)
if self._con.get_module('VCardTemp').own_vcard_received:
sha = app.config.get_per('accounts', self._account, 'avatar_sha')
own_jid = self._con.get_own_jid()
log.info('Send avatar presence to: %s %s',
node.getTo() or own_jid, sha or 'no sha advertised')
update.setTagData('photo', sha)
return node

View File

@ -0,0 +1,308 @@
# This file is part of Gajim.
#
# Gajim is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published
# by the Free Software Foundation; version 3 only.
#
# Gajim is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
# XEP-0054: vcard-temp
import os
import hashlib
import binascii
import base64
import logging
import nbxmpp
from gajim.common import app
from gajim.common import configpaths
from gajim.common.const import RequestAvatar
from gajim.common.nec import NetworkIncomingEvent
from gajim.common.connection_handlers_events import InformationEvent
log = logging.getLogger('gajim.c.m.vcard')
class VCardTemp:
def __init__(self, con):
self._con = con
self._account = con.name
self.handlers = []
self._own_vcard = None
self.own_vcard_received = False
self.room_jids = []
def _node_to_dict(self, node):
dict_ = {}
for info in node.getChildren():
name = info.getName()
if name in ('ADR', 'TEL', 'EMAIL'): # we can have several
dict_.setdefault(name, [])
entry = {}
for c in info.getChildren():
entry[c.getName()] = c.getData()
dict_[name].append(entry)
elif info.getChildren() == []:
dict_[name] = info.getData()
else:
dict_[name] = {}
for c in info.getChildren():
dict_[name][c.getName()] = c.getData()
return dict_
def request_vcard(self, callback=RequestAvatar.SELF, jid=None,
room=False, sha=None):
if not app.account_is_connected(self._account):
return
if isinstance(callback, RequestAvatar):
if callback == RequestAvatar.SELF:
callback = self._on_own_avatar_received
elif callback == RequestAvatar.ROOM:
callback = self._on_room_avatar_received
elif callback == RequestAvatar.USER:
callback = self._on_avatar_received
if room:
room_jid = app.get_room_from_fjid(jid)
if room_jid not in self.room_jids:
self.room_jids.append(room_jid)
iq = nbxmpp.Iq(typ='get')
if jid:
iq.setTo(jid)
iq.setQuery('vCard').setNamespace(nbxmpp.NS_VCARD)
own_jid = self._con.get_own_jid().getStripped()
log.info('Request: %s, expected sha: %s', jid or own_jid, sha)
self._con.connection.SendAndCallForResponse(
iq, self._parse_vcard, {'callback': callback, 'expected_sha': sha})
def send_vcard(self, vcard, sha):
if not app.account_is_connected(self._account):
return
iq = nbxmpp.Iq(typ='set')
iq2 = iq.setTag(nbxmpp.NS_VCARD + ' vCard')
for i in vcard:
if i == 'jid':
continue
if isinstance(vcard[i], dict):
iq3 = iq2.addChild(i)
for j in vcard[i]:
iq3.addChild(j).setData(vcard[i][j])
elif isinstance(vcard[i], list):
for j in vcard[i]:
iq3 = iq2.addChild(i)
for k in j:
iq3.addChild(k).setData(j[k])
else:
iq2.addChild(i).setData(vcard[i])
log.info('Upload avatar: %s %s', self._account, sha)
self._con.connection.SendAndCallForResponse(
iq, self._avatar_publish_result, {'sha': sha})
def upload_room_avatar(self, room_jid, data):
iq = nbxmpp.Iq(typ='set', to=room_jid)
vcard = iq.addChild('vCard', namespace=nbxmpp.NS_VCARD)
photo = vcard.addChild('PHOTO')
photo.addChild('TYPE', payload='image/png')
photo.addChild('BINVAL', payload=data)
log.info('Upload avatar: %s %s', room_jid)
self._con.connection.SendAndCallForResponse(
iq, self._upload_room_avatar_result)
def _upload_room_avatar_result(self, stanza):
if not nbxmpp.isResultNode(stanza):
reason = stanza.getErrorMsg() or stanza.getError()
app.nec.push_incoming_event(InformationEvent(
None, dialog_name='avatar-upload-error', args=reason))
def _avatar_publish_result(self, con, stanza, sha):
if stanza.getType() == 'result':
current_sha = app.config.get_per(
'accounts', self._account, 'avatar_sha')
if (current_sha != sha and not app.is_invisible(self._account)):
if not app.account_is_connected(self._account):
return
app.config.set_per(
'accounts', self._account, 'avatar_sha', sha or '')
own_jid = self._con.get_own_jid().getStripped()
app.contacts.set_avatar(self._account, own_jid, sha)
self._con.get_module('VCardAvatars').send_avatar_presence(
force=True)
log.info('%s: Published: %s', self._account, sha)
app.nec.push_incoming_event(
VcardPublishedEvent(None, conn=self._con))
elif stanza.getType() == 'error':
app.nec.push_incoming_event(
VcardNotPublishedEvent(None, conn=self._con))
def _get_vcard_photo(self, vcard, jid):
try:
photo = vcard['PHOTO']['BINVAL']
except (KeyError, AttributeError, TypeError):
avatar_sha = None
photo_decoded = None
else:
if photo == '':
avatar_sha = None
photo_decoded = None
else:
try:
photo_decoded = base64.b64decode(photo.encode('utf-8'))
except binascii.Error as error:
log.warning('Invalid avatar for %s: %s', jid, error)
return None, None
avatar_sha = hashlib.sha1(photo_decoded).hexdigest()
return avatar_sha, photo_decoded
def _parse_vcard(self, con, stanza, callback, expected_sha):
frm_jid = stanza.getFrom()
room = False
if frm_jid is None:
frm_jid = self._con.get_own_jid()
elif frm_jid.getStripped() in self.room_jids:
room = True
resource = frm_jid.getResource()
jid = frm_jid.getStripped()
stanza_error = stanza.getError()
if stanza_error in ('service-unavailable', 'item-not-found',
'not-allowed'):
log.info('vCard not available: %s %s', frm_jid, stanza_error)
callback(jid, resource, room, {}, expected_sha)
return
vcard_node = stanza.getTag('vCard', namespace=nbxmpp.NS_VCARD)
if vcard_node is None:
log.info('vCard not available: %s', frm_jid)
log.debug(stanza)
return
vcard = self._node_to_dict(vcard_node)
if self._con.get_own_jid().bareMatch(jid):
if 'NICKNAME' in vcard:
app.nicks[self._account] = vcard['NICKNAME']
elif 'FN' in vcard:
app.nicks[self._account] = vcard['FN']
app.nec.push_incoming_event(
VcardReceivedEvent(None, conn=self._con, vcard_dict=vcard))
callback(jid, resource, room, vcard, expected_sha)
def _on_own_avatar_received(self, jid, resource, room, vcard, *args):
avatar_sha, photo_decoded = self._get_vcard_photo(vcard, jid)
log.info('Received own vcard, avatar sha is: %s', avatar_sha)
self._own_vcard = vcard
self.own_vcard_received = True
if avatar_sha is None:
log.info('No avatar found')
app.config.set_per('accounts', self._account, 'avatar_sha', '')
self._con.get_module('VCardAvatars').send_avatar_presence(force=True)
return
current_sha = app.config.get_per('accounts', self._account, 'avatar_sha')
if current_sha == avatar_sha:
path = os.path.join(configpaths.get('AVATAR'), current_sha)
if not os.path.isfile(path):
log.info('Caching: %s', current_sha)
app.interface.save_avatar(photo_decoded)
self._con.get_module('VCardAvatars').send_avatar_presence()
else:
app.interface.save_avatar(photo_decoded)
app.config.set_per('accounts', self._account, 'avatar_sha', avatar_sha)
if app.is_invisible(self._account):
log.info('We are invisible, not advertising avatar')
return
self._con.get_module('VCardAvatars').send_avatar_presence(force=True)
def _on_room_avatar_received(self, jid, resource, room, vcard,
expected_sha):
avatar_sha, photo_decoded = self._get_vcard_photo(vcard, jid)
if expected_sha != avatar_sha:
log.warning('Avatar mismatch: %s %s', jid, avatar_sha)
return
app.interface.save_avatar(photo_decoded)
log.info('Received: %s %s', jid, avatar_sha)
app.contacts.set_avatar(self._account, jid, avatar_sha)
app.interface.update_avatar(self._account, jid, room_avatar=True)
def _on_avatar_received(self, jid, resource, room, vcard, expected_sha):
request_jid = jid
if room:
request_jid = '%s/%s' % (jid, resource)
avatar_sha, photo_decoded = self._get_vcard_photo(vcard, request_jid)
if expected_sha != avatar_sha:
log.warning('Received: avatar mismatch: %s %s',
request_jid, avatar_sha)
return
app.interface.save_avatar(photo_decoded)
# Received vCard from a contact
if room:
log.info('Received: %s %s', resource, avatar_sha)
contact = app.contacts.get_gc_contact(self._account, jid, resource)
if contact is not None:
contact.avatar_sha = avatar_sha
app.interface.update_avatar(contact=contact)
else:
log.info('Received: %s %s', jid, avatar_sha)
own_jid = self._con.get_own_jid().getStripped()
app.logger.set_avatar_sha(own_jid, jid, avatar_sha)
app.contacts.set_avatar(self._account, jid, avatar_sha)
app.interface.update_avatar(self._account, jid)
def get_vard_name(self):
name = ''
vcard = self._own_vcard
if not vcard:
return name
if 'N' in vcard:
if 'GIVEN' in vcard['N'] and 'FAMILY' in vcard['N']:
name = vcard['N']['GIVEN'] + ' ' + vcard['N']['FAMILY']
if not name and 'FN' in vcard:
name = vcard['FN']
return name
class VcardPublishedEvent(NetworkIncomingEvent):
name = 'vcard-published'
base_network_events = []
class VcardNotPublishedEvent(NetworkIncomingEvent):
name = 'vcard-not-published'
base_network_events = []
class VcardReceivedEvent(NetworkIncomingEvent):
name = 'vcard-received'
base_network_events = []

View File

@ -40,19 +40,13 @@ AGENT_REMOVED = 'agent_removed'
from gajim.common import connection_handlers from gajim.common import connection_handlers
class ConnectionVcard(connection_handlers.ConnectionVcard): class ConnectionVcard:
def add_sha(self, p, *args): def add_sha(self, p, *args):
return p return p
def add_caps(self, p): def add_caps(self, p):
return p return p
def request_vcard(self, *args):
pass
def send_vcard(self, *args):
pass
class ConnectionHandlersZeroconf(ConnectionVcard, class ConnectionHandlersZeroconf(ConnectionVcard,
ConnectionSocks5BytestreamZeroconf, ConnectionCommands, ConnectionSocks5BytestreamZeroconf, ConnectionCommands,

View File

@ -762,8 +762,8 @@ class GroupchatControl(ChatControlBase):
publish = app.interface.get_avatar(sha, publish=True) publish = app.interface.get_avatar(sha, publish=True)
avatar = base64.b64encode(publish).decode('utf-8') avatar = base64.b64encode(publish).decode('utf-8')
con = app.connections[self.account]
app.connections[self.account].upload_room_avatar( con.get_module('VCardTemp').upload_room_avatar(
self.room_jid, avatar) self.room_jid, avatar)
AvatarChooserDialog(_on_accept, AvatarChooserDialog(_on_accept,

View File

@ -77,7 +77,7 @@ class ProfileWindow:
self._nec_vcard_not_published) self._nec_vcard_not_published)
self.window.show_all() self.window.show_all()
self.xml.get_object('ok_button').grab_focus() self.xml.get_object('ok_button').grab_focus()
app.connections[account].request_vcard( app.connections[account].get_module('VCardTemp').request_vcard(
self._nec_vcard_received, self.jid) self._nec_vcard_received, self.jid)
def on_information_notebook_switch_page(self, widget, page, page_num): def on_information_notebook_switch_page(self, widget, page, page_num):
@ -261,7 +261,7 @@ class ProfileWindow:
self.progressbar.set_fraction(0) self.progressbar.set_fraction(0)
self.update_progressbar_timeout_id = None self.update_progressbar_timeout_id = None
def _nec_vcard_received(self, jid, resource, room, vcard_): def _nec_vcard_received(self, jid, resource, room, vcard_, *args):
self.set_values(vcard_) self.set_values(vcard_)
def add_to_vcard(self, vcard_, entry, txt): def add_to_vcard(self, vcard_, entry, txt):
@ -339,7 +339,8 @@ class ProfileWindow:
app.connections[self.account].retract_nickname() app.connections[self.account].retract_nickname()
nick = app.config.get_per('accounts', self.account, 'name') nick = app.config.get_per('accounts', self.account, 'name')
app.nicks[self.account] = nick app.nicks[self.account] = nick
app.connections[self.account].send_vcard(vcard_, sha) app.connections[self.account].get_module('VCardTemp').send_vcard(
vcard_, sha)
self.message_id = self.statusbar.push(self.context_id, self.message_id = self.statusbar.push(self.context_id,
_('Sending profile…')) _('Sending profile…'))
self.progressbar.show() self.progressbar.show()

View File

@ -840,10 +840,12 @@ class SignalObject(dbus.service.Object):
if avatar_mime_type: if avatar_mime_type:
vcard['PHOTO']['TYPE'] = avatar_mime_type vcard['PHOTO']['TYPE'] = avatar_mime_type
if account: if account:
app.connections[account].send_vcard(vcard, sha) app.connections[account].get_module('VCardTemp').send_vcard(
vcard, sha)
else: else:
for acc in app.connections: for acc in app.connections:
app.connections[acc].send_vcard(vcard, sha) app.connections[acc].get_module('VCardTemp').send_vcard(
vcard, sha)
@dbus.service.method(INTERFACE, in_signature='ssss', out_signature='') @dbus.service.method(INTERFACE, in_signature='ssss', out_signature='')
def join_room(self, room_jid, nick, password, account): def join_room(self, room_jid, nick, password, account):

View File

@ -268,7 +268,7 @@ class VcardWindow:
widget.set_text('') widget.set_text('')
self.xml.get_object('DESC_textview').get_buffer().set_text('') self.xml.get_object('DESC_textview').get_buffer().set_text('')
def _nec_vcard_received(self, jid, resource, room, vcard): def _nec_vcard_received(self, jid, resource, room, vcard, *args):
self.clear_values() self.clear_values()
self._set_values(vcard, jid) self._set_values(vcard, jid)
@ -477,10 +477,13 @@ class VcardWindow:
self.fill_status_label() self.fill_status_label()
if self.gc_contact: if self.gc_contact:
con.request_vcard(self._nec_vcard_received, con.get_module('VCardTemp').request_vcard(
self.gc_contact.get_full_jid(), room=True) self._nec_vcard_received,
self.gc_contact.get_full_jid(),
room=True)
else: else:
con.request_vcard(self._nec_vcard_received, self.contact.jid) con.get_module('VCardTemp').request_vcard(
self._nec_vcard_received, self.contact.jid)
def on_close_button_clicked(self, widget): def on_close_button_clicked(self, widget):
self.window.destroy() self.window.destroy()

View File

@ -47,9 +47,6 @@ class MockConnection(Mock, ConnectionHandlers):
app.connections[account] = self app.connections[account] = self
def request_vcard(self, *args):
pass
class MockWindow(Mock): class MockWindow(Mock):
def __init__(self, *args): def __init__(self, *args):
Mock.__init__(self, *args) Mock.__init__(self, *args)