comparison 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
comparison
equal deleted inserted replaced
47:9aa2d9dd4045 48:4392f1fdb064
34 34
35 from base64 import b64decode 35 from base64 import b64decode
36 from hashlib import sha1 36 from hashlib import sha1
37 from time import sleep 37 from time import sleep
38 38
39 try:
40 from twisted.words.protocols.xmlstream import XMPPHandler
41 except ImportError:
42 from wokkel.subprotocols import XMPPHandler
43
39 AVATAR_PATH = "/avatars" 44 AVATAR_PATH = "/avatars"
40 45
41 IQ_GET = '/iq[@type="get"]' 46 IQ_GET = '/iq[@type="get"]'
42 NS_VCARD = 'vcard-temp' 47 NS_VCARD = 'vcard-temp'
43 VCARD_REQUEST = IQ_GET + '/vCard[@xmlns="' + NS_VCARD + '"]' #TODO: manage requests 48 VCARD_REQUEST = IQ_GET + '/vCard[@xmlns="' + NS_VCARD + '"]' #TODO: manage requests
49
50 PRESENCE = '/presence'
51 NS_VCARD_UPDATE = 'vcard-temp:x:update'
52 VCARD_UPDATE = PRESENCE + '/x[@xmlns="' + NS_VCARD_UPDATE + '"]'
44 53
45 PLUGIN_INFO = { 54 PLUGIN_INFO = {
46 "name": "XEP 0054 Plugin", 55 "name": "XEP 0054 Plugin",
47 "import_name": "XEP_0054", 56 "import_name": "XEP_0054",
48 "type": "XEP", 57 "type": "XEP",
58 "protocols": ["XEP-0054", "XEP-0153"],
49 "dependencies": [], 59 "dependencies": [],
50 "main": "XEP_0054", 60 "main": "XEP_0054",
51 "description": """Implementation of vcard-temp""" 61 "description": """Implementation of vcard-temp"""
52 } 62 }
53 63
54 class XEP_0054(): 64 class XEP_0054(XMPPHandler):
55 implements(iwokkel.IDisco) 65 implements(iwokkel.IDisco)
56 66
57 def __init__(self, host): 67 def __init__(self, host):
58 info("Plugin XEP_0054 initialization") 68 info("Plugin XEP_0054 initialization")
59 self.host = host 69 self.host = host
60 self.avatar_path = os.path.expanduser(self.host.get_const('local_dir') + AVATAR_PATH) 70 self.avatar_path = os.path.expanduser(self.host.get_const('local_dir') + AVATAR_PATH)
71 self.vcard_cache = host.memory.getPrivate("vcard_cache") or {} #used to store nicknames and avatar, key = jid
61 if not os.path.exists(self.avatar_path): 72 if not os.path.exists(self.avatar_path):
62 os.makedirs(self.avatar_path) 73 os.makedirs(self.avatar_path)
63 host.bridge.addMethod("getProfile", ".communication", in_sign='s', out_sign='s', method=self.getProfile) 74 host.bridge.addMethod("getProfile", ".communication", in_sign='s', out_sign='s', method=self.getProfile)
64 host.bridge.addMethod("getAvatarFile", ".communication", in_sign='s', out_sign='s', method=self.getAvatarFile) 75 host.bridge.addMethod("getAvatarFile", ".communication", in_sign='s', out_sign='s', method=self.getAvatarFile)
76 host.bridge.addMethod("getProfileCache", ".communication", in_sign='s', out_sign='a{ss}', method=self.getProfileCache)
77
78 def update_cache(self, jid, name, value):
79 """update cache value
80 - save value in memory in case of change
81 - send updatedValue signal if the value is new or updated
82 """
83 if not self.vcard_cache.has_key(jid.userhost()):
84 self.vcard_cache[jid.userhost()] = {}
85
86 cache = self.vcard_cache[jid.userhost()]
87 old_value = cache[name] if cache.has_key(name) else None
88 if not old_value or value != old_value:
89 cache[name] = value
90 self.host.memory.setPrivate("vcard_cache", self.vcard_cache)
91 self.host.bridge.updatedValue('profile_'+name, {'jid':jid.userhost(), name:value})
92
93 def get_cache(self, jid, name):
94 """return cached value for jid
95 @param jid: target contact
96 @param name: name of the value ('nick' or 'avatar')
97 @return: wanted value or None"""
98 try:
99 return self.vcard_cache[jid.userhost()][name]
100 except KeyError:
101 return None
102
103
104 def connectionInitialized(self):
105 self.xmlstream.addObserver(VCARD_UPDATE, self.update)
65 106
66 def getDiscoInfo(self, requestor, target, nodeIdentifier=''): 107 def getDiscoInfo(self, requestor, target, nodeIdentifier=''):
67 return [disco.DiscoFeature(NS_VCARD)] 108 return [disco.DiscoFeature(NS_VCARD)]
68 109
69 def getDiscoItems(self, requestor, target, nodeIdentifier=''): 110 def getDiscoItems(self, requestor, target, nodeIdentifier=''):
70 return [] 111 return []
71 112
72 def save_photo(self, photo_xml): 113 def save_photo(self, photo_xml):
73 """Parse a <PHOTO> elem and save the picture""" 114 """Parse a <PHOTO> elem and save the picture"""
74 print "save_photo result"
75 for elem in photo_xml.elements(): 115 for elem in photo_xml.elements():
76 if elem.name == 'TYPE': 116 if elem.name == 'TYPE':
77 info('Photo of type [%s] found' % str(elem)) 117 info('Photo of type [%s] found' % str(elem))
78 if elem.name == 'BINVAL': 118 if elem.name == 'BINVAL':
79 debug('Decoding binary') 119 debug('Decoding binary')
87 else: 127 else:
88 debug("file [%s] already in cache" % hash) 128 debug("file [%s] already in cache" % hash)
89 return hash 129 return hash
90 130
91 @defer.deferredGenerator 131 @defer.deferredGenerator
92 def vCard2Dict(self, vcard): 132 def vCard2Dict(self, vcard, target):
93 """Convert a VCard to a dict, and save binaries""" 133 """Convert a VCard to a dict, and save binaries"""
94 debug ("parsing vcard") 134 debug ("parsing vcard")
95 dictionary = {} 135 dictionary = {}
96 d = defer.Deferred() 136 d = defer.Deferred()
97 137
98 for elem in vcard.elements(): 138 for elem in vcard.elements():
99 if elem.name == 'FN': 139 if elem.name == 'FN':
100 dictionary['fullname'] = unicode(elem) 140 dictionary['fullname'] = unicode(elem)
101 elif elem.name == 'NICKNAME': 141 elif elem.name == 'NICKNAME':
102 dictionary['nick'] = unicode(elem) 142 dictionary['nick'] = unicode(elem)
143 self.update_cache(target, 'nick', dictionary['nick'])
103 elif elem.name == 'URL': 144 elif elem.name == 'URL':
104 dictionary['website'] = unicode(elem) 145 dictionary['website'] = unicode(elem)
105 elif elem.name == 'EMAIL': 146 elif elem.name == 'EMAIL':
106 dictionary['email'] = unicode(elem) 147 dictionary['email'] = unicode(elem)
107 elif elem.name == 'BDAY': 148 elif elem.name == 'BDAY':
108 dictionary['birthday'] = unicode(elem) 149 dictionary['birthday'] = unicode(elem)
109 elif elem.name == 'PHOTO': 150 elif elem.name == 'PHOTO':
110 debug('photo deferred')
111 d2 = defer.waitForDeferred( 151 d2 = defer.waitForDeferred(
112 threads.deferToThread(self.save_photo, elem)) 152 threads.deferToThread(self.save_photo, elem))
113 yield d2 153 yield d2
114 dictionary["avatar"] = d2.getResult() 154 dictionary["avatar"] = d2.getResult()
155 if not dictionary["avatar"]: #can happen in case of e.g. empty photo elem
156 del dictionary['avatar']
157 else:
158 self.update_cache(target, 'avatar', dictionary['avatar'])
115 else: 159 else:
116 info ('FIXME: [%s] VCard tag is not managed yet' % elem.name) 160 info ('FIXME: [%s] VCard tag is not managed yet' % elem.name)
117 161
118 yield dictionary 162 yield dictionary
119 163
120 def vcard_ok(self, answer): 164 def vcard_ok(self, answer):
121 """Called after the first get IQ""" 165 """Called after the first get IQ"""
122 debug ("VCard found") 166 debug ("VCard found")
123 167
124 if answer.firstChildElement().name == "vCard": 168 if answer.firstChildElement().name == "vCard":
125 d = self.vCard2Dict(answer.firstChildElement()) 169 d = self.vCard2Dict(answer.firstChildElement(), jid.JID(answer["from"]))
126 d.addCallback(lambda data: self.host.bridge.actionResult("RESULT", answer['id'], data)) 170 d.addCallback(lambda data: self.host.bridge.actionResult("RESULT", answer['id'], data))
127 else: 171 else:
128 error ("FIXME: vCard not found as first child element") 172 error ("FIXME: vCard not found as first child element")
129 self.host.bridge.actionResult("SUPPRESS", answer['id'], {}) #FIXME: maybe an error message would be best 173 self.host.bridge.actionResult("SUPPRESS", answer['id'], {}) #FIXME: maybe an error message would be best
130 174
136 def getProfile(self, target): 180 def getProfile(self, target):
137 """Ask server for VCard 181 """Ask server for VCard
138 @param target: jid from which we want the VCard 182 @param target: jid from which we want the VCard
139 @result: id to retrieve the profile""" 183 @result: id to retrieve the profile"""
140 to_jid = jid.JID(target) 184 to_jid = jid.JID(target)
141 debug("Asking for %s's VCard" % to_jid.full()) 185 debug("Asking for %s's VCard" % to_jid.userhost())
142 reg_request=IQ(self.host.xmlstream,'get') 186 reg_request=IQ(self.host.xmlstream,'get')
143 reg_request["from"]=self.host.me.full() 187 reg_request["from"]=self.host.me.full()
144 reg_request["to"] = to_jid.full() 188 reg_request["to"] = to_jid.userhost()
145 query=reg_request.addElement('vCard', NS_VCARD) 189 query=reg_request.addElement('vCard', NS_VCARD)
146 reg_request.send(to_jid.full()).addCallbacks(self.vcard_ok, self.vcard_err) 190 reg_request.send(to_jid.userhost()).addCallbacks(self.vcard_ok, self.vcard_err)
147 return reg_request["id"] 191 return reg_request["id"]
148 192
149 def getAvatarFile(self, hash): 193 def getAvatarFile(self, hash):
150 """Give the full path of avatar from hash 194 """Give the full path of avatar from hash
151 @param hash: SHA1 hash 195 @param hash: SHA1 hash
155 if not os.path.exists(filename): 199 if not os.path.exists(filename):
156 error ("Asking for an uncached avatar [%s]" % hash) 200 error ("Asking for an uncached avatar [%s]" % hash)
157 return "" 201 return ""
158 return filename 202 return filename
159 203
204 def getProfileCache(self, target):
205 """Request for cached values of profile
206 return the cached nickname and avatar if exists, else get VCard
207 """
208 to_jid = jid.JID(target)
209 result = {}
210 nick = self.get_cache(to_jid, 'nick')
211 if nick:
212 result['nick'] = nick
213 avatar = self.get_cache(to_jid, 'avatar')
214 if avatar:
215 result['avatar'] = avatar
216 return result
217
218 def update(self, presence):
219 """Request for VCard's nickname
220 return the cached nickname if exists, else get VCard
221 """
222 to_jid = jid.JID(presence['from'])
223 x_elem = filter (lambda x:x.name == "x", presence.elements())[0] #We only want the "x" element
224 for elem in x_elem.elements():
225 if elem.name == 'photo':
226 hash = str(elem)
227 old_avatar = self.get_cache(to_jid, 'avatar')
228 if not old_avatar or old_avatar != hash:
229 debug('New avatar found, requesting vcard')
230 self.getProfile(to_jid.userhost())
231