Mercurial > libervia-backend
diff src/plugins/plugin_xep_0054.py @ 562:0bb2e0d1c878
core, plugin XEP-0054: avatar upload:
- plugin XEP-0054: new setAvatar bridge method
- new "presence_available" trigger
- new DataError
author | Goffi <goffi@goffi.org> |
---|---|
date | Fri, 28 Dec 2012 01:00:31 +0100 |
parents | 7ffae708b176 |
children | bf1505df088c |
line wrap: on
line diff
--- a/src/plugins/plugin_xep_0054.py Wed Dec 19 23:22:10 2012 +0100 +++ b/src/plugins/plugin_xep_0054.py Fri Dec 28 01:00:31 2012 +0100 @@ -24,16 +24,19 @@ from twisted.internet.defer import inlineCallbacks, returnValue from twisted.words.protocols.jabber import jid from twisted.words.protocols.jabber.xmlstream import IQ +from twisted.words.xish import domish import os.path from zope.interface import implements from wokkel import disco, iwokkel -from base64 import b64decode +from base64 import b64decode,b64encode from hashlib import sha1 from sat.core import exceptions from sat.memory.persistent import PersistentDict +import Image +from cStringIO import StringIO try: from twisted.words.protocols.xmlstream import XMPPHandler @@ -76,9 +79,19 @@ self.avatars_cache.load() #FIXME: resulting deferred must be correctly managed host.bridge.addMethod("getCard", ".plugin", in_sign='ss', out_sign='s', method=self.getCard) host.bridge.addMethod("getAvatarFile", ".plugin", in_sign='s', out_sign='s', method=self.getAvatarFile) + host.bridge.addMethod("setAvatar", ".plugin", in_sign='ss', out_sign='', method=self.setAvatar, async = True) + host.trigger.add("presence_available", self.presenceTrigger) def getHandler(self, profile): - return XEP_0054_handler(self) + return XEP_0054_handler(self) + + def presenceTrigger(self, presence_elt, client): + if client.jid.userhost() in self.avatars_cache: + x_elt = domish.Element((NS_VCARD_UPDATE, 'x')) + x_elt.addElement('photo', content=self.avatars_cache[client.jid.userhost()]) + presence_elt.addChild(x_elt) + + return True def _fillCachedValues(self, result, client): #FIXME: this is really suboptimal, need to be reworked @@ -130,15 +143,15 @@ if elem.name == 'BINVAL': debug(_('Decoding binary')) decoded = b64decode(str(elem)) - hash = sha1(decoded).hexdigest() - filename = self.avatar_path+'/'+hash + image_hash = sha1(decoded).hexdigest() + filename = self.avatar_path+'/'+image_hash if not os.path.exists(filename): with open(filename,'wb') as file: file.write(decoded) - debug(_("file saved to %s") % hash) + debug(_("file saved to %s") % image_hash) else: - debug(_("file [%s] already in cache") % hash) - return hash + debug(_("file [%s] already in cache") % image_hash) + return image_hash @inlineCallbacks def vCard2Dict(self, vcard, target, profile): @@ -174,7 +187,12 @@ debug (_("VCard found")) if answer.firstChildElement().name == "vCard": - d = self.vCard2Dict(answer.firstChildElement(), jid.JID(answer["from"]), profile) + _jid, steam = self.host.getJidNStream(profile) + try: + from_jid = jid.JID(answer["from"]) + except KeyError: + from_jid = _jid.userhostJID() + d = self.vCard2Dict(answer.firstChildElement(), from_jid, profile) d.addCallback(lambda data: self.host.bridge.actionResult("RESULT", answer['id'], data, profile)) else: error (_("FIXME: vCard not found as first child element")) @@ -191,7 +209,7 @@ @result: id to retrieve the profile""" current_jid, xmlstream = self.host.getJidNStream(profile_key) if not xmlstream: - error (_('Asking vcard for an non-existant or not connected profile')) + error (_('Asking vcard for a non-existant or not connected profile')) return "" profile = self.host.memory.getProfileName(profile_key) to_jid = jid.JID(target_s) @@ -203,17 +221,58 @@ reg_request.send(to_jid.userhost()).addCallbacks(self.vcard_ok, self.vcard_err, callbackArgs=[profile], errbackArgs=[profile]) return reg_request["id"] - def getAvatarFile(self, hash): + def getAvatarFile(self, avatar_hash): """Give the full path of avatar from hash @param hash: SHA1 hash @return full_path """ - filename = self.avatar_path+'/'+hash + filename = self.avatar_path+'/'+avatar_hash if not os.path.exists(filename): - error (_("Asking for an uncached avatar [%s]") % hash) + error (_("Asking for an uncached avatar [%s]") % avatar_hash) return "" return filename + def _buildSetAvatar(self, vcard_set, filepath): + try: + img = Image.open(filepath) + except IOError: + raise exceptions.DataError("Can't open image") + + if img.size != (64, 64): + img.resize((64, 64)) + 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())) + img_hash = sha1(img_buf.getvalue()).hexdigest() + return (vcard_set, img_hash) + + def setAvatar(self, filepath, profile_key='@DEFAULT@'): + """Set avatar of the profile + @param filepath: path of the image of the avatar""" + #TODO: This is a temporary way of setting avatar, as other VCard informations are not managed. + # A proper full VCard management should be done (and more generaly a public/private profile) + client = self.host.getClient(profile_key) + if not client: + raise exceptions.NotConnectedProfileError(_('Trying to set avatar for a non-existant or not connected profile')) + + vcard_set = IQ(client.xmlstream,'set') + d = threads.deferToThread(self._buildSetAvatar, vcard_set, filepath) + + def elementBuilt(result): + """Called once the image is at the right size/format, and the vcard set element is build""" + set_avatar_elt, img_hash = result + self.avatars_cache[client.jid.userhost()] = img_hash # we need to update the hash, so we can send a new presence + # element with the right hash + return set_avatar_elt.send().addCallback(lambda ignore: client.presence.available()) + + d.addCallback(elementBuilt) + + return d + class XEP_0054_handler(XMPPHandler): implements(iwokkel.IDisco)