Mercurial > libervia-backend
comparison sat/plugins/plugin_xep_0054.py @ 3709:09f5ac48ffe3
merge bookmark @
author | Goffi <goffi@goffi.org> |
---|---|
date | Fri, 12 Nov 2021 17:21:24 +0100 |
parents | 36849fb5c854 |
children | 213e83a4ed10 |
comparison
equal
deleted
inserted
replaced
3684:8353cc3b8db9 | 3709:09f5ac48ffe3 |
---|---|
43 except: | 43 except: |
44 raise exceptions.MissingModule( | 44 raise exceptions.MissingModule( |
45 "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 | 47 |
48 try: | 48 from twisted.words.protocols.jabber.xmlstream import XMPPHandler |
49 from twisted.words.protocols.xmlstream import XMPPHandler | |
50 except ImportError: | |
51 from wokkel.subprotocols import XMPPHandler | |
52 | 49 |
53 IMPORT_NAME = "XEP-0054" | 50 IMPORT_NAME = "XEP-0054" |
54 | 51 |
55 PLUGIN_INFO = { | 52 PLUGIN_INFO = { |
56 C.PI_NAME: "XEP 0054 Plugin", | 53 C.PI_NAME: "XEP 0054 Plugin", |
107 async def profileConnecting(self, client): | 104 async def profileConnecting(self, client): |
108 client._xep_0054_avatar_hashes = persistent.PersistentDict( | 105 client._xep_0054_avatar_hashes = persistent.PersistentDict( |
109 NS_VCARD, client.profile) | 106 NS_VCARD, client.profile) |
110 await client._xep_0054_avatar_hashes.load() | 107 await client._xep_0054_avatar_hashes.load() |
111 | 108 |
112 def savePhoto(self, client, photo_elt, entity_jid): | 109 def savePhoto(self, client, photo_elt, entity): |
113 """Parse a <PHOTO> photo_elt and save the picture""" | 110 """Parse a <PHOTO> photo_elt and save the picture""" |
114 # XXX: this method is launched in a separate thread | 111 # XXX: this method is launched in a separate thread |
115 try: | 112 try: |
116 mime_type = str(next(photo_elt.elements(NS_VCARD, "TYPE"))) | 113 mime_type = str(next(photo_elt.elements(NS_VCARD, "TYPE"))) |
117 except StopIteration: | 114 except StopIteration: |
129 except StopIteration: | 126 except StopIteration: |
130 log.warning("BINVAL element not found") | 127 log.warning("BINVAL element not found") |
131 raise Failure(exceptions.NotFound()) | 128 raise Failure(exceptions.NotFound()) |
132 | 129 |
133 if not buf: | 130 if not buf: |
134 log.warning("empty avatar for {jid}".format(jid=entity_jid.full())) | 131 log.warning("empty avatar for {jid}".format(jid=entity.full())) |
135 raise Failure(exceptions.NotFound()) | 132 raise Failure(exceptions.NotFound()) |
136 | 133 |
137 log.debug(_("Decoding binary")) | 134 log.debug(_("Decoding binary")) |
138 decoded = b64decode(buf) | 135 decoded = b64decode(buf) |
139 del buf | 136 del buf |
140 | 137 |
141 if mime_type is None: | 138 if mime_type is None: |
142 log.debug( | 139 log.debug( |
143 f"no media type found specified for {entity_jid}'s avatar, trying to " | 140 f"no media type found specified for {entity}'s avatar, trying to " |
144 f"guess") | 141 f"guess") |
145 | 142 |
146 try: | 143 try: |
147 mime_type = image.guess_type(io.BytesIO(decoded)) | 144 mime_type = image.guess_type(io.BytesIO(decoded)) |
148 except IOError as e: | 145 except IOError as e: |
149 log.warning(f"Can't open avatar buffer: {e}") | 146 log.warning(f"Can't open avatar buffer: {e}") |
150 | 147 |
151 if mime_type is None: | 148 if mime_type is None: |
152 msg = f"Can't find media type for {entity_jid}'s avatar" | 149 msg = f"Can't find media type for {entity}'s avatar" |
153 log.warning(msg) | 150 log.warning(msg) |
154 raise Failure(exceptions.DataError(msg)) | 151 raise Failure(exceptions.DataError(msg)) |
155 | 152 |
156 image_hash = sha1(decoded).hexdigest() | 153 image_hash = sha1(decoded).hexdigest() |
157 with self.host.common_cache.cacheData( | 154 with self.host.common_cache.cacheData( |
433 return [disco.DiscoFeature(NS_VCARD)] | 430 return [disco.DiscoFeature(NS_VCARD)] |
434 | 431 |
435 def getDiscoItems(self, requestor, target, nodeIdentifier=""): | 432 def getDiscoItems(self, requestor, target, nodeIdentifier=""): |
436 return [] | 433 return [] |
437 | 434 |
438 def _checkAvatarHash(self, client, entity, given_hash): | |
439 """Check that hash in cache (i.e. computed hash) is the same as given one""" | |
440 # XXX: if they differ, the avatar will be requested on each connection | |
441 # TODO: try to avoid re-requesting avatar in this case | |
442 computed_hash = client._xep_0054_avatar_hashes[entity.full()] | |
443 if computed_hash != given_hash: | |
444 log.warning( | |
445 "computed hash differs from given hash for {entity}:\n" | |
446 "computed: {computed}\ngiven: {given}".format( | |
447 entity=entity, computed=computed_hash, given=given_hash | |
448 ) | |
449 ) | |
450 | |
451 async def update(self, presence): | 435 async def update(self, presence): |
452 """Called on <presence/> stanza with vcard data | 436 """Called on <presence/> stanza with vcard data |
453 | 437 |
454 Check for avatar information, and get VCard if needed | 438 Check for avatar information, and get VCard if needed |
455 @param presence(domish.Element): <presence/> stanza | 439 @param presence(domish.Element): <presence/> stanza |
466 try: | 450 try: |
467 photo_elt = next(x_elt.elements(NS_VCARD_UPDATE, "photo")) | 451 photo_elt = next(x_elt.elements(NS_VCARD_UPDATE, "photo")) |
468 except StopIteration: | 452 except StopIteration: |
469 return | 453 return |
470 | 454 |
471 new_hash = str(photo_elt).strip() | 455 given_hash = str(photo_elt).strip() |
472 if new_hash == HASH_SHA1_EMPTY: | 456 if given_hash == HASH_SHA1_EMPTY: |
473 new_hash = "" | 457 given_hash = "" |
474 | 458 |
475 hashes_cache = client._xep_0054_avatar_hashes | 459 hashes_cache = client._xep_0054_avatar_hashes |
476 | 460 |
477 old_hash = hashes_cache.get(entity_jid.full()) | 461 old_hash = hashes_cache.get(entity_jid.full()) |
478 | 462 |
479 if old_hash == new_hash: | 463 if old_hash == given_hash: |
480 # no change, we can return… | 464 # no change, we can return… |
481 if new_hash: | 465 if given_hash: |
482 # …but we double check that avatar is in cache | 466 # …but we double check that avatar is in cache |
483 avatar_cache = self.host.common_cache.getMetadata(new_hash) | 467 avatar_cache = self.host.common_cache.getMetadata(given_hash) |
484 if avatar_cache is None: | 468 if avatar_cache is None: |
485 log.debug( | 469 log.debug( |
486 f"Avatar for [{entity_jid}] is known but not in cache, we get " | 470 f"Avatar for [{entity_jid}] is known but not in cache, we get " |
487 f"it" | 471 f"it" |
488 ) | 472 ) |
490 await self.plugin_parent.getCard(client, entity_jid) | 474 await self.plugin_parent.getCard(client, entity_jid) |
491 else: | 475 else: |
492 log.debug(f"avatar for {entity_jid} is already in cache") | 476 log.debug(f"avatar for {entity_jid} is already in cache") |
493 return | 477 return |
494 | 478 |
495 if new_hash is None: | 479 if given_hash is None: |
496 # XXX: we use empty string to indicate that there is no avatar | 480 # XXX: we use empty string to indicate that there is no avatar |
497 new_hash = "" | 481 given_hash = "" |
498 | 482 |
499 await hashes_cache.aset(entity_jid.full(), new_hash) | 483 await hashes_cache.aset(entity_jid.full(), given_hash) |
500 | 484 |
501 if not new_hash: | 485 if not given_hash: |
502 await self.plugin_parent._i.update( | 486 await self.plugin_parent._i.update( |
503 client, IMPORT_NAME, "avatar", None, entity_jid) | 487 client, IMPORT_NAME, "avatar", None, entity_jid) |
504 # the avatar has been removed, no need to go further | 488 # the avatar has been removed, no need to go further |
505 return | 489 return |
506 | 490 |
507 avatar_cache = self.host.common_cache.getMetadata(new_hash) | 491 avatar_cache = self.host.common_cache.getMetadata(given_hash) |
508 if avatar_cache is not None: | 492 if avatar_cache is not None: |
509 log.debug( | 493 log.debug( |
510 f"New avatar found for [{entity_jid}], it's already in cache, we use it" | 494 f"New avatar found for [{entity_jid}], it's already in cache, we use it" |
511 ) | 495 ) |
512 await self.plugin_parent._i.update( | 496 await self.plugin_parent._i.update( |
514 IMPORT_NAME, "avatar", | 498 IMPORT_NAME, "avatar", |
515 { | 499 { |
516 'path': avatar_cache['path'], | 500 'path': avatar_cache['path'], |
517 'filename': avatar_cache['filename'], | 501 'filename': avatar_cache['filename'], |
518 'media_type': avatar_cache['mime_type'], | 502 'media_type': avatar_cache['mime_type'], |
519 'cache_uid': new_hash, | 503 'cache_uid': given_hash, |
520 }, | 504 }, |
521 entity_jid | 505 entity_jid |
522 ) | 506 ) |
523 else: | 507 else: |
524 log.debug( | 508 log.debug( |
526 ) | 510 ) |
527 vcard = await self.plugin_parent.getCard(client, entity_jid) | 511 vcard = await self.plugin_parent.getCard(client, entity_jid) |
528 if vcard is None: | 512 if vcard is None: |
529 log.warning(f"Unexpected empty vCard for {entity_jid}") | 513 log.warning(f"Unexpected empty vCard for {entity_jid}") |
530 return | 514 return |
531 await self._checkAvatarHash(client, entity_jid, new_hash) | 515 computed_hash = client._xep_0054_avatar_hashes[entity_jid.full()] |
516 if computed_hash != given_hash: | |
517 log.warning( | |
518 "computed hash differs from given hash for {entity}:\n" | |
519 "computed: {computed}\ngiven: {given}".format( | |
520 entity=entity_jid, computed=computed_hash, given=given_hash | |
521 ) | |
522 ) | |
532 | 523 |
533 def _update(self, presence): | 524 def _update(self, presence): |
534 defer.ensureDeferred(self.update(presence)) | 525 defer.ensureDeferred(self.update(presence)) |