changeset 2252:cffa50c9f26b

plugin XEP-0054: nick handling + don't remove data on avatar set - new getNick and setNick allow to manipulate nickname - setAvatar was removing any data previously in vCard, this is not the case anymore
author Goffi <goffi@goffi.org>
date Sun, 21 May 2017 20:01:24 +0200
parents 83bcd9ec4782
children db468f24b9fc
files src/plugins/plugin_xep_0054.py
diffstat 1 files changed, 90 insertions(+), 31 deletions(-) [+]
line wrap: on
line diff
--- a/src/plugins/plugin_xep_0054.py	Sun May 21 19:59:42 2017 +0200
+++ b/src/plugins/plugin_xep_0054.py	Sun May 21 20:01:24 2017 +0200
@@ -23,7 +23,7 @@
 from sat.core.log import getLogger
 log = getLogger(__name__)
 from twisted.internet import threads, defer
-from twisted.words.protocols.jabber import jid
+from twisted.words.protocols.jabber import jid, error
 from twisted.words.xish import domish
 from twisted.python.failure import Failure
 
@@ -187,9 +187,9 @@
     def getCache(self, client, entity_jid, name):
         """return cached value for jid
 
-        @param entity_jid: target contact
-        @param name: name of the value ('nick' or 'avatar')
-        @return: wanted value or None"""
+        @param entity_jid(jid.JID): target contact
+        @param name(unicode): name of the value ('nick' or 'avatar')
+        @return(unicode, None): wanted value or None"""
         entity_jid = self.getBareOrFull(client, entity_jid)
         try:
             data = self.host.memory.getEntityData(entity_jid, [name], client.profile)
@@ -289,15 +289,10 @@
 
         defer.returnValue(vcard_dict)
 
-    def _vCardCb(self, iq_elt, to_jid, client):
+    def _vCardCb(self, vcard_elt, to_jid, client):
         """Called after the first get IQ"""
         log.debug(_("VCard found"))
-
-        try:
-            vcard_elt = iq_elt.elements(NS_VCARD, "vCard").next()
-        except StopIteration:
-            log.warning(u"Can't find vCard element in answer for jid {jid}", jid=to_jid.full())
-            return
+        iq_elt = vcard_elt.parent
         try:
             from_jid = jid.JID(iq_elt["from"])
         except KeyError:
@@ -310,11 +305,13 @@
         log.warning(u"Can't get vCard for {jid}: {failure}".format(jid=to_jid.full, failure=failure_))
         self.updateCache(client, to_jid, "avatar", None)
 
-    def getCard(self, client, entity_jid):
-        """Ask server for VCard
+    def _getVcardElt(self, iq_elt):
+       return iq_elt.elements(NS_VCARD, "vCard").next()
 
-        @param entity_jid(jid.JID): jid from which we want the VCard
-        @result: id to retrieve the profile
+    def getCardRaw(self, client, entity_jid):
+        """get raw vCard XML
+
+        params are as in [getCard]
         """
         entity_jid = self.getBareOrFull(client, entity_jid)
         log.debug(u"Asking for {}'s VCard".format(entity_jid.full()))
@@ -322,7 +319,18 @@
         reg_request["from"] = client.jid.full()
         reg_request["to"] = entity_jid.full()
         reg_request.addElement('vCard', NS_VCARD)
-        d = reg_request.send(entity_jid.full()).addCallbacks(self._vCardCb, self._vCardEb, callbackArgs=[entity_jid, client], errbackArgs=[entity_jid, client])
+        d = reg_request.send(entity_jid.full())
+        d.addCallback(self._getVcardElt)
+        return d
+
+    def getCard(self, client, entity_jid):
+        """Ask server for VCard
+
+        @param entity_jid(jid.JID): jid from which we want the VCard
+        @result: id to retrieve the profile
+        """
+        d = self.getCardRaw(client, entity_jid)
+        d.addCallbacks(self._vCardCb, self._vCardEb, callbackArgs=[entity_jid, client], errbackArgs=[entity_jid, client])
         return d
 
     def _getCardCb(self, dummy, client, entity):
@@ -382,7 +390,47 @@
                 d.addCallback(lambda dummy: full_path)
         return d
 
-    def _buildSetAvatar(self, client, vcard_set, file_path):
+    @defer.inlineCallbacks
+    def getNick(self, client, entity):
+        """get nick from cache, or check vCard
+
+        @param entity(jid.JID): entity to get nick from
+        @return(unicode, None): nick or None if not found
+        """
+        nick = self.getCache(client, entity, u'nick')
+        if nick is not None:
+            defer.returnValue(nick)
+        yield self.getCard(client, entity)
+        defer.returnValue(self.getCache(client, entity, u'nick'))
+
+    @defer.inlineCallbacks
+    def setNick(self, client, nick):
+        """update our vCard and set a nickname
+
+        @param nick(unicode): new nickname to use
+        """
+        jid_ = client.jid.userhostJID()
+        try:
+            vcard_elt = yield self.getCardRaw(client, jid_)
+        except error.StanzaError as e:
+            if e.condition == 'item-not-found':
+                vcard_elt = domish.Element((NS_VCARD, 'vCard'))
+            else:
+                raise e
+        try:
+            nickname_elt = next(vcard_elt.elements(NS_VCARD, u'NICKNAME'))
+        except StopIteration:
+            pass
+        else:
+            vcard_elt.children.remove(nickname_elt)
+
+        nickname_elt = vcard_elt.addElement((NS_VCARD, u'NICKNAME'), content=nick)
+        iq_elt = client.IQ()
+        vcard_elt = iq_elt.addChild(vcard_elt)
+        yield iq_elt.send()
+        self.updateCache(client, jid_, u'nick', unicode(nick))
+
+    def _buildSetAvatar(self, client, vcard_elt, file_path):
         # XXX: this method is executed in a separate thread
         try:
             img = Image.open(file_path)
@@ -405,7 +453,6 @@
         img_buf = StringIO()
         img.save(img_buf, 'PNG')
 
-        vcard_elt = vcard_set.addElement('vCard', NS_VCARD)
         photo_elt = vcard_elt.addElement('PHOTO')
         photo_elt.addElement('TYPE', content='image/png')
         photo_elt.addElement('BINVAL', content=b64encode(img_buf.getvalue()))
@@ -417,31 +464,43 @@
             MAX_AGE
             ) as f:
             f.write(img_buf.getvalue())
-        return vcard_set, image_hash
+        return image_hash
 
     def _setAvatar(self, file_path, profile_key=C.PROF_KEY_NONE):
         client = self.host.getClient(profile_key)
         return self.setAvatar(client, file_path)
 
+    @defer.inlineCallbacks
     def setAvatar(self, client, file_path):
         """Set avatar of the profile
 
         @param file_path: path of the image of the avatar
         """
-        #TODO: This is a temporary way of setting the avatar, as other VCard information is not managed.
-        #      A proper full VCard management should be done (and more generaly a public/private profile)
-        vcard_set = client.IQ()
-        d = threads.deferToThread(self._buildSetAvatar, client, vcard_set, file_path)
+        try:
+            # we first check if a vcard already exists, to keep data
+            vcard_elt = yield self.getCardRaw(client, client.jid.userhostJID())
+        except error.StanzaError as e:
+            if e.condition == 'item-not-found':
+                vcard_elt = domish.Element((NS_VCARD, 'vCard'))
+            else:
+                raise e
+        else:
+            # the vcard exists, we need to remove PHOTO element as we'll make a new one
+            try:
+                photo_elt = next(vcard_elt.elements(NS_VCARD, u'PHOTO'))
+            except StopIteration:
+                pass
+            else:
+                vcard_elt.children.remove(photo_elt)
 
-        def elementBuilt(result):
-            """Called once the image is at the right size/format, and the vcard set element is build"""
-            set_avatar_elt, image_hash = result
-            self.updateCache(client, client.jid.userhostJID(), 'avatar', image_hash)
-            return set_avatar_elt.send().addCallback(lambda ignore: client.presence.available()) # FIXME: should send the current presence, not always "available" !
+        iq_elt = client.IQ()
+        iq_elt.addChild(vcard_elt)
+        image_hash = yield threads.deferToThread(self._buildSetAvatar, client, vcard_elt, file_path)
+        # image is now at the right size/format
 
-        d.addCallback(elementBuilt)
-
-        return d
+        self.updateCache(client, client.jid.userhostJID(), 'avatar', image_hash)
+        yield iq_elt.send()
+        client.presence.available() # FIXME: should send the current presence, not always "available" !
 
 
 class XEP_0054_handler(XMPPHandler):