Return ThumbnailInfo in more places (#16438)

Improves type hints by using concrete types instead of
dictionaries.
This commit is contained in:
Patrick Cloke 2023-10-06 10:12:43 -04:00 committed by GitHub
parent cabd577460
commit 7615e2bf48
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 90 additions and 80 deletions

1
changelog.d/16438.misc Normal file
View file

@ -0,0 +1 @@
Reduce memory allocations.

View file

@ -332,7 +332,7 @@ class ThumbnailInfo:
# Content type of thumbnail, e.g. image/png # Content type of thumbnail, e.g. image/png
type: str type: str
# The size of the media file, in bytes. # The size of the media file, in bytes.
length: Optional[int] = None length: int
@attr.s(slots=True, frozen=True, auto_attribs=True) @attr.s(slots=True, frozen=True, auto_attribs=True)

View file

@ -624,6 +624,7 @@ class MediaRepository:
height=t_height, height=t_height,
method=t_method, method=t_method,
type=t_type, type=t_type,
length=t_byte_source.tell(),
), ),
) )
@ -694,6 +695,7 @@ class MediaRepository:
height=t_height, height=t_height,
method=t_method, method=t_method,
type=t_type, type=t_type,
length=t_byte_source.tell(),
), ),
) )
@ -839,6 +841,7 @@ class MediaRepository:
height=t_height, height=t_height,
method=t_method, method=t_method,
type=t_type, type=t_type,
length=t_byte_source.tell(),
), ),
) )

View file

@ -15,7 +15,7 @@
import logging import logging
import re import re
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple from typing import TYPE_CHECKING, List, Optional, Tuple
from synapse.api.errors import Codes, SynapseError, cs_error from synapse.api.errors import Codes, SynapseError, cs_error
from synapse.config.repository import THUMBNAIL_SUPPORTED_MEDIA_FORMAT_MAP from synapse.config.repository import THUMBNAIL_SUPPORTED_MEDIA_FORMAT_MAP
@ -159,30 +159,24 @@ class ThumbnailResource(RestServlet):
thumbnail_infos = await self.store.get_local_media_thumbnails(media_id) thumbnail_infos = await self.store.get_local_media_thumbnails(media_id)
for info in thumbnail_infos: for info in thumbnail_infos:
t_w = info["thumbnail_width"] == desired_width t_w = info.width == desired_width
t_h = info["thumbnail_height"] == desired_height t_h = info.height == desired_height
t_method = info["thumbnail_method"] == desired_method t_method = info.method == desired_method
t_type = info["thumbnail_type"] == desired_type t_type = info.type == desired_type
if t_w and t_h and t_method and t_type: if t_w and t_h and t_method and t_type:
file_info = FileInfo( file_info = FileInfo(
server_name=None, server_name=None,
file_id=media_id, file_id=media_id,
url_cache=media_info["url_cache"], url_cache=media_info["url_cache"],
thumbnail=ThumbnailInfo( thumbnail=info,
width=info["thumbnail_width"],
height=info["thumbnail_height"],
type=info["thumbnail_type"],
method=info["thumbnail_method"],
),
) )
t_type = file_info.thumbnail_type
t_length = info["thumbnail_length"]
responder = await self.media_storage.fetch_media(file_info) responder = await self.media_storage.fetch_media(file_info)
if responder: if responder:
await respond_with_responder(request, responder, t_type, t_length) await respond_with_responder(
request, responder, info.type, info.length
)
return return
logger.debug("We don't have a thumbnail of that size. Generating") logger.debug("We don't have a thumbnail of that size. Generating")
@ -222,29 +216,23 @@ class ThumbnailResource(RestServlet):
file_id = media_info["filesystem_id"] file_id = media_info["filesystem_id"]
for info in thumbnail_infos: for info in thumbnail_infos:
t_w = info["thumbnail_width"] == desired_width t_w = info.width == desired_width
t_h = info["thumbnail_height"] == desired_height t_h = info.height == desired_height
t_method = info["thumbnail_method"] == desired_method t_method = info.method == desired_method
t_type = info["thumbnail_type"] == desired_type t_type = info.type == desired_type
if t_w and t_h and t_method and t_type: if t_w and t_h and t_method and t_type:
file_info = FileInfo( file_info = FileInfo(
server_name=server_name, server_name=server_name,
file_id=media_info["filesystem_id"], file_id=media_info["filesystem_id"],
thumbnail=ThumbnailInfo( thumbnail=info,
width=info["thumbnail_width"],
height=info["thumbnail_height"],
type=info["thumbnail_type"],
method=info["thumbnail_method"],
),
) )
t_type = file_info.thumbnail_type
t_length = info["thumbnail_length"]
responder = await self.media_storage.fetch_media(file_info) responder = await self.media_storage.fetch_media(file_info)
if responder: if responder:
await respond_with_responder(request, responder, t_type, t_length) await respond_with_responder(
request, responder, info.type, info.length
)
return return
logger.debug("We don't have a thumbnail of that size. Generating") logger.debug("We don't have a thumbnail of that size. Generating")
@ -304,7 +292,7 @@ class ThumbnailResource(RestServlet):
desired_height: int, desired_height: int,
desired_method: str, desired_method: str,
desired_type: str, desired_type: str,
thumbnail_infos: List[Dict[str, Any]], thumbnail_infos: List[ThumbnailInfo],
media_id: str, media_id: str,
file_id: str, file_id: str,
url_cache: bool, url_cache: bool,
@ -319,7 +307,7 @@ class ThumbnailResource(RestServlet):
desired_height: The desired height, the returned thumbnail may be larger than this. desired_height: The desired height, the returned thumbnail may be larger than this.
desired_method: The desired method used to generate the thumbnail. desired_method: The desired method used to generate the thumbnail.
desired_type: The desired content-type of the thumbnail. desired_type: The desired content-type of the thumbnail.
thumbnail_infos: A list of dictionaries of candidate thumbnails. thumbnail_infos: A list of thumbnail info of candidate thumbnails.
file_id: The ID of the media that a thumbnail is being requested for. file_id: The ID of the media that a thumbnail is being requested for.
url_cache: True if this is from a URL cache. url_cache: True if this is from a URL cache.
server_name: The server name, if this is a remote thumbnail. server_name: The server name, if this is a remote thumbnail.
@ -443,7 +431,7 @@ class ThumbnailResource(RestServlet):
desired_height: int, desired_height: int,
desired_method: str, desired_method: str,
desired_type: str, desired_type: str,
thumbnail_infos: List[Dict[str, Any]], thumbnail_infos: List[ThumbnailInfo],
file_id: str, file_id: str,
url_cache: bool, url_cache: bool,
server_name: Optional[str], server_name: Optional[str],
@ -456,7 +444,7 @@ class ThumbnailResource(RestServlet):
desired_height: The desired height, the returned thumbnail may be larger than this. desired_height: The desired height, the returned thumbnail may be larger than this.
desired_method: The desired method used to generate the thumbnail. desired_method: The desired method used to generate the thumbnail.
desired_type: The desired content-type of the thumbnail. desired_type: The desired content-type of the thumbnail.
thumbnail_infos: A list of dictionaries of candidate thumbnails. thumbnail_infos: A list of thumbnail infos of candidate thumbnails.
file_id: The ID of the media that a thumbnail is being requested for. file_id: The ID of the media that a thumbnail is being requested for.
url_cache: True if this is from a URL cache. url_cache: True if this is from a URL cache.
server_name: The server name, if this is a remote thumbnail. server_name: The server name, if this is a remote thumbnail.
@ -474,21 +462,25 @@ class ThumbnailResource(RestServlet):
if desired_method == "crop": if desired_method == "crop":
# Thumbnails that match equal or larger sizes of desired width/height. # Thumbnails that match equal or larger sizes of desired width/height.
crop_info_list: List[Tuple[int, int, int, bool, int, Dict[str, Any]]] = [] crop_info_list: List[
Tuple[int, int, int, bool, Optional[int], ThumbnailInfo]
] = []
# Other thumbnails. # Other thumbnails.
crop_info_list2: List[Tuple[int, int, int, bool, int, Dict[str, Any]]] = [] crop_info_list2: List[
Tuple[int, int, int, bool, Optional[int], ThumbnailInfo]
] = []
for info in thumbnail_infos: for info in thumbnail_infos:
# Skip thumbnails generated with different methods. # Skip thumbnails generated with different methods.
if info["thumbnail_method"] != "crop": if info.method != "crop":
continue continue
t_w = info["thumbnail_width"] t_w = info.width
t_h = info["thumbnail_height"] t_h = info.height
aspect_quality = abs(d_w * t_h - d_h * t_w) aspect_quality = abs(d_w * t_h - d_h * t_w)
min_quality = 0 if d_w <= t_w and d_h <= t_h else 1 min_quality = 0 if d_w <= t_w and d_h <= t_h else 1
size_quality = abs((d_w - t_w) * (d_h - t_h)) size_quality = abs((d_w - t_w) * (d_h - t_h))
type_quality = desired_type != info["thumbnail_type"] type_quality = desired_type != info.type
length_quality = info["thumbnail_length"] length_quality = info.length
if t_w >= d_w or t_h >= d_h: if t_w >= d_w or t_h >= d_h:
crop_info_list.append( crop_info_list.append(
( (
@ -513,7 +505,7 @@ class ThumbnailResource(RestServlet):
) )
# Pick the most appropriate thumbnail. Some values of `desired_width` and # Pick the most appropriate thumbnail. Some values of `desired_width` and
# `desired_height` may result in a tie, in which case we avoid comparing on # `desired_height` may result in a tie, in which case we avoid comparing on
# the thumbnail info dictionary and pick the thumbnail that appears earlier # the thumbnail info and pick the thumbnail that appears earlier
# in the list of candidates. # in the list of candidates.
if crop_info_list: if crop_info_list:
thumbnail_info = min(crop_info_list, key=lambda t: t[:-1])[-1] thumbnail_info = min(crop_info_list, key=lambda t: t[:-1])[-1]
@ -521,20 +513,20 @@ class ThumbnailResource(RestServlet):
thumbnail_info = min(crop_info_list2, key=lambda t: t[:-1])[-1] thumbnail_info = min(crop_info_list2, key=lambda t: t[:-1])[-1]
elif desired_method == "scale": elif desired_method == "scale":
# Thumbnails that match equal or larger sizes of desired width/height. # Thumbnails that match equal or larger sizes of desired width/height.
info_list: List[Tuple[int, bool, int, Dict[str, Any]]] = [] info_list: List[Tuple[int, bool, int, ThumbnailInfo]] = []
# Other thumbnails. # Other thumbnails.
info_list2: List[Tuple[int, bool, int, Dict[str, Any]]] = [] info_list2: List[Tuple[int, bool, int, ThumbnailInfo]] = []
for info in thumbnail_infos: for info in thumbnail_infos:
# Skip thumbnails generated with different methods. # Skip thumbnails generated with different methods.
if info["thumbnail_method"] != "scale": if info.method != "scale":
continue continue
t_w = info["thumbnail_width"] t_w = info.width
t_h = info["thumbnail_height"] t_h = info.height
size_quality = abs((d_w - t_w) * (d_h - t_h)) size_quality = abs((d_w - t_w) * (d_h - t_h))
type_quality = desired_type != info["thumbnail_type"] type_quality = desired_type != info.type
length_quality = info["thumbnail_length"] length_quality = info.length
if t_w >= d_w or t_h >= d_h: if t_w >= d_w or t_h >= d_h:
info_list.append((size_quality, type_quality, length_quality, info)) info_list.append((size_quality, type_quality, length_quality, info))
else: else:
@ -543,7 +535,7 @@ class ThumbnailResource(RestServlet):
) )
# Pick the most appropriate thumbnail. Some values of `desired_width` and # Pick the most appropriate thumbnail. Some values of `desired_width` and
# `desired_height` may result in a tie, in which case we avoid comparing on # `desired_height` may result in a tie, in which case we avoid comparing on
# the thumbnail info dictionary and pick the thumbnail that appears earlier # the thumbnail info and pick the thumbnail that appears earlier
# in the list of candidates. # in the list of candidates.
if info_list: if info_list:
thumbnail_info = min(info_list, key=lambda t: t[:-1])[-1] thumbnail_info = min(info_list, key=lambda t: t[:-1])[-1]
@ -555,13 +547,7 @@ class ThumbnailResource(RestServlet):
file_id=file_id, file_id=file_id,
url_cache=url_cache, url_cache=url_cache,
server_name=server_name, server_name=server_name,
thumbnail=ThumbnailInfo( thumbnail=thumbnail_info,
width=thumbnail_info["thumbnail_width"],
height=thumbnail_info["thumbnail_height"],
type=thumbnail_info["thumbnail_type"],
method=thumbnail_info["thumbnail_method"],
length=thumbnail_info["thumbnail_length"],
),
) )
# No matching thumbnail was found. # No matching thumbnail was found.

View file

@ -28,6 +28,7 @@ from typing import (
from synapse.api.constants import Direction from synapse.api.constants import Direction
from synapse.logging.opentracing import trace from synapse.logging.opentracing import trace
from synapse.media._base import ThumbnailInfo
from synapse.storage._base import SQLBaseStore from synapse.storage._base import SQLBaseStore
from synapse.storage.database import ( from synapse.storage.database import (
DatabasePool, DatabasePool,
@ -435,8 +436,8 @@ class MediaRepositoryStore(MediaRepositoryBackgroundUpdateStore):
desc="store_url_cache", desc="store_url_cache",
) )
async def get_local_media_thumbnails(self, media_id: str) -> List[Dict[str, Any]]: async def get_local_media_thumbnails(self, media_id: str) -> List[ThumbnailInfo]:
return await self.db_pool.simple_select_list( rows = await self.db_pool.simple_select_list(
"local_media_repository_thumbnails", "local_media_repository_thumbnails",
{"media_id": media_id}, {"media_id": media_id},
( (
@ -448,6 +449,16 @@ class MediaRepositoryStore(MediaRepositoryBackgroundUpdateStore):
), ),
desc="get_local_media_thumbnails", desc="get_local_media_thumbnails",
) )
return [
ThumbnailInfo(
width=row["thumbnail_width"],
height=row["thumbnail_height"],
method=row["thumbnail_method"],
type=row["thumbnail_type"],
length=row["thumbnail_length"],
)
for row in rows
]
@trace @trace
async def store_local_thumbnail( async def store_local_thumbnail(
@ -556,8 +567,8 @@ class MediaRepositoryStore(MediaRepositoryBackgroundUpdateStore):
async def get_remote_media_thumbnails( async def get_remote_media_thumbnails(
self, origin: str, media_id: str self, origin: str, media_id: str
) -> List[Dict[str, Any]]: ) -> List[ThumbnailInfo]:
return await self.db_pool.simple_select_list( rows = await self.db_pool.simple_select_list(
"remote_media_cache_thumbnails", "remote_media_cache_thumbnails",
{"media_origin": origin, "media_id": media_id}, {"media_origin": origin, "media_id": media_id},
( (
@ -566,10 +577,19 @@ class MediaRepositoryStore(MediaRepositoryBackgroundUpdateStore):
"thumbnail_method", "thumbnail_method",
"thumbnail_type", "thumbnail_type",
"thumbnail_length", "thumbnail_length",
"filesystem_id",
), ),
desc="get_remote_media_thumbnails", desc="get_remote_media_thumbnails",
) )
return [
ThumbnailInfo(
width=row["thumbnail_width"],
height=row["thumbnail_height"],
method=row["thumbnail_method"],
type=row["thumbnail_type"],
length=row["thumbnail_length"],
)
for row in rows
]
@trace @trace
async def get_remote_media_thumbnail( async def get_remote_media_thumbnail(

View file

@ -34,7 +34,7 @@ from synapse.api.errors import Codes
from synapse.events import EventBase from synapse.events import EventBase
from synapse.http.types import QueryParams from synapse.http.types import QueryParams
from synapse.logging.context import make_deferred_yieldable from synapse.logging.context import make_deferred_yieldable
from synapse.media._base import FileInfo from synapse.media._base import FileInfo, ThumbnailInfo
from synapse.media.filepath import MediaFilePaths from synapse.media.filepath import MediaFilePaths
from synapse.media.media_storage import MediaStorage, ReadableFileWrapper from synapse.media.media_storage import MediaStorage, ReadableFileWrapper
from synapse.media.storage_provider import FileStorageProviderBackend from synapse.media.storage_provider import FileStorageProviderBackend
@ -605,6 +605,8 @@ class MediaRepoTests(unittest.HomeserverTestCase):
"""Test that choosing between thumbnails with the same quality rating succeeds. """Test that choosing between thumbnails with the same quality rating succeeds.
We are not particular about which thumbnail is chosen.""" We are not particular about which thumbnail is chosen."""
content_type = self.test_image.content_type.decode()
media_repo = self.hs.get_media_repository() media_repo = self.hs.get_media_repository()
thumbnail_resouce = ThumbnailResource( thumbnail_resouce = ThumbnailResource(
self.hs, media_repo, media_repo.media_storage self.hs, media_repo, media_repo.media_storage
@ -615,26 +617,24 @@ class MediaRepoTests(unittest.HomeserverTestCase):
desired_width=desired_size, desired_width=desired_size,
desired_height=desired_size, desired_height=desired_size,
desired_method=method, desired_method=method,
desired_type=self.test_image.content_type, # type: ignore[arg-type] desired_type=content_type,
# Provide two identical thumbnails which are guaranteed to have the same # Provide two identical thumbnails which are guaranteed to have the same
# quality rating. # quality rating.
thumbnail_infos=[ thumbnail_infos=[
{ ThumbnailInfo(
"thumbnail_width": 32, width=32,
"thumbnail_height": 32, height=32,
"thumbnail_method": method, method=method,
"thumbnail_type": self.test_image.content_type, type=content_type,
"thumbnail_length": 256, length=256,
"filesystem_id": f"thumbnail1{self.test_image.extension.decode()}", ),
}, ThumbnailInfo(
{ width=32,
"thumbnail_width": 32, height=32,
"thumbnail_height": 32, method=method,
"thumbnail_method": method, type=content_type,
"thumbnail_type": self.test_image.content_type, length=256,
"thumbnail_length": 256, ),
"filesystem_id": f"thumbnail2{self.test_image.extension.decode()}",
},
], ],
file_id=f"image{self.test_image.extension.decode()}", file_id=f"image{self.test_image.extension.decode()}",
url_cache=False, url_cache=False,