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)