thumbnail: Support checking for images from streaming sources.

We may not always have trivial access to all of the bytes of the
uploaded file -- for instance, if the file was uploaded previously, or
by some other process.  Downloading the entire image in order to check
its headers is an inefficient use of time and bandwidth.

Adjust `maybe_thumbnail` and dependencies to potentially take a
`pyvips.Source` which supports streaming data from S3 or disk.  This
allows making the ImageAttachment row, if deemed appropriate, based on
only a few KB of data, and not the entire image.
This commit is contained in:
Alex Vandiver
2024-09-10 18:33:25 +00:00
committed by Tim Abbott
parent 758aa36cbe
commit 9a1f78db22
8 changed files with 113 additions and 10 deletions

View File

@@ -7,13 +7,14 @@ from collections.abc import Callable, Iterator
from datetime import datetime
from typing import IO, Any, BinaryIO, Literal
import pyvips
from django.conf import settings
from typing_extensions import override
from zerver.lib.mime_types import guess_type
from zerver.lib.thumbnail import resize_avatar, resize_logo
from zerver.lib.timestamp import timestamp_to_datetime
from zerver.lib.upload.base import ZulipUploadBackend
from zerver.lib.upload.base import StreamingSourceWithSize, ZulipUploadBackend
from zerver.lib.utils import assert_is_not_none
from zerver.models import Realm, RealmEmoji, UserProfile
@@ -100,6 +101,13 @@ class LocalUploadBackend(ZulipUploadBackend):
def save_attachment_contents(self, path_id: str, filehandle: BinaryIO) -> None:
filehandle.write(read_local_file("files", path_id))
@override
def attachment_vips_source(self, path_id: str) -> StreamingSourceWithSize:
file_path = os.path.join(assert_is_not_none(settings.LOCAL_UPLOADS_DIR), "files", path_id)
assert_is_local_storage_path("files", file_path)
source = pyvips.Source.new_from_file(file_path)
return StreamingSourceWithSize(size=os.path.getsize(file_path), source=source)
@override
def delete_message_attachment(self, path_id: str) -> bool:
return delete_local_file("files", path_id)