images: add implementation for gif handling

- images: prefix the icon_hash with `a_`
 - pubsub.lazy_guild: ignore update_user when unitialized list data
This commit is contained in:
Luna Mendes 2018-11-21 20:01:16 -03:00
parent 2140f572e8
commit d2bedb75a4
2 changed files with 65 additions and 8 deletions

View File

@ -1,6 +1,8 @@
import os
import mimetypes import mimetypes
import asyncio import asyncio
import base64 import base64
import tempfile
from dataclasses import dataclass from dataclasses import dataclass
from hashlib import sha256 from hashlib import sha256
@ -10,6 +12,7 @@ from io import BytesIO
from logbook import Logger from logbook import Logger
from PIL import Image from PIL import Image
IMAGE_FOLDER = Path('./images') IMAGE_FOLDER = Path('./images')
log = Logger(__name__) log = Logger(__name__)
@ -228,10 +231,54 @@ class IconManager:
return await self.generic_get( return await self.generic_get(
'guild', guild_id, icon_hash, **kwargs) 'guild', guild_id, icon_hash, **kwargs)
async def _put_gif(self, scope: str, key, async def _resize_gif(self, scope: str, key,
raw_data: bytes, **kwargs) -> Icon: raw_data: bytes, target: tuple) -> tuple:
"""Insert a gif icon""" """Resize a GIF image."""
raise NotImplementedError # generate a temporary file to call gifsticle to and from.
input_fd, input_path = tempfile.mkstemp(suffix='.gif')
_, output_path = tempfile.mkstemp(suffix='.gif')
input_handler = os.fdopen(input_fd, 'wb')
# make sure its valid image data
data_fd = BytesIO(raw_data)
image = Image.open(data_fd)
image.close()
log.info('resizing a GIF from {} to {}',
image.size, target)
# insert image info on input_handler
input_handler.write(raw_data)
# call gifsicle under subprocess
log.debug('input: {}', input_path)
log.debug('output: {}', output_path)
process = await asyncio.create_subprocess_shell(
f'gifsicle --resize {target[0]}x{target[1]} '
f'{input_path} > {output_path}',
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
# run it, etc.
await process.communicate()
# write over an empty data_fd
data_fd = BytesIO()
output_handler = open(output_path, 'rb')
data_fd.write(output_handler.read())
# reseek, save to raw_data, reseek again.
# TODO: remove raw_data altogether as its inefficient
# to have two representations of the same bytes
data_fd.seek(0)
raw_data = data_fd.read()
data_fd.seek(0)
return data_fd, raw_data
async def put(self, scope: str, key: str, async def put(self, scope: str, key: str,
b64_data: str, **kwargs) -> Icon: b64_data: str, **kwargs) -> Icon:
@ -252,10 +299,10 @@ class IconManager:
# size management is different for gif files # size management is different for gif files
# as they're composed of multiple frames. # as they're composed of multiple frames.
if mime == 'image/gif': if 'size' in kwargs and mime == 'image/gif':
return await self._put_gif(scope, key, raw_data, **kwargs) data_fd, raw_data = await self._resize_gif(
scope, key, raw_data, kwargs['size'])
if 'size' in kwargs: elif 'size' in kwargs:
image = Image.open(data_fd) image = Image.open(data_fd)
want = kwargs['size'] want = kwargs['size']
@ -280,6 +327,9 @@ class IconManager:
if scope != 'emoji' if scope != 'emoji'
else None) else None)
if scope == 'user' and mime == 'image/gif':
icon_hash = f'a_{icon_hash}'
await self.storage.db.execute(""" await self.storage.db.execute("""
INSERT INTO icons (scope, key, hash, mime) INSERT INTO icons (scope, key, hash, mime)
VALUES ($1, $2, $3, $4) VALUES ($1, $2, $3, $4)

View File

@ -931,6 +931,13 @@ class GuildMemberList:
async def update_user(self, user_id: int): async def update_user(self, user_id: int):
"""Called for user updates such as avatar or username.""" """Called for user updates such as avatar or username."""
if not self.list:
return
if user_id not in self.list.members:
log.warning('lazy: ignoring unknown uid {}',
user_id)
return
# update user information inside self.list.members # update user information inside self.list.members
self.list.members[user_id]['user'] = \ self.list.members[user_id]['user'] = \