diff --git a/gajim/common/app.py b/gajim/common/app.py
index 8bdad77f3..bcccee4ec 100644
--- a/gajim/common/app.py
+++ b/gajim/common/app.py
@@ -397,6 +397,9 @@ def account_is_connected(account):
else:
return False
+def is_invisible(account):
+ return SHOW_LIST[connections[account].connected] == 'invisible'
+
def account_is_disconnected(account):
return not account_is_connected(account)
diff --git a/gajim/common/connection.py b/gajim/common/connection.py
index 5f464ca80..4fc325604 100644
--- a/gajim/common/connection.py
+++ b/gajim/common/connection.py
@@ -71,6 +71,8 @@ from gajim.common.modules.annotations import Annotations
from gajim.common.modules.roster_item_exchange import RosterItemExchange
from gajim.common.modules.last_activity import LastActivity
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.contacts import GC_Contact
from gajim.gtkgui_helpers import get_action
@@ -666,6 +668,8 @@ class Connection(CommonConnection, ConnectionHandlers):
self.register_module('RosterItemExchange', RosterItemExchange, self)
self.register_module('LastActivity', LastActivity, 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,
self._nec_privacy_list_received)
@@ -756,7 +760,7 @@ class Connection(CommonConnection, ConnectionHandlers):
self.connected = 0
self.time_to_reconnect = None
self.privacy_rules_supported = False
- self.avatar_presence_sent = False
+ self.get_module('VCardAvatars').avatar_advertised = False
if on_purpose:
self.sm = Smacks(self)
if self.connection:
@@ -1768,7 +1772,7 @@ class Connection(CommonConnection, ConnectionHandlers):
show='invisible'))
if initial:
# ask our VCard
- self.request_vcard(self._on_own_avatar_received)
+ self.get_module('VCardTemp').request_vcard()
# Get bookmarks from private namespace
self.get_bookmarks()
diff --git a/gajim/common/connection_handlers.py b/gajim/common/connection_handlers.py
index 0f6d68acf..be25c7828 100644
--- a/gajim/common/connection_handlers.py
+++ b/gajim/common/connection_handlers.py
@@ -283,388 +283,6 @@ class ConnectionDisco:
app.nec.push_incoming_event(AgentInfoReceivedEvent(None, conn=self,
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 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 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):
@@ -1296,13 +914,12 @@ class ConnectionHandlersBase:
return sess
class ConnectionHandlers(ConnectionArchive313,
-ConnectionVcard, ConnectionSocks5Bytestream, ConnectionDisco,
+ConnectionSocks5Bytestream, ConnectionDisco,
ConnectionCommands, ConnectionPubSub, ConnectionPEP, ConnectionCaps,
ConnectionHandlersBase, ConnectionJingle, ConnectionIBBytestream,
ConnectionHTTPUpload):
def __init__(self):
ConnectionArchive313.__init__(self)
- ConnectionVcard.__init__(self)
ConnectionSocks5Bytestream.__init__(self)
ConnectionIBBytestream.__init__(self)
ConnectionCommands.__init__(self)
@@ -1398,11 +1015,7 @@ ConnectionHTTPUpload):
app.ged.remove_event_handler('blocking', ged.CORE, self._nec_blocking)
def add_sha(self, p, send_caps=True):
- c = p.setTag('x', namespace=nbxmpp.NS_VCARD_UPDATE)
- 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)
+ p = self.get_module('VCardAvatars').add_update_node(p)
if send_caps:
return self._add_caps(p)
return p
@@ -1925,7 +1538,7 @@ ConnectionHTTPUpload):
show=show))
if self.vcard_supported:
# ask our VCard
- self.request_vcard(self._on_own_avatar_received)
+ self.get_module('VCardTemp').request_vcard()
# Get bookmarks from private namespace
self.get_bookmarks()
diff --git a/gajim/common/connection_handlers_events.py b/gajim/common/connection_handlers_events.py
index 5963f0423..4027a22bd 100644
--- a/gajim/common/connection_handlers_events.py
+++ b/gajim/common/connection_handlers_events.py
@@ -550,16 +550,6 @@ PresenceHelperEvent):
tim = helpers.datetime_tuple(time_str)
self.idle_time = timegm(tim)
- # Check if presence is from the room itself, used when the room
- # sends a avatar hash
- contact = app.contacts.get_groupchat_contact(self.conn.name, self.fjid)
- if contact:
- app.nec.push_incoming_event(
- RoomAvatarReceivedEvent(
- None, conn=self.conn, stanza=self.stanza,
- contact=contact, jid=self.jid))
- return
-
xtags = self.stanza.getTags('x')
for x in xtags:
namespace = x.getNamespace()
@@ -567,9 +557,6 @@ PresenceHelperEvent):
self.is_gc = True
elif namespace == nbxmpp.NS_SIGNED:
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:
# XEP-0091
self._generate_timestamp(self.stanza.timestamp)
@@ -1770,14 +1757,6 @@ class ConnectionTypeEvent(nec.NetworkIncomingEvent):
name = 'connection-type'
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):
name = 'stanza-received'
base_network_events = []
@@ -1967,13 +1946,6 @@ class NonAnonymousServerErrorEvent(nec.NetworkIncomingEvent):
name = 'non-anonymous-server-error'
base_network_events = []
-class VcardReceivedEvent(nec.NetworkIncomingEvent):
- name = 'vcard-received'
- base_network_events = []
-
- def generate(self):
- return True
-
class UpdateGCAvatarEvent(nec.NetworkIncomingEvent):
name = 'update-gc-avatar'
base_network_events = []
@@ -1995,19 +1967,6 @@ class UpdateRoomAvatarEvent(nec.NetworkIncomingEvent):
def generate(self):
return True
-class RoomAvatarReceivedEvent(nec.NetworkIncomingEvent):
- name = 'room-avatar-received'
- base_network_events = []
-
- def generate(self):
- vcard = self.stanza.getTag('x', namespace=nbxmpp.NS_VCARD_UPDATE)
- if vcard is None:
- 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):
name = 'pep-config-received'
base_network_events = []
diff --git a/gajim/common/const.py b/gajim/common/const.py
index 3c7ce2e33..c77cbb37b 100644
--- a/gajim/common/const.py
+++ b/gajim/common/const.py
@@ -113,6 +113,12 @@ class IdleState(IntEnum):
AWAY = 2
AWAKE = 3
+@unique
+class RequestAvatar(IntEnum):
+ SELF = 0
+ ROOM = 1
+ USER = 2
+
SSLError = {
2: _("Unable to get issuer certificate"),
3: _("Unable to get certificate CRL"),
diff --git a/gajim/common/helpers.py b/gajim/common/helpers.py
index 52321e6ec..ad7a1c7df 100644
--- a/gajim/common/helpers.py
+++ b/gajim/common/helpers.py
@@ -1398,15 +1398,7 @@ def get_subscription_request_msg(account=None):
s = _('I would like to add you to my contact list.')
if account:
s = _('Hello, I am $name.') + ' ' + s
- our_jid = app.get_jid_from_account(account)
- 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']
+ name = app.connections[account].get_module('VCardTemp').get_vard_name()
nick = app.nicks[account]
if name and nick:
name += ' (%s)' % nick
diff --git a/gajim/common/modules/vcard_avatars.py b/gajim/common/modules/vcard_avatars.py
new file mode 100644
index 000000000..bff1f63b3
--- /dev/null
+++ b/gajim/common/modules/vcard_avatars.py
@@ -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 .
+
+# 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 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 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 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
diff --git a/gajim/common/modules/vcard_temp.py b/gajim/common/modules/vcard_temp.py
new file mode 100644
index 000000000..5a5b53916
--- /dev/null
+++ b/gajim/common/modules/vcard_temp.py
@@ -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 .
+
+# 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 = []
diff --git a/gajim/common/zeroconf/connection_handlers_zeroconf.py b/gajim/common/zeroconf/connection_handlers_zeroconf.py
index 3684e4827..f67a75293 100644
--- a/gajim/common/zeroconf/connection_handlers_zeroconf.py
+++ b/gajim/common/zeroconf/connection_handlers_zeroconf.py
@@ -40,19 +40,13 @@ AGENT_REMOVED = 'agent_removed'
from gajim.common import connection_handlers
-class ConnectionVcard(connection_handlers.ConnectionVcard):
+class ConnectionVcard:
def add_sha(self, p, *args):
return p
def add_caps(self, p):
return p
- def request_vcard(self, *args):
- pass
-
- def send_vcard(self, *args):
- pass
-
class ConnectionHandlersZeroconf(ConnectionVcard,
ConnectionSocks5BytestreamZeroconf, ConnectionCommands,
diff --git a/gajim/groupchat_control.py b/gajim/groupchat_control.py
index 1761ef5d9..69287f1a9 100644
--- a/gajim/groupchat_control.py
+++ b/gajim/groupchat_control.py
@@ -762,8 +762,8 @@ class GroupchatControl(ChatControlBase):
publish = app.interface.get_avatar(sha, publish=True)
avatar = base64.b64encode(publish).decode('utf-8')
-
- app.connections[self.account].upload_room_avatar(
+ con = app.connections[self.account]
+ con.get_module('VCardTemp').upload_room_avatar(
self.room_jid, avatar)
AvatarChooserDialog(_on_accept,
diff --git a/gajim/profile_window.py b/gajim/profile_window.py
index 16e8417d3..e178e0e2e 100644
--- a/gajim/profile_window.py
+++ b/gajim/profile_window.py
@@ -77,7 +77,7 @@ class ProfileWindow:
self._nec_vcard_not_published)
self.window.show_all()
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)
def on_information_notebook_switch_page(self, widget, page, page_num):
@@ -261,7 +261,7 @@ class ProfileWindow:
self.progressbar.set_fraction(0)
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_)
def add_to_vcard(self, vcard_, entry, txt):
@@ -339,7 +339,8 @@ class ProfileWindow:
app.connections[self.account].retract_nickname()
nick = app.config.get_per('accounts', self.account, 'name')
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,
_('Sending profileā¦'))
self.progressbar.show()
diff --git a/gajim/remote_control.py b/gajim/remote_control.py
index da27f147c..36e8836f1 100644
--- a/gajim/remote_control.py
+++ b/gajim/remote_control.py
@@ -840,10 +840,12 @@ class SignalObject(dbus.service.Object):
if avatar_mime_type:
vcard['PHOTO']['TYPE'] = avatar_mime_type
if account:
- app.connections[account].send_vcard(vcard, sha)
+ app.connections[account].get_module('VCardTemp').send_vcard(
+ vcard, sha)
else:
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='')
def join_room(self, room_jid, nick, password, account):
diff --git a/gajim/vcard.py b/gajim/vcard.py
index 29b275bbc..1ad3a64f7 100644
--- a/gajim/vcard.py
+++ b/gajim/vcard.py
@@ -268,7 +268,7 @@ class VcardWindow:
widget.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._set_values(vcard, jid)
@@ -477,10 +477,13 @@ class VcardWindow:
self.fill_status_label()
if self.gc_contact:
- con.request_vcard(self._nec_vcard_received,
- self.gc_contact.get_full_jid(), room=True)
+ con.get_module('VCardTemp').request_vcard(
+ self._nec_vcard_received,
+ self.gc_contact.get_full_jid(),
+ room=True)
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):
self.window.destroy()
diff --git a/test/lib/gajim_mocks.py b/test/lib/gajim_mocks.py
index 1d7cc6d76..6efcdc904 100644
--- a/test/lib/gajim_mocks.py
+++ b/test/lib/gajim_mocks.py
@@ -47,9 +47,6 @@ class MockConnection(Mock, ConnectionHandlers):
app.connections[account] = self
- def request_vcard(self, *args):
- pass
-
class MockWindow(Mock):
def __init__(self, *args):
Mock.__init__(self, *args)