core, plugin XEP-0054: avatar upload:

- plugin XEP-0054: new setAvatar bridge method
    - new "presence_available" trigger
    - new DataError
This commit is contained in:
Goffi 2012-12-28 01:00:31 +01:00
commit 181d08f2e4

View file

@ -24,16 +24,19 @@ from twisted.internet import threads
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 @@ class XEP_0054():
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 @@ class XEP_0054():
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 @@ class XEP_0054():
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 @@ class XEP_0054():
@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 @@ class XEP_0054():
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)