# HG changeset patch # User Goffi # Date 1262782604 -39600 # Node ID 4392f1fdb0640233bdfb4700fec7aae40fc29532 # Parent 9aa2d9dd40458f50981aabcc0f73c5193c5fa417 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 diff -r 9aa2d9dd4045 -r 4392f1fdb064 plugins/plugin_xep_0054.py --- 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 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()) + diff -r 9aa2d9dd4045 -r 4392f1fdb064 plugins/plugin_xep_0065.py --- a/plugins/plugin_xep_0065.py Wed Jan 06 23:49:55 2010 +1100 +++ b/plugins/plugin_xep_0065.py Wed Jan 06 23:56:44 2010 +1100 @@ -83,6 +83,7 @@ "name": "XEP 0065 Plugin", "import_name": "XEP_0065", "type": "XEP", +"protocols": ["XEP-0065"], "main": "XEP_0065", "description": """Implementation of SOCKS5 Bytestreams""" } diff -r 9aa2d9dd4045 -r 4392f1fdb064 plugins/plugin_xep_0077.py --- a/plugins/plugin_xep_0077.py Wed Jan 06 23:49:55 2010 +1100 +++ b/plugins/plugin_xep_0077.py Wed Jan 06 23:56:44 2010 +1100 @@ -35,6 +35,7 @@ "name": "XEP 0077 Plugin", "import_name": "XEP_0077", "type": "XEP", +"protocols": ["XEP-0077"], "dependencies": [], "main": "XEP_0077", "description": """Implementation of in-band registration""" diff -r 9aa2d9dd4045 -r 4392f1fdb064 plugins/plugin_xep_0096.py --- a/plugins/plugin_xep_0096.py Wed Jan 06 23:49:55 2010 +1100 +++ b/plugins/plugin_xep_0096.py Wed Jan 06 23:56:44 2010 +1100 @@ -45,6 +45,7 @@ "name": "XEP 0096 Plugin", "import_name": "XEP_0096", "type": "XEP", +"protocols": ["XEP-0096"], "dependencies": ["XEP_0065"], "main": "XEP_0096", "description": """Implementation of SI File Transfert""" diff -r 9aa2d9dd4045 -r 4392f1fdb064 plugins/plugin_xep_0100.py --- a/plugins/plugin_xep_0100.py Wed Jan 06 23:49:55 2010 +1100 +++ b/plugins/plugin_xep_0100.py Wed Jan 06 23:56:44 2010 +1100 @@ -31,6 +31,7 @@ "name": "Gateways Plugin", "import_name": "XEP_0100", "type": "XEP", +"protocols": ["XEP-0100"], "dependencies": ["XEP_0077"], "main": "XEP_0100", "description": """Implementation of Gateways protocol""" @@ -79,7 +80,6 @@ """Look for items with disco protocol, and ask infos for each one""" #FIXME: target is used as we can't find the original iq node (parent is None) # an other way would avoid this useless parameter (is there a way with wokkel ?) - if len(disco._items) == 0: debug ("No gateway found") self.host.actionResultExt(request_id,"DICT_DICT",{})