Mercurial > libervia-backend
comparison sat/plugins/plugin_xep_0054.py @ 3816:213e83a4ed10
plugin identity, XEP-0054: move avatar resizing and caching method to identity plugin:
resizing and caching is now done in identity plugin, to prepare for the implementation of
other XEP to handle avatars.
rel 368
author | Goffi <goffi@goffi.org> |
---|---|
date | Wed, 29 Jun 2022 11:47:48 +0200 |
parents | 36849fb5c854 |
children | 524856bd7b19 |
comparison
equal
deleted
inserted
replaced
3815:853cbaf56e9e | 3816:213e83a4ed10 |
---|---|
51 | 51 |
52 PLUGIN_INFO = { | 52 PLUGIN_INFO = { |
53 C.PI_NAME: "XEP 0054 Plugin", | 53 C.PI_NAME: "XEP 0054 Plugin", |
54 C.PI_IMPORT_NAME: IMPORT_NAME, | 54 C.PI_IMPORT_NAME: IMPORT_NAME, |
55 C.PI_TYPE: "XEP", | 55 C.PI_TYPE: "XEP", |
56 C.PI_MODES: C.PLUG_MODE_BOTH, | |
56 C.PI_PROTOCOLS: ["XEP-0054", "XEP-0153"], | 57 C.PI_PROTOCOLS: ["XEP-0054", "XEP-0153"], |
57 C.PI_DEPENDENCIES: ["IDENTITY"], | 58 C.PI_DEPENDENCIES: ["IDENTITY"], |
58 C.PI_RECOMMENDATIONS: [], | 59 C.PI_RECOMMENDATIONS: [], |
59 C.PI_MAIN: "XEP_0054", | 60 C.PI_MAIN: "XEP_0054", |
60 C.PI_HANDLER: "yes", | 61 C.PI_HANDLER: "yes", |
61 C.PI_DESCRIPTION: _("""Implementation of vcard-temp"""), | 62 C.PI_DESCRIPTION: _("""Implementation of vcard-temp"""), |
62 } | 63 } |
63 | |
64 AVATAR_PATH = "avatars" | |
65 AVATAR_DIM = (128, 128) | |
66 | 64 |
67 IQ_GET = '/iq[@type="get"]' | 65 IQ_GET = '/iq[@type="get"]' |
68 NS_VCARD = "vcard-temp" | 66 NS_VCARD = "vcard-temp" |
69 VCARD_REQUEST = IQ_GET + '/vCard[@xmlns="' + NS_VCARD + '"]' # TODO: manage requests | 67 VCARD_REQUEST = IQ_GET + '/vCard[@xmlns="' + NS_VCARD + '"]' # TODO: manage requests |
70 | 68 |
316 | 314 |
317 avatar_cache = self.host.common_cache.getMetadata(avatar_hash) | 315 avatar_cache = self.host.common_cache.getMetadata(avatar_hash) |
318 return self._i.avatarBuildMetadata( | 316 return self._i.avatarBuildMetadata( |
319 avatar_cache['path'], avatar_cache['mime_type'], avatar_hash) | 317 avatar_cache['path'], avatar_cache['mime_type'], avatar_hash) |
320 | 318 |
321 def _buildSetAvatar(self, client, vcard_elt, avatar_data): | |
322 # XXX: this method is executed in a separate thread | |
323 if avatar_data["media_type"] == "image/svg+xml": | |
324 # for vector image, we save directly | |
325 img_buf = open(avatar_data["path"], "rb") | |
326 else: | |
327 # for bitmap image, we check size and resize if necessary | |
328 try: | |
329 img = Image.open(avatar_data["path"]) | |
330 except IOError as e: | |
331 raise exceptions.DataError(f"Can't open image: {e}") | |
332 | |
333 if img.size != AVATAR_DIM: | |
334 img.thumbnail(AVATAR_DIM) | |
335 if img.size[0] != img.size[1]: # we need to crop first | |
336 left, upper = (0, 0) | |
337 right, lower = img.size | |
338 offset = abs(right - lower) / 2 | |
339 if right == min(img.size): | |
340 upper += offset | |
341 lower -= offset | |
342 else: | |
343 left += offset | |
344 right -= offset | |
345 img = img.crop((left, upper, right, lower)) | |
346 img_buf = io.BytesIO() | |
347 # PNG is well supported among clients, so we convert to this format | |
348 img.save(img_buf, "PNG") | |
349 img_buf.seek(0) | |
350 avatar_data["media_type"] = "image/png" | |
351 | |
352 media_type = avatar_data["media_type"] | |
353 photo_elt = vcard_elt.addElement("PHOTO") | |
354 photo_elt.addElement("TYPE", content=media_type) | |
355 image_b64 = b64encode(img_buf.read()).decode() | |
356 img_buf.seek(0) | |
357 photo_elt.addElement("BINVAL", content=image_b64) | |
358 image_hash = sha1(img_buf.read()).hexdigest() | |
359 img_buf.seek(0) | |
360 with self.host.common_cache.cacheData( | |
361 PLUGIN_INFO["import_name"], image_hash, media_type | |
362 ) as f: | |
363 f.write(img_buf.read()) | |
364 avatar_data['path'] = Path(f.name) | |
365 avatar_data['filename'] = avatar_data['path'].name | |
366 avatar_data['cache_uid'] = image_hash | |
367 return image_hash | |
368 | |
369 async def setAvatar(self, client, avatar_data, entity): | 319 async def setAvatar(self, client, avatar_data, entity): |
370 """Set avatar of the profile | 320 """Set avatar of the profile |
371 | 321 |
372 @param avatar_data(dict): data of the image to use as avatar, as built by | 322 @param avatar_data(dict): data of the image to use as avatar, as built by |
373 IDENTITY plugin. | 323 IDENTITY plugin. |
375 """ | 325 """ |
376 vcard_elt = await self.updateVCardElt(client, entity, ['PHOTO']) | 326 vcard_elt = await self.updateVCardElt(client, entity, ['PHOTO']) |
377 | 327 |
378 iq_elt = client.IQ() | 328 iq_elt = client.IQ() |
379 iq_elt.addChild(vcard_elt) | 329 iq_elt.addChild(vcard_elt) |
380 await threads.deferToThread( | 330 # metadata with encoded image are now filled at the right size/format |
381 self._buildSetAvatar, client, vcard_elt, avatar_data | 331 photo_elt = vcard_elt.addElement("PHOTO") |
382 ) | 332 photo_elt.addElement("TYPE", content=avatar_data["media_type"]) |
383 # image is now at the right size/format | 333 photo_elt.addElement("BINVAL", content=avatar_data["base64"]) |
384 | 334 |
385 await iq_elt.send() | 335 await iq_elt.send() |
386 | 336 |
387 # FIXME: should send the current presence, not always "available" ! | 337 # FIXME: should send the current presence, not always "available" ! |
388 await client.presence.available() | 338 await client.presence.available() |