diff plugins/plugin_xep_0054.py @ 48:4392f1fdb064

plugins improvement - new protocols field in plugins info, useful to know which ones are implemented by the plugin - XEP-0153 first implementation (vcard avatars) - plugin vcard: avatar & nick cached, clients are noticed of updated values
author Goffi <goffi@goffi.org>
date Wed, 06 Jan 2010 23:56:44 +1100
parents bfa7086d26d6
children a5b5fb5fc9fd
line wrap: on
line diff
--- a/plugins/plugin_xep_0054.py	Wed Jan 06 23:49:55 2010 +1100
+++ b/plugins/plugin_xep_0054.py	Wed Jan 06 23:56:44 2010 +1100
@@ -36,32 +36,73 @@
 from hashlib import sha1
 from time import sleep
 
+try:
+    from twisted.words.protocols.xmlstream import XMPPHandler
+except ImportError:
+    from wokkel.subprotocols import XMPPHandler
+
 AVATAR_PATH = "/avatars"
 
 IQ_GET = '/iq[@type="get"]'
 NS_VCARD = 'vcard-temp'
 VCARD_REQUEST = IQ_GET + '/vCard[@xmlns="' + NS_VCARD + '"]'  #TODO: manage requests
 
+PRESENCE = '/presence'
+NS_VCARD_UPDATE = 'vcard-temp:x:update'
+VCARD_UPDATE = PRESENCE + '/x[@xmlns="' +  NS_VCARD_UPDATE + '"]'
+
 PLUGIN_INFO = {
 "name": "XEP 0054 Plugin",
 "import_name": "XEP_0054",
 "type": "XEP",
+"protocols": ["XEP-0054", "XEP-0153"],
 "dependencies": [],
 "main": "XEP_0054",
 "description": """Implementation of vcard-temp"""
 }
 
-class XEP_0054():
+class XEP_0054(XMPPHandler):
     implements(iwokkel.IDisco)
 
     def __init__(self, host):
         info("Plugin XEP_0054 initialization")
         self.host = host
         self.avatar_path = os.path.expanduser(self.host.get_const('local_dir') + AVATAR_PATH)
+        self.vcard_cache = host.memory.getPrivate("vcard_cache") or {}  #used to store nicknames and avatar, key = jid
         if not os.path.exists(self.avatar_path):
             os.makedirs(self.avatar_path)
         host.bridge.addMethod("getProfile", ".communication", in_sign='s', out_sign='s', method=self.getProfile)
         host.bridge.addMethod("getAvatarFile", ".communication", in_sign='s', out_sign='s', method=self.getAvatarFile)
+        host.bridge.addMethod("getProfileCache", ".communication", in_sign='s', out_sign='a{ss}', method=self.getProfileCache)
+   
+    def update_cache(self, jid, name, value):
+        """update cache value
+        - save value in memory in case of change
+        - send updatedValue signal if the value is new or updated
+        """
+        if not self.vcard_cache.has_key(jid.userhost()):
+            self.vcard_cache[jid.userhost()] = {}
+        
+        cache = self.vcard_cache[jid.userhost()]
+        old_value = cache[name] if cache.has_key(name) else None
+        if not old_value or value != old_value:
+            cache[name] = value
+            self.host.memory.setPrivate("vcard_cache", self.vcard_cache)
+            self.host.bridge.updatedValue('profile_'+name, {'jid':jid.userhost(), name:value})
+
+    def get_cache(self, jid, name):
+        """return cached value for jid
+        @param jid: target contact
+        @param name: name of the value ('nick' or 'avatar')
+        @return: wanted value or None"""
+        try:
+            return self.vcard_cache[jid.userhost()][name]
+        except KeyError:
+            return None
+
+
+    def connectionInitialized(self):
+        self.xmlstream.addObserver(VCARD_UPDATE, self.update)
     
     def getDiscoInfo(self, requestor, target, nodeIdentifier=''):
         return [disco.DiscoFeature(NS_VCARD)]
@@ -71,7 +112,6 @@
 
     def save_photo(self, photo_xml):
         """Parse a <PHOTO> elem and save the picture"""
-        print "save_photo result"
         for elem in photo_xml.elements():
             if elem.name == 'TYPE':
                 info('Photo of type [%s] found' % str(elem))
@@ -89,7 +129,7 @@
                 return hash
 
     @defer.deferredGenerator
-    def vCard2Dict(self, vcard):
+    def vCard2Dict(self, vcard, target):
         """Convert a VCard to a dict, and save binaries"""
         debug ("parsing vcard")
         dictionary = {}
@@ -100,6 +140,7 @@
                 dictionary['fullname'] = unicode(elem)
             elif elem.name == 'NICKNAME':
                 dictionary['nick'] = unicode(elem)
+                self.update_cache(target, 'nick', dictionary['nick'])
             elif elem.name == 'URL':
                 dictionary['website'] = unicode(elem)
             elif elem.name == 'EMAIL':
@@ -107,11 +148,14 @@
             elif elem.name == 'BDAY':
                 dictionary['birthday'] = unicode(elem) 
             elif elem.name == 'PHOTO':
-                debug('photo deferred')
                 d2 = defer.waitForDeferred(
                             threads.deferToThread(self.save_photo, elem))
                 yield d2
                 dictionary["avatar"] = d2.getResult()
+                if not dictionary["avatar"]:  #can happen in case of e.g. empty photo elem
+                    del dictionary['avatar']
+                else:
+                    self.update_cache(target, 'avatar', dictionary['avatar'])
             else:
                 info ('FIXME: [%s] VCard tag is not managed yet' % elem.name)
 
@@ -122,7 +166,7 @@
         debug ("VCard found")
 
         if answer.firstChildElement().name == "vCard":
-            d = self.vCard2Dict(answer.firstChildElement())
+            d = self.vCard2Dict(answer.firstChildElement(), jid.JID(answer["from"]))
             d.addCallback(lambda data: self.host.bridge.actionResult("RESULT", answer['id'], data))
         else:
             error ("FIXME: vCard not found as first child element")
@@ -138,12 +182,12 @@
         @param target: jid from which we want the VCard
         @result: id to retrieve the profile"""
         to_jid = jid.JID(target)
-        debug("Asking for %s's VCard" % to_jid.full())
+        debug("Asking for %s's VCard" % to_jid.userhost())
         reg_request=IQ(self.host.xmlstream,'get')
         reg_request["from"]=self.host.me.full()
-        reg_request["to"] = to_jid.full()
+        reg_request["to"] = to_jid.userhost()
         query=reg_request.addElement('vCard', NS_VCARD)
-        reg_request.send(to_jid.full()).addCallbacks(self.vcard_ok, self.vcard_err)
+        reg_request.send(to_jid.userhost()).addCallbacks(self.vcard_ok, self.vcard_err)
         return reg_request["id"] 
 
     def getAvatarFile(self, hash):
@@ -157,3 +201,31 @@
             return ""
         return filename
 
+    def getProfileCache(self, target):
+        """Request for cached values of profile 
+        return the cached nickname and avatar if exists, else get VCard
+        """
+        to_jid = jid.JID(target)
+        result = {}
+        nick = self.get_cache(to_jid, 'nick')
+        if nick:
+            result['nick'] = nick
+        avatar = self.get_cache(to_jid, 'avatar')
+        if avatar:
+            result['avatar'] = avatar
+        return result
+
+    def update(self, presence):
+        """Request for VCard's nickname
+        return the cached nickname if exists, else get VCard
+        """
+        to_jid = jid.JID(presence['from'])
+        x_elem = filter (lambda x:x.name == "x", presence.elements())[0] #We only want the "x" element
+        for elem in x_elem.elements():
+            if elem.name == 'photo':
+                hash = str(elem)
+                old_avatar = self.get_cache(to_jid, 'avatar')
+                if not old_avatar or old_avatar != hash:
+                    debug('New avatar found, requesting vcard')
+                    self.getProfile(to_jid.userhost())
+