comparison sat/plugins/plugin_xep_0054.py @ 3028:ab2696e34d29

Python 3 port: /!\ this is a huge commit /!\ starting from this commit, SàT is needs Python 3.6+ /!\ SàT maybe be instable or some feature may not work anymore, this will improve with time This patch port backend, bridge and frontends to Python 3. Roughly this has been done this way: - 2to3 tools has been applied (with python 3.7) - all references to python2 have been replaced with python3 (notably shebangs) - fixed files not handled by 2to3 (notably the shell script) - several manual fixes - fixed issues reported by Python 3 that where not handled in Python 2 - replaced "async" with "async_" when needed (it's a reserved word from Python 3.7) - replaced zope's "implements" with @implementer decorator - temporary hack to handle data pickled in database, as str or bytes may be returned, to be checked later - fixed hash comparison for password - removed some code which is not needed anymore with Python 3 - deactivated some code which needs to be checked (notably certificate validation) - tested with jp, fixed reported issues until some basic commands worked - ported Primitivus (after porting dependencies like urwid satext) - more manual fixes
author Goffi <goffi@goffi.org>
date Tue, 13 Aug 2019 19:08:41 +0200
parents e624550d5c24
children fee60f17ebac
comparison
equal deleted inserted replaced
3027:ff5bcb12ae60 3028:ab2696e34d29
1 #!/usr/bin/env python2 1 #!/usr/bin/env python3
2 # -*- coding: utf-8 -*- 2 # -*- coding: utf-8 -*-
3 3
4 # SAT plugin for managing xep-0054 4 # SAT plugin for managing xep-0054
5 # Copyright (C) 2009-2019 Jérôme Poisson (goffi@goffi.org) 5 # Copyright (C) 2009-2019 Jérôme Poisson (goffi@goffi.org)
6 # Copyright (C) 2014 Emmanuel Gil Peyrot (linkmauve@linkmauve.fr) 6 # Copyright (C) 2014 Emmanuel Gil Peyrot (linkmauve@linkmauve.fr)
26 from twisted.internet import threads, defer 26 from twisted.internet import threads, defer
27 from twisted.words.protocols.jabber import jid, error 27 from twisted.words.protocols.jabber import jid, error
28 from twisted.words.xish import domish 28 from twisted.words.xish import domish
29 from twisted.python.failure import Failure 29 from twisted.python.failure import Failure
30 30
31 from zope.interface import implements 31 from zope.interface import implementer
32 32
33 from wokkel import disco, iwokkel 33 from wokkel import disco, iwokkel
34 34
35 from base64 import b64decode, b64encode 35 from base64 import b64decode, b64encode
36 from hashlib import sha1 36 from hashlib import sha1
40 40
41 try: 41 try:
42 from PIL import Image 42 from PIL import Image
43 except: 43 except:
44 raise exceptions.MissingModule( 44 raise exceptions.MissingModule(
45 u"Missing module pillow, please download/install it from https://python-pillow.github.io" 45 "Missing module pillow, please download/install it from https://python-pillow.github.io"
46 ) 46 )
47 from cStringIO import StringIO 47 from io import StringIO
48 48
49 try: 49 try:
50 from twisted.words.protocols.xmlstream import XMPPHandler 50 from twisted.words.protocols.xmlstream import XMPPHandler
51 except ImportError: 51 except ImportError:
52 from wokkel.subprotocols import XMPPHandler 52 from wokkel.subprotocols import XMPPHandler
82 # TODO: - check that nickname is ok 82 # TODO: - check that nickname is ok
83 # - refactor the code/better use of Wokkel 83 # - refactor the code/better use of Wokkel
84 # - get missing values 84 # - get missing values
85 85
86 def __init__(self, host): 86 def __init__(self, host):
87 log.info(_(u"Plugin XEP_0054 initialization")) 87 log.info(_("Plugin XEP_0054 initialization"))
88 self.host = host 88 self.host = host
89 host.bridge.addMethod( 89 host.bridge.addMethod(
90 u"avatarGet", 90 "avatarGet",
91 u".plugin", 91 ".plugin",
92 in_sign=u"sbbs", 92 in_sign="sbbs",
93 out_sign=u"s", 93 out_sign="s",
94 method=self._getAvatar, 94 method=self._getAvatar,
95 async=True, 95 async_=True,
96 ) 96 )
97 host.bridge.addMethod( 97 host.bridge.addMethod(
98 u"avatarSet", 98 "avatarSet",
99 u".plugin", 99 ".plugin",
100 in_sign=u"ss", 100 in_sign="ss",
101 out_sign=u"", 101 out_sign="",
102 method=self._setAvatar, 102 method=self._setAvatar,
103 async=True, 103 async_=True,
104 ) 104 )
105 host.trigger.add(u"presence_available", self.presenceAvailableTrigger) 105 host.trigger.add("presence_available", self.presenceAvailableTrigger)
106 host.memory.setSignalOnUpdate(u"avatar") 106 host.memory.setSignalOnUpdate("avatar")
107 host.memory.setSignalOnUpdate(u"nick") 107 host.memory.setSignalOnUpdate("nick")
108 108
109 def getHandler(self, client): 109 def getHandler(self, client):
110 return XEP_0054_handler(self) 110 return XEP_0054_handler(self)
111 111
112 def isRoom(self, client, entity_jid): 112 def isRoom(self, client, entity_jid):
141 def presenceAvailableTrigger(self, presence_elt, client): 141 def presenceAvailableTrigger(self, presence_elt, client):
142 if client.jid.userhost() in client._cache_0054: 142 if client.jid.userhost() in client._cache_0054:
143 try: 143 try:
144 avatar_hash = client._cache_0054[client.jid.userhost()]["avatar"] 144 avatar_hash = client._cache_0054[client.jid.userhost()]["avatar"]
145 except KeyError: 145 except KeyError:
146 log.info(u"No avatar in cache for {}".format(client.jid.userhost())) 146 log.info("No avatar in cache for {}".format(client.jid.userhost()))
147 return True 147 return True
148 x_elt = domish.Element((NS_VCARD_UPDATE, "x")) 148 x_elt = domish.Element((NS_VCARD_UPDATE, "x"))
149 x_elt.addElement("photo", content=avatar_hash) 149 x_elt.addElement("photo", content=avatar_hash)
150 presence_elt.addChild(x_elt) 150 presence_elt.addChild(x_elt)
151 return True 151 return True
163 # Hashes should be shared between profiles (or not ? what 163 # Hashes should be shared between profiles (or not ? what
164 # if the avatar is different depending on who is requesting it 164 # if the avatar is different depending on who is requesting it
165 # this is not possible with vcard-tmp, but it is with XEP-0084). 165 # this is not possible with vcard-tmp, but it is with XEP-0084).
166 # Loading avatar on demand per jid may be an option to investigate. 166 # Loading avatar on demand per jid may be an option to investigate.
167 client = self.host.getClient(profile) 167 client = self.host.getClient(profile)
168 for jid_s, data in client._cache_0054.iteritems(): 168 for jid_s, data in client._cache_0054.items():
169 jid_ = jid.JID(jid_s) 169 jid_ = jid.JID(jid_s)
170 for name in CACHED_DATA: 170 for name in CACHED_DATA:
171 try: 171 try:
172 value = data[name] 172 value = data[name]
173 if value is None: 173 if value is None:
174 log.error( 174 log.error(
175 u"{name} value for {jid_} is None, ignoring".format( 175 "{name} value for {jid_} is None, ignoring".format(
176 name=name, jid_=jid_ 176 name=name, jid_=jid_
177 ) 177 )
178 ) 178 )
179 continue 179 continue
180 self.host.memory.updateEntityData( 180 self.host.memory.updateEntityData(
230 230
231 def savePhoto(self, client, photo_elt, entity_jid): 231 def savePhoto(self, client, photo_elt, entity_jid):
232 """Parse a <PHOTO> photo_elt and save the picture""" 232 """Parse a <PHOTO> photo_elt and save the picture"""
233 # XXX: this method is launched in a separate thread 233 # XXX: this method is launched in a separate thread
234 try: 234 try:
235 mime_type = unicode(photo_elt.elements(NS_VCARD, "TYPE").next()) 235 mime_type = str(next(photo_elt.elements(NS_VCARD, "TYPE")))
236 except StopIteration: 236 except StopIteration:
237 mime_type = None 237 mime_type = None
238 else: 238 else:
239 if not mime_type: 239 if not mime_type:
240 # MIME type not know, we'll only support PNG files 240 # MIME type not know, we'll only support PNG files
246 # XXX: this old MIME type is still used by some clients 246 # XXX: this old MIME type is still used by some clients
247 mime_type = "image/png" 247 mime_type = "image/png"
248 else: 248 else:
249 # TODO: handle other image formats (svg?) 249 # TODO: handle other image formats (svg?)
250 log.warning( 250 log.warning(
251 u"following avatar image format is not handled: {type} [{jid}]".format( 251 "following avatar image format is not handled: {type} [{jid}]".format(
252 type=mime_type, jid=entity_jid.full() 252 type=mime_type, jid=entity_jid.full()
253 ) 253 )
254 ) 254 )
255 raise Failure(exceptions.DataError()) 255 raise Failure(exceptions.DataError())
256 256
257 ext = mimetypes.guess_extension(mime_type, strict=False) 257 ext = mimetypes.guess_extension(mime_type, strict=False)
258 assert ext is not None 258 assert ext is not None
259 if ext == u".jpe": 259 if ext == ".jpe":
260 ext = u".jpg" 260 ext = ".jpg"
261 log.debug( 261 log.debug(
262 u"photo of type {type} with extension {ext} found [{jid}]".format( 262 "photo of type {type} with extension {ext} found [{jid}]".format(
263 type=mime_type, ext=ext, jid=entity_jid.full() 263 type=mime_type, ext=ext, jid=entity_jid.full()
264 ) 264 )
265 ) 265 )
266 try: 266 try:
267 buf = str(photo_elt.elements(NS_VCARD, "BINVAL").next()) 267 buf = str(next(photo_elt.elements(NS_VCARD, "BINVAL")))
268 except StopIteration: 268 except StopIteration:
269 log.warning(u"BINVAL element not found") 269 log.warning("BINVAL element not found")
270 raise Failure(exceptions.NotFound()) 270 raise Failure(exceptions.NotFound())
271 if not buf: 271 if not buf:
272 log.warning(u"empty avatar for {jid}".format(jid=entity_jid.full())) 272 log.warning("empty avatar for {jid}".format(jid=entity_jid.full()))
273 raise Failure(exceptions.NotFound()) 273 raise Failure(exceptions.NotFound())
274 if mime_type is None: 274 if mime_type is None:
275 log.warning(_(u"no MIME type found for {entity}'s avatar, assuming image/png") 275 log.warning(_("no MIME type found for {entity}'s avatar, assuming image/png")
276 .format(entity=entity_jid.full())) 276 .format(entity=entity_jid.full()))
277 if buf[:8] != b'\x89\x50\x4e\x47\x0d\x0a\x1a\x0a': 277 if buf[:8] != b'\x89\x50\x4e\x47\x0d\x0a\x1a\x0a':
278 log.warning(u"this is not a PNG file, ignoring it") 278 log.warning("this is not a PNG file, ignoring it")
279 raise Failure(exceptions.DataError()) 279 raise Failure(exceptions.DataError())
280 else: 280 else:
281 mime_type = u"image/png" 281 mime_type = "image/png"
282 282
283 log.debug(_(u"Decoding binary")) 283 log.debug(_("Decoding binary"))
284 decoded = b64decode(buf) 284 decoded = b64decode(buf)
285 del buf 285 del buf
286 image_hash = sha1(decoded).hexdigest() 286 image_hash = sha1(decoded).hexdigest()
287 with client.cache.cacheData( 287 with client.cache.cacheData(
288 PLUGIN_INFO["import_name"], 288 PLUGIN_INFO["import_name"],
295 return image_hash 295 return image_hash
296 296
297 @defer.inlineCallbacks 297 @defer.inlineCallbacks
298 def vCard2Dict(self, client, vcard, entity_jid): 298 def vCard2Dict(self, client, vcard, entity_jid):
299 """Convert a VCard to a dict, and save binaries""" 299 """Convert a VCard to a dict, and save binaries"""
300 log.debug((u"parsing vcard")) 300 log.debug(("parsing vcard"))
301 vcard_dict = {} 301 vcard_dict = {}
302 302
303 for elem in vcard.elements(): 303 for elem in vcard.elements():
304 if elem.name == "FN": 304 if elem.name == "FN":
305 vcard_dict["fullname"] = unicode(elem) 305 vcard_dict["fullname"] = str(elem)
306 elif elem.name == "NICKNAME": 306 elif elem.name == "NICKNAME":
307 vcard_dict["nick"] = unicode(elem) 307 vcard_dict["nick"] = str(elem)
308 self.updateCache(client, entity_jid, "nick", vcard_dict["nick"]) 308 self.updateCache(client, entity_jid, "nick", vcard_dict["nick"])
309 elif elem.name == "URL": 309 elif elem.name == "URL":
310 vcard_dict["website"] = unicode(elem) 310 vcard_dict["website"] = str(elem)
311 elif elem.name == "EMAIL": 311 elif elem.name == "EMAIL":
312 vcard_dict["email"] = unicode(elem) 312 vcard_dict["email"] = str(elem)
313 elif elem.name == "BDAY": 313 elif elem.name == "BDAY":
314 vcard_dict["birthday"] = unicode(elem) 314 vcard_dict["birthday"] = str(elem)
315 elif elem.name == "PHOTO": 315 elif elem.name == "PHOTO":
316 # TODO: handle EXTVAL 316 # TODO: handle EXTVAL
317 try: 317 try:
318 avatar_hash = yield threads.deferToThread( 318 avatar_hash = yield threads.deferToThread(
319 self.savePhoto, client, elem, entity_jid 319 self.savePhoto, client, elem, entity_jid
320 ) 320 )
321 except (exceptions.DataError, exceptions.NotFound) as e: 321 except (exceptions.DataError, exceptions.NotFound) as e:
322 avatar_hash = "" 322 avatar_hash = ""
323 vcard_dict["avatar"] = avatar_hash 323 vcard_dict["avatar"] = avatar_hash
324 except Exception as e: 324 except Exception as e:
325 log.error(u"avatar saving error: {}".format(e)) 325 log.error("avatar saving error: {}".format(e))
326 avatar_hash = None 326 avatar_hash = None
327 else: 327 else:
328 vcard_dict["avatar"] = avatar_hash 328 vcard_dict["avatar"] = avatar_hash
329 self.updateCache(client, entity_jid, "avatar", avatar_hash) 329 self.updateCache(client, entity_jid, "avatar", avatar_hash)
330 else: 330 else:
331 log.debug(u"FIXME: [{}] VCard tag is not managed yet".format(elem.name)) 331 log.debug("FIXME: [{}] VCard tag is not managed yet".format(elem.name))
332 332
333 # if a data in cache doesn't exist anymore, we need to delete it 333 # if a data in cache doesn't exist anymore, we need to delete it
334 # so we check CACHED_DATA no gotten (i.e. not in vcard_dict keys) 334 # so we check CACHED_DATA no gotten (i.e. not in vcard_dict keys)
335 # and we reset them 335 # and we reset them
336 for datum in CACHED_DATA.difference(vcard_dict.keys()): 336 for datum in CACHED_DATA.difference(list(vcard_dict.keys())):
337 log.debug( 337 log.debug(
338 u"reseting vcard datum [{datum}] for {entity}".format( 338 "reseting vcard datum [{datum}] for {entity}".format(
339 datum=datum, entity=entity_jid.full() 339 datum=datum, entity=entity_jid.full()
340 ) 340 )
341 ) 341 )
342 self.updateCache(client, entity_jid, datum, None) 342 self.updateCache(client, entity_jid, datum, None)
343 343
355 return d 355 return d
356 356
357 def _vCardEb(self, failure_, to_jid, client): 357 def _vCardEb(self, failure_, to_jid, client):
358 """Called when something is wrong with registration""" 358 """Called when something is wrong with registration"""
359 log.warning( 359 log.warning(
360 u"Can't get vCard for {jid}: {failure}".format( 360 "Can't get vCard for {jid}: {failure}".format(
361 jid=to_jid.full, failure=failure_ 361 jid=to_jid.full, failure=failure_
362 ) 362 )
363 ) 363 )
364 self.updateCache(client, to_jid, "avatar", None) 364 self.updateCache(client, to_jid, "avatar", None)
365 365
366 def _getVcardElt(self, iq_elt): 366 def _getVcardElt(self, iq_elt):
367 return iq_elt.elements(NS_VCARD, "vCard").next() 367 return next(iq_elt.elements(NS_VCARD, "vCard"))
368 368
369 def getCardRaw(self, client, entity_jid): 369 def getCardRaw(self, client, entity_jid):
370 """get raw vCard XML 370 """get raw vCard XML
371 371
372 params are as in [getCard] 372 params are as in [getCard]
373 """ 373 """
374 entity_jid = self.getBareOrFull(client, entity_jid) 374 entity_jid = self.getBareOrFull(client, entity_jid)
375 log.debug(u"Asking for {}'s VCard".format(entity_jid.full())) 375 log.debug("Asking for {}'s VCard".format(entity_jid.full()))
376 reg_request = client.IQ("get") 376 reg_request = client.IQ("get")
377 reg_request["from"] = client.jid.full() 377 reg_request["from"] = client.jid.full()
378 reg_request["to"] = entity_jid.full() 378 reg_request["to"] = entity_jid.full()
379 reg_request.addElement("vCard", NS_VCARD) 379 reg_request.addElement("vCard", NS_VCARD)
380 d = reg_request.send(entity_jid.full()) 380 d = reg_request.send(entity_jid.full())
433 if full_path is None: 433 if full_path is None:
434 # cache file is not available (probably expired) 434 # cache file is not available (probably expired)
435 raise KeyError 435 raise KeyError
436 else: 436 else:
437 # avatar has already been checked but it is not set 437 # avatar has already been checked but it is not set
438 full_path = u"" 438 full_path = ""
439 except KeyError: 439 except KeyError:
440 # avatar is not in cache 440 # avatar is not in cache
441 if cache_only: 441 if cache_only:
442 return defer.fail(Failure(exceptions.NotFound())) 442 return defer.fail(Failure(exceptions.NotFound()))
443 # we request vCard to get avatar 443 # we request vCard to get avatar
460 """get nick from cache, or check vCard 460 """get nick from cache, or check vCard
461 461
462 @param entity(jid.JID): entity to get nick from 462 @param entity(jid.JID): entity to get nick from
463 @return(unicode, None): nick or None if not found 463 @return(unicode, None): nick or None if not found
464 """ 464 """
465 nick = self.getCache(client, entity, u"nick") 465 nick = self.getCache(client, entity, "nick")
466 if nick is not None: 466 if nick is not None:
467 defer.returnValue(nick) 467 defer.returnValue(nick)
468 yield self.getCard(client, entity) 468 yield self.getCard(client, entity)
469 defer.returnValue(self.getCache(client, entity, u"nick")) 469 defer.returnValue(self.getCache(client, entity, "nick"))
470 470
471 @defer.inlineCallbacks 471 @defer.inlineCallbacks
472 def setNick(self, client, nick): 472 def setNick(self, client, nick):
473 """update our vCard and set a nickname 473 """update our vCard and set a nickname
474 474
481 if e.condition == "item-not-found": 481 if e.condition == "item-not-found":
482 vcard_elt = domish.Element((NS_VCARD, "vCard")) 482 vcard_elt = domish.Element((NS_VCARD, "vCard"))
483 else: 483 else:
484 raise e 484 raise e
485 try: 485 try:
486 nickname_elt = next(vcard_elt.elements(NS_VCARD, u"NICKNAME")) 486 nickname_elt = next(vcard_elt.elements(NS_VCARD, "NICKNAME"))
487 except StopIteration: 487 except StopIteration:
488 pass 488 pass
489 else: 489 else:
490 vcard_elt.children.remove(nickname_elt) 490 vcard_elt.children.remove(nickname_elt)
491 491
492 nickname_elt = vcard_elt.addElement((NS_VCARD, u"NICKNAME"), content=nick) 492 nickname_elt = vcard_elt.addElement((NS_VCARD, "NICKNAME"), content=nick)
493 iq_elt = client.IQ() 493 iq_elt = client.IQ()
494 vcard_elt = iq_elt.addChild(vcard_elt) 494 vcard_elt = iq_elt.addChild(vcard_elt)
495 yield iq_elt.send() 495 yield iq_elt.send()
496 self.updateCache(client, jid_, u"nick", unicode(nick)) 496 self.updateCache(client, jid_, "nick", str(nick))
497 497
498 def _buildSetAvatar(self, client, vcard_elt, file_path): 498 def _buildSetAvatar(self, client, vcard_elt, file_path):
499 # XXX: this method is executed in a separate thread 499 # XXX: this method is executed in a separate thread
500 try: 500 try:
501 img = Image.open(file_path) 501 img = Image.open(file_path)
502 except IOError: 502 except IOError:
503 return Failure(exceptions.DataError(u"Can't open image")) 503 return Failure(exceptions.DataError("Can't open image"))
504 504
505 if img.size != AVATAR_DIM: 505 if img.size != AVATAR_DIM:
506 img.thumbnail(AVATAR_DIM) 506 img.thumbnail(AVATAR_DIM)
507 if img.size[0] != img.size[1]: # we need to crop first 507 if img.size[0] != img.size[1]: # we need to crop first
508 left, upper = (0, 0) 508 left, upper = (0, 0)
547 else: 547 else:
548 raise e 548 raise e
549 else: 549 else:
550 # the vcard exists, we need to remove PHOTO element as we'll make a new one 550 # the vcard exists, we need to remove PHOTO element as we'll make a new one
551 try: 551 try:
552 photo_elt = next(vcard_elt.elements(NS_VCARD, u"PHOTO")) 552 photo_elt = next(vcard_elt.elements(NS_VCARD, "PHOTO"))
553 except StopIteration: 553 except StopIteration:
554 pass 554 pass
555 else: 555 else:
556 vcard_elt.children.remove(photo_elt) 556 vcard_elt.children.remove(photo_elt)
557 557
565 self.updateCache(client, client.jid.userhostJID(), "avatar", image_hash) 565 self.updateCache(client, client.jid.userhostJID(), "avatar", image_hash)
566 yield iq_elt.send() 566 yield iq_elt.send()
567 client.presence.available() # FIXME: should send the current presence, not always "available" ! 567 client.presence.available() # FIXME: should send the current presence, not always "available" !
568 568
569 569
570 @implementer(iwokkel.IDisco)
570 class XEP_0054_handler(XMPPHandler): 571 class XEP_0054_handler(XMPPHandler):
571 implements(iwokkel.IDisco)
572 572
573 def __init__(self, plugin_parent): 573 def __init__(self, plugin_parent):
574 self.plugin_parent = plugin_parent 574 self.plugin_parent = plugin_parent
575 self.host = plugin_parent.host 575 self.host = plugin_parent.host
576 576
588 # XXX: if they differ, the avater will be requested on each connection 588 # XXX: if they differ, the avater will be requested on each connection
589 # TODO: try to avoid re-requesting avatar in this case 589 # TODO: try to avoid re-requesting avatar in this case
590 computed_hash = self.plugin_parent.getCache(client, entity, "avatar") 590 computed_hash = self.plugin_parent.getCache(client, entity, "avatar")
591 if computed_hash != given_hash: 591 if computed_hash != given_hash:
592 log.warning( 592 log.warning(
593 u"computed hash differs from given hash for {entity}:\n" 593 "computed hash differs from given hash for {entity}:\n"
594 "computed: {computed}\ngiven: {given}".format( 594 "computed: {computed}\ngiven: {given}".format(
595 entity=entity, computed=computed_hash, given=given_hash 595 entity=entity, computed=computed_hash, given=given_hash
596 ) 596 )
597 ) 597 )
598 598
604 """ 604 """
605 client = self.parent 605 client = self.parent
606 entity_jid = self.plugin_parent.getBareOrFull(client, jid.JID(presence["from"])) 606 entity_jid = self.plugin_parent.getBareOrFull(client, jid.JID(presence["from"]))
607 # FIXME: wokkel's data_form should be used here 607 # FIXME: wokkel's data_form should be used here
608 try: 608 try:
609 x_elt = presence.elements(NS_VCARD_UPDATE, "x").next() 609 x_elt = next(presence.elements(NS_VCARD_UPDATE, "x"))
610 except StopIteration: 610 except StopIteration:
611 return 611 return
612 612
613 try: 613 try:
614 photo_elt = x_elt.elements(NS_VCARD_UPDATE, "photo").next() 614 photo_elt = next(x_elt.elements(NS_VCARD_UPDATE, "photo"))
615 except StopIteration: 615 except StopIteration:
616 return 616 return
617 617
618 hash_ = unicode(photo_elt).strip() 618 hash_ = str(photo_elt).strip()
619 if hash_ == C.HASH_SHA1_EMPTY: 619 if hash_ == C.HASH_SHA1_EMPTY:
620 hash_ = u"" 620 hash_ = ""
621 old_avatar = self.plugin_parent.getCache(client, entity_jid, "avatar") 621 old_avatar = self.plugin_parent.getCache(client, entity_jid, "avatar")
622 622
623 if old_avatar == hash_: 623 if old_avatar == hash_:
624 # no change, we can return... 624 # no change, we can return...
625 if hash_: 625 if hash_:
626 # ...but we double check that avatar is in cache 626 # ...but we double check that avatar is in cache
627 file_path = client.cache.getFilePath(hash_) 627 file_path = client.cache.getFilePath(hash_)
628 if file_path is None: 628 if file_path is None:
629 log.error( 629 log.error(
630 u"Avatar for [{}] should be in cache but it is not! We get it".format( 630 "Avatar for [{}] should be in cache but it is not! We get it".format(
631 entity_jid.full() 631 entity_jid.full()
632 ) 632 )
633 ) 633 )
634 self.plugin_parent.getCard(client, entity_jid) 634 self.plugin_parent.getCard(client, entity_jid)
635 else: 635 else:
636 log.debug(u"avatar for {} already in cache".format(entity_jid.full())) 636 log.debug("avatar for {} already in cache".format(entity_jid.full()))
637 return 637 return
638 638
639 if not hash_: 639 if not hash_:
640 # the avatar has been removed 640 # the avatar has been removed
641 # XXX: we use empty string instead of None to indicate that we took avatar 641 # XXX: we use empty string instead of None to indicate that we took avatar
644 return 644 return
645 645
646 file_path = client.cache.getFilePath(hash_) 646 file_path = client.cache.getFilePath(hash_)
647 if file_path is not None: 647 if file_path is not None:
648 log.debug( 648 log.debug(
649 u"New avatar found for [{}], it's already in cache, we use it".format( 649 "New avatar found for [{}], it's already in cache, we use it".format(
650 entity_jid.full() 650 entity_jid.full()
651 ) 651 )
652 ) 652 )
653 self.plugin_parent.updateCache(client, entity_jid, "avatar", hash_) 653 self.plugin_parent.updateCache(client, entity_jid, "avatar", hash_)
654 else: 654 else:
655 log.debug( 655 log.debug(
656 u"New avatar found for [{}], requesting vcard".format(entity_jid.full()) 656 "New avatar found for [{}], requesting vcard".format(entity_jid.full())
657 ) 657 )
658 d = self.plugin_parent.getCard(client, entity_jid) 658 d = self.plugin_parent.getCard(client, entity_jid)
659 d.addCallback(self._checkAvatarHash, client, entity_jid, hash_) 659 d.addCallback(self._checkAvatarHash, client, entity_jid, hash_)