comparison src/plugins/plugin_xep_0054.py @ 1970:200cd707a46d

plugin XEP-0045, quick_frontend + primitivus (chat): cleaning of XEP-0045 (first pass): - bridge methods/signals now all start with "muc" to follow new convention - internal method use client instead of profile to follow new convention - removed excetpions from plugin XEP-0045 in favor of core.exceptions, NotReady added - cleaned/simplified several part of the code. checkClient removed as it is not needed anymore - self.clients map removed, muc data are now stored directly in client - getRoomEntityNick and getRoomNicksOfUsers are removed as they don't look sane. /!\ This break all room game plugins for the moment - use of uuid4 instead of uuid1 for getUniqueName, as host ID and current time are used for uuid1
author Goffi <goffi@goffi.org>
date Mon, 27 Jun 2016 21:45:11 +0200
parents 2daf7b4c6756
children 8156f2116dc9
comparison
equal deleted inserted replaced
1969:5fbe09b9b568 1970:200cd707a46d
107 x_elt.addElement('photo', content=avatar_hash) 107 x_elt.addElement('photo', content=avatar_hash)
108 presence_elt.addChild(x_elt) 108 presence_elt.addChild(x_elt)
109 109
110 return True 110 return True
111 111
112 def isInRoom(self, entity_jid, profile): 112 def isInRoom(self, client, entity_jid):
113 """Tell if an full jid is a member of a room 113 """Tell if an full jid is a member of a room
114 114
115 @param entity_jid(jid.JID): full jid of the entity 115 @param entity_jid(jid.JID): full jid of the entity
116 @return (bool): True if the bare jid of the entity is a room jid 116 @return (bool): True if the bare jid of the entity is a room jid
117 """ 117 """
118 try: 118 try:
119 return self.host.plugins['XEP-0045'].isRoom(entity_jid.userhostJID(), profile_key=profile) 119 self.host.plugins['XEP-0045'].checkRoomJoined(client, entity_jid.userhostJID())
120 except KeyError: 120 except exceptions.NotFound:
121 return False 121 return False
122 else:
123 return True
122 124
123 def _fillCachedValues(self, profile): 125 def _fillCachedValues(self, profile):
124 #FIXME: this is really suboptimal, need to be reworked 126 #FIXME: this is really suboptimal, need to be reworked
125 # the current naive approach keeps a map between all jids of all profiles 127 # the current naive approach keeps a map between all jids of all profiles
126 # in persistent cache, then put avatar hashs in memory. 128 # in persistent cache, then put avatar hashs in memory.
141 143
142 def profileDisconnected(self, profile): 144 def profileDisconnected(self, profile):
143 log.debug(u"Deleting profile cache for avatars") 145 log.debug(u"Deleting profile cache for avatars")
144 del self.cache[profile] 146 del self.cache[profile]
145 147
146 def updateCache(self, jid_, name, value, profile): 148 def updateCache(self, client, jid_, name, value):
147 """update cache value 149 """update cache value
148 150
149 save value in memory in case of change 151 save value in memory in case of change
150 @param jid_(jid.JID): jid of the owner of the vcard 152 @param jid_(jid.JID): jid of the owner of the vcard
151 @param name(str): name of the item which changed 153 @param name(str): name of the item which changed
152 @param value(unicode): new value of the item 154 @param value(unicode): new value of the item
153 @param profile(unicode): profile which received the update
154 """ 155 """
155 if jid_.resource: 156 if jid_.resource:
156 if not self.isInRoom(jid_, profile): 157 if not self.isInRoom(client, jid_):
157 # VCard are retrieved with bare jid 158 # VCard are retrieved with bare jid
158 # but MUC room is a special case 159 # but MUC room is a special case
159 jid_ = jid.userhostJID() 160 jid_ = jid.userhostJID()
160 161
161 self.host.memory.updateEntityData(jid_, name, value, profile_key=profile) 162 self.host.memory.updateEntityData(jid_, name, value, profile_key=client.profile)
162 if name in CACHED_DATA: 163 if name in CACHED_DATA:
163 jid_s = jid_.userhost() 164 jid_s = jid_.userhost()
164 self.cache[profile].setdefault(jid_s, {})[name] = value 165 self.cache[client.profile].setdefault(jid_s, {})[name] = value
165 self.cache[profile].force(jid_s) 166 self.cache[client.profile].force(jid_s)
166 167
167 def getCache(self, entity_jid, name, profile): 168 def getCache(self, client, entity_jid, name):
168 """return cached value for jid 169 """return cached value for jid
169 170
170 @param entity_jid: target contact 171 @param entity_jid: target contact
171 @param name: name of the value ('nick' or 'avatar') 172 @param name: name of the value ('nick' or 'avatar')
172 @param profile: %(doc_profile)s
173 @return: wanted value or None""" 173 @return: wanted value or None"""
174 if entity_jid.resource: 174 if entity_jid.resource:
175 if not self.isInRoom(entity_jid, profile): 175 if not self.isInRoom(client, entity_jid):
176 # VCard are retrieved with bare jid 176 # VCard are retrieved with bare jid
177 # but MUC room is a special case 177 # but MUC room is a special case
178 entity_jid = jid.userhostJID() 178 entity_jid = jid.userhostJID()
179 try: 179 try:
180 data = self.host.memory.getEntityData(entity_jid, [name], profile) 180 data = self.host.memory.getEntityData(entity_jid, [name], client.profile)
181 except exceptions.UnknownEntityError: 181 except exceptions.UnknownEntityError:
182 return None 182 return None
183 return data.get(name) 183 return data.get(name)
184 184
185 def _getFilename(self, hash_): 185 def _getFilename(self, hash_):
215 image_hash = sha1(decoded).hexdigest() 215 image_hash = sha1(decoded).hexdigest()
216 self.saveAvatarFile(decoded, image_hash) 216 self.saveAvatarFile(decoded, image_hash)
217 return image_hash 217 return image_hash
218 218
219 @defer.inlineCallbacks 219 @defer.inlineCallbacks
220 def vCard2Dict(self, vcard, target, profile): 220 def vCard2Dict(self, client, vcard, target):
221 """Convert a VCard to a dict, and save binaries""" 221 """Convert a VCard to a dict, and save binaries"""
222 log.debug(_("parsing vcard")) 222 log.debug(_("parsing vcard"))
223 dictionary = {} 223 dictionary = {}
224 224
225 for elem in vcard.elements(): 225 for elem in vcard.elements():
226 if elem.name == 'FN': 226 if elem.name == 'FN':
227 dictionary['fullname'] = unicode(elem) 227 dictionary['fullname'] = unicode(elem)
228 elif elem.name == 'NICKNAME': 228 elif elem.name == 'NICKNAME':
229 dictionary['nick'] = unicode(elem) 229 dictionary['nick'] = unicode(elem)
230 self.updateCache(target, 'nick', dictionary['nick'], profile) 230 self.updateCache(client, target, 'nick', dictionary['nick'])
231 elif elem.name == 'URL': 231 elif elem.name == 'URL':
232 dictionary['website'] = unicode(elem) 232 dictionary['website'] = unicode(elem)
233 elif elem.name == 'EMAIL': 233 elif elem.name == 'EMAIL':
234 dictionary['email'] = unicode(elem) 234 dictionary['email'] = unicode(elem)
235 elif elem.name == 'BDAY': 235 elif elem.name == 'BDAY':
237 elif elem.name == 'PHOTO': 237 elif elem.name == 'PHOTO':
238 dictionary["avatar"] = yield threads.deferToThread(self.savePhoto, elem) 238 dictionary["avatar"] = yield threads.deferToThread(self.savePhoto, elem)
239 if not dictionary["avatar"]: # can happen in case of e.g. empty photo elem 239 if not dictionary["avatar"]: # can happen in case of e.g. empty photo elem
240 del dictionary['avatar'] 240 del dictionary['avatar']
241 else: 241 else:
242 self.updateCache(target, 'avatar', dictionary['avatar'], profile) 242 self.updateCache(client, target, 'avatar', dictionary['avatar'])
243 else: 243 else:
244 log.info(_('FIXME: [%s] VCard tag is not managed yet') % elem.name) 244 log.info(_('FIXME: [%s] VCard tag is not managed yet') % elem.name)
245 245
246 # if a data in cache doesn't exist anymore, we need to reset it 246 # if a data in cache doesn't exist anymore, we need to reset it
247 # so we check CACHED_DATA no gotten (i.e. not in dictionary keys) 247 # so we check CACHED_DATA no gotten (i.e. not in dictionary keys)
248 # and we reset them 248 # and we reset them
249 for datum in CACHED_DATA.difference(dictionary.keys()): 249 for datum in CACHED_DATA.difference(dictionary.keys()):
250 log.debug(u"reseting vcard datum [{datum}] for {entity}".format(datum=datum, entity=target.full())) 250 log.debug(u"reseting vcard datum [{datum}] for {entity}".format(datum=datum, entity=target.full()))
251 self.updateCache(target, datum, '', profile) 251 self.updateCache(client, target, datum, '')
252 252
253 defer.returnValue(dictionary) 253 defer.returnValue(dictionary)
254 254
255 def _VCardCb(self, answer, profile): 255 def _VCardCb(self, answer, client):
256 """Called after the first get IQ""" 256 """Called after the first get IQ"""
257 log.debug(_("VCard found")) 257 log.debug(_("VCard found"))
258 258
259 if answer.firstChildElement().name == "vCard": 259 if answer.firstChildElement().name == "vCard":
260 _jid, steam = self.host.getJidNStream(profile)
261 try: 260 try:
262 from_jid = jid.JID(answer["from"]) 261 from_jid = jid.JID(answer["from"])
263 except KeyError: 262 except KeyError:
264 from_jid = _jid.userhostJID() 263 from_jid = client.jid.userhostJID()
265 d = self.vCard2Dict(answer.firstChildElement(), from_jid, profile) 264 d = self.vCard2Dict(client, answer.firstChildElement(), from_jid)
266 d.addCallback(lambda data: self.host.bridge.actionResult("RESULT", answer['id'], data, profile)) 265 d.addCallback(lambda data: self.host.bridge.actionResult("RESULT", answer['id'], data, client.profile))
267 else: 266 else:
268 log.error(_("FIXME: vCard not found as first child element")) 267 log.error(_("FIXME: vCard not found as first child element"))
269 self.host.bridge.actionResult("SUPPRESS", answer['id'], {}, profile) # FIXME: maybe an error message would be better 268 self.host.bridge.actionResult("SUPPRESS", answer['id'], {}, client.profile) # FIXME: maybe an error message would be better
270 269
271 def _VCardEb(self, failure, profile): 270 def _VCardEb(self, failure, client):
272 """Called when something is wrong with registration""" 271 """Called when something is wrong with registration"""
273 try: 272 try:
274 self.host.bridge.actionResult("SUPPRESS", failure.value.stanza['id'], {}, profile) # FIXME: maybe an error message would be better 273 self.host.bridge.actionResult("SUPPRESS", failure.value.stanza['id'], {}, client.profile) # FIXME: maybe an error message would be better
275 log.warning(_(u"Can't find VCard of %s") % failure.value.stanza['from']) 274 log.warning(_(u"Can't find VCard of %s") % failure.value.stanza['from'])
276 self.updateCache(jid.JID(failure.value.stanza['from']), "avatar", '', profile) 275 self.updateCache(client, jid.JID(failure.value.stanza['from']), "avatar", '')
277 except (AttributeError, KeyError): 276 except (AttributeError, KeyError):
278 # 'ConnectionLost' object has no attribute 'stanza' + sometimes 'from' key doesn't exist 277 # 'ConnectionLost' object has no attribute 'stanza' + sometimes 'from' key doesn't exist
279 log.warning(_(u"Can't find VCard: %s") % failure.getErrorMessage()) 278 log.warning(_(u"Can't find VCard: %s") % failure.getErrorMessage())
280 279
281 def _getCard(self, target_s, profile_key=C.PROF_KEY_NONE): 280 def _getCard(self, target_s, profile_key=C.PROF_KEY_NONE):
282 return self.getCard(jid.JID(target_s), profile_key) 281 client = self.host.getClient(profile_key)
283 282 return self.getCard(client, jid.JID(target_s))
284 def getCard(self, target, profile_key=C.PROF_KEY_NONE): 283
284 def getCard(self, client, target):
285 """Ask server for VCard 285 """Ask server for VCard
286 286
287 @param target(jid.JID): jid from which we want the VCard 287 @param target(jid.JID): jid from which we want the VCard
288 @result: id to retrieve the profile 288 @result: id to retrieve the profile
289 """ 289 """
290 current_jid, xmlstream = self.host.getJidNStream(profile_key)
291 if not xmlstream:
292 raise exceptions.ProfileUnknownError('Asking vcard for a non-existant or not connected profile ({})'.format(profile_key))
293 profile = self.host.memory.getProfileName(profile_key)
294 to_jid = target.userhostJID() 290 to_jid = target.userhostJID()
295 log.debug(_(u"Asking for %s's VCard") % to_jid.userhost()) 291 log.debug(_(u"Asking for %s's VCard") % to_jid.userhost())
296 reg_request = IQ(xmlstream, 'get') 292 reg_request = client.IQ('get')
297 reg_request["from"] = current_jid.full() 293 reg_request["from"] = client.jid.full()
298 reg_request["to"] = to_jid.userhost() 294 reg_request["to"] = to_jid.userhost()
299 reg_request.addElement('vCard', NS_VCARD) 295 reg_request.addElement('vCard', NS_VCARD)
300 reg_request.send(to_jid.userhost()).addCallbacks(self._VCardCb, self._VCardEb, callbackArgs=[profile], errbackArgs=[profile]) 296 reg_request.send(to_jid.userhost()).addCallbacks(self._VCardCb, self._VCardEb, callbackArgs=[client], errbackArgs=[client])
301 return reg_request["id"] 297 return reg_request["id"]
302 298
303 def getAvatarFile(self, avatar_hash): 299 def getAvatarFile(self, avatar_hash):
304 """Give the full path of avatar from hash 300 """Give the full path of avatar from hash
305 @param hash: SHA1 hash 301 @param hash: SHA1 hash
352 d = threads.deferToThread(self._buildSetAvatar, vcard_set, filepath) 348 d = threads.deferToThread(self._buildSetAvatar, vcard_set, filepath)
353 349
354 def elementBuilt(result): 350 def elementBuilt(result):
355 """Called once the image is at the right size/format, and the vcard set element is build""" 351 """Called once the image is at the right size/format, and the vcard set element is build"""
356 set_avatar_elt, img_hash = result 352 set_avatar_elt, img_hash = result
357 self.updateCache(client.jid.userhostJID(), 'avatar', img_hash, client.profile) 353 self.updateCache(client, client.jid.userhostJID(), 'avatar', img_hash)
358 return set_avatar_elt.send().addCallback(lambda ignore: client.presence.available()) # FIXME: should send the current presence, not always "available" ! 354 return set_avatar_elt.send().addCallback(lambda ignore: client.presence.available()) # FIXME: should send the current presence, not always "available" !
359 355
360 d.addCallback(elementBuilt) 356 d.addCallback(elementBuilt)
361 357
362 return d 358 return d
383 379
384 Check for avatar information, and get VCard if needed 380 Check for avatar information, and get VCard if needed
385 @param presend(domish.Element): <presence/> stanza 381 @param presend(domish.Element): <presence/> stanza
386 """ 382 """
387 from_jid = jid.JID(presence['from']) 383 from_jid = jid.JID(presence['from'])
388 if from_jid.resource and not self.plugin_parent.isInRoom(from_jid, self.parent.profile): 384 if from_jid.resource and not self.plugin_parent.isInRoom(self.parent, from_jid):
389 from_jid = from_jid.userhostJID() 385 from_jid = from_jid.userhostJID()
390 #FIXME: wokkel's data_form should be used here 386 #FIXME: wokkel's data_form should be used here
391 try: 387 try:
392 x_elt = presence.elements(NS_VCARD_UPDATE, 'x').next() 388 x_elt = presence.elements(NS_VCARD_UPDATE, 'x').next()
393 except StopIteration: 389 except StopIteration:
399 return 395 return
400 396
401 hash_ = str(photo_elt) 397 hash_ = str(photo_elt)
402 if not hash_: 398 if not hash_:
403 return 399 return
404 old_avatar = self.plugin_parent.getCache(from_jid, 'avatar', self.parent.profile) 400 old_avatar = self.plugin_parent.getCache(self.parent, from_jid, 'avatar')
405 filename = self.plugin_parent._getFilename(hash_) 401 filename = self.plugin_parent._getFilename(hash_)
406 if not old_avatar or old_avatar != hash_: 402 if not old_avatar or old_avatar != hash_:
407 if os.path.exists(filename): 403 if os.path.exists(filename):
408 log.debug(u"New avatar found for [{}], it's already in cache, we use it".format(from_jid.full())) 404 log.debug(u"New avatar found for [{}], it's already in cache, we use it".format(from_jid.full()))
409 self.plugin_parent.updateCache(from_jid, 'avatar', hash_, self.parent.profile) 405 self.plugin_parent.updateCache(self.parent, from_jid, 'avatar', hash_)
410 else: 406 else:
411 log.debug(u'New avatar found for [{}], requesting vcard'.format(from_jid.full())) 407 log.debug(u'New avatar found for [{}], requesting vcard'.format(from_jid.full()))
412 self.plugin_parent.getCard(from_jid, self.parent.profile) 408 self.plugin_parent.getCard(from_jid, self.parent.profile)
413 else: 409 else:
414 if os.path.exists(filename): 410 if os.path.exists(filename):