# HG changeset patch # User Goffi # Date 1495389684 -7200 # Node ID cffa50c9f26bced28f7dc37e0693a032dd94a7e3 # Parent 83bcd9ec47825830002dc79d5ca77a4b293bd948 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 diff -r 83bcd9ec4782 -r cffa50c9f26b src/plugins/plugin_xep_0054.py --- 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):