upload: Factor out common emoji logic.

This commit is contained in:
Alex Vandiver
2024-06-24 20:25:16 +00:00
committed by Tim Abbott
parent f66b78f6b3
commit d92993c972
6 changed files with 48 additions and 66 deletions

View File

@@ -9,6 +9,7 @@ from django.db import connection
from zerver.lib.avatar_hash import user_avatar_path from zerver.lib.avatar_hash import user_avatar_path
from zerver.lib.mime_types import guess_type from zerver.lib.mime_types import guess_type
from zerver.lib.upload import upload_emoji_image
from zerver.lib.upload.s3 import S3UploadBackend, upload_image_to_s3 from zerver.lib.upload.s3 import S3UploadBackend, upload_image_to_s3
from zerver.models import Attachment, RealmEmoji, UserProfile from zerver.models import Attachment, RealmEmoji, UserProfile
@@ -102,7 +103,7 @@ def _transfer_emoji_to_s3(realm_emoji: RealmEmoji) -> None:
emoji_path = os.path.join(settings.LOCAL_AVATARS_DIR, emoji_path) + ".original" emoji_path = os.path.join(settings.LOCAL_AVATARS_DIR, emoji_path) + ".original"
try: try:
with open(emoji_path, "rb") as f: with open(emoji_path, "rb") as f:
s3backend.upload_emoji_image(f, realm_emoji.file_name, realm_emoji.author) upload_emoji_image(f, realm_emoji.file_name, realm_emoji.author, backend=s3backend)
logging.info("Uploaded emoji file in path %s", emoji_path) logging.info("Uploaded emoji file in path %s", emoji_path)
except FileNotFoundError: # nocoverage except FileNotFoundError: # nocoverage
pass pass

View File

@@ -1,5 +1,6 @@
import io import io
import logging import logging
import os
from datetime import datetime from datetime import datetime
from typing import IO, Any, BinaryIO, Callable, Iterator, List, Optional, Tuple, Union from typing import IO, Any, BinaryIO, Callable, Iterator, List, Optional, Tuple, Union
from urllib.parse import unquote, urljoin from urllib.parse import unquote, urljoin
@@ -11,6 +12,7 @@ from django.utils.translation import gettext as _
from zerver.lib.exceptions import ErrorCode, JsonableError from zerver.lib.exceptions import ErrorCode, JsonableError
from zerver.lib.mime_types import guess_type from zerver.lib.mime_types import guess_type
from zerver.lib.outgoing_http import OutgoingSession from zerver.lib.outgoing_http import OutgoingSession
from zerver.lib.thumbnail import resize_emoji
from zerver.lib.upload.base import ZulipUploadBackend from zerver.lib.upload.base import ZulipUploadBackend
from zerver.models import Attachment, Message, Realm, RealmEmoji, ScheduledMessage, UserProfile from zerver.models import Attachment, Message, Realm, RealmEmoji, ScheduledMessage, UserProfile
@@ -172,9 +174,33 @@ def upload_logo_image(user_file: IO[bytes], user_profile: UserProfile, night: bo
def upload_emoji_image( def upload_emoji_image(
emoji_file: IO[bytes], emoji_file_name: str, user_profile: UserProfile emoji_file: IO[bytes],
emoji_file_name: str,
user_profile: UserProfile,
backend: Optional[ZulipUploadBackend] = None,
) -> bool: ) -> bool:
return upload_backend.upload_emoji_image(emoji_file, emoji_file_name, user_profile) if backend is None:
backend = upload_backend
content_type = guess_type(emoji_file_name)[0]
emoji_path = RealmEmoji.PATH_ID_TEMPLATE.format(
realm_id=user_profile.realm_id,
emoji_file_name=emoji_file_name,
)
image_data = emoji_file.read()
backend.upload_single_emoji_image(
f"{emoji_path}.original", content_type, user_profile, image_data
)
resized_image_data, is_animated, still_image_data = resize_emoji(image_data)
backend.upload_single_emoji_image(emoji_path, content_type, user_profile, resized_image_data)
if is_animated:
assert still_image_data is not None
still_path = RealmEmoji.STILL_PATH_ID_TEMPLATE.format(
realm_id=user_profile.realm_id,
emoji_filename_without_extension=os.path.splitext(emoji_file_name)[0],
)
backend.upload_single_emoji_image(still_path, content_type, user_profile, still_image_data)
return is_animated
def get_emoji_file_content( def get_emoji_file_content(

View File

@@ -125,9 +125,13 @@ class ZulipUploadBackend:
def get_emoji_url(self, emoji_file_name: str, realm_id: int, still: bool = False) -> str: def get_emoji_url(self, emoji_file_name: str, realm_id: int, still: bool = False) -> str:
raise NotImplementedError raise NotImplementedError
def upload_emoji_image( def upload_single_emoji_image(
self, emoji_file: IO[bytes], emoji_file_name: str, user_profile: UserProfile self,
) -> bool: path: str,
content_type: Optional[str],
user_profile: UserProfile,
image_data: bytes,
) -> None:
raise NotImplementedError raise NotImplementedError
# Export tarballs # Export tarballs

View File

@@ -10,7 +10,7 @@ from django.conf import settings
from typing_extensions import override from typing_extensions import override
from zerver.lib.avatar_hash import user_avatar_path from zerver.lib.avatar_hash import user_avatar_path
from zerver.lib.thumbnail import MEDIUM_AVATAR_SIZE, resize_avatar, resize_emoji, resize_logo from zerver.lib.thumbnail import MEDIUM_AVATAR_SIZE, resize_avatar, resize_logo
from zerver.lib.timestamp import timestamp_to_datetime from zerver.lib.timestamp import timestamp_to_datetime
from zerver.lib.upload.base import ZulipUploadBackend, create_attachment, sanitize_name from zerver.lib.upload.base import ZulipUploadBackend, create_attachment, sanitize_name
from zerver.lib.utils import assert_is_not_none from zerver.lib.utils import assert_is_not_none
@@ -238,26 +238,10 @@ class LocalUploadBackend(ZulipUploadBackend):
) )
@override @override
def upload_emoji_image( def upload_single_emoji_image(
self, emoji_file: IO[bytes], emoji_file_name: str, user_profile: UserProfile self, path: str, content_type: Optional[str], user_profile: UserProfile, image_data: bytes
) -> bool: ) -> None:
emoji_path = RealmEmoji.PATH_ID_TEMPLATE.format( write_local_file("avatars", path, image_data)
realm_id=user_profile.realm_id,
emoji_file_name=emoji_file_name,
)
image_data = emoji_file.read()
write_local_file("avatars", f"{emoji_path}.original", image_data)
resized_image_data, is_animated, still_image_data = resize_emoji(image_data)
write_local_file("avatars", emoji_path, resized_image_data)
if is_animated:
assert still_image_data is not None
still_path = RealmEmoji.STILL_PATH_ID_TEMPLATE.format(
realm_id=user_profile.realm_id,
emoji_filename_without_extension=os.path.splitext(emoji_file_name)[0],
)
write_local_file("avatars", still_path, still_image_data)
return is_animated
@override @override
def get_export_tarball_url(self, realm: Realm, export_path: str) -> str: def get_export_tarball_url(self, realm: Realm, export_path: str) -> str:

View File

@@ -14,7 +14,7 @@ from typing_extensions import override
from zerver.lib.avatar_hash import user_avatar_path from zerver.lib.avatar_hash import user_avatar_path
from zerver.lib.mime_types import guess_type from zerver.lib.mime_types import guess_type
from zerver.lib.thumbnail import MEDIUM_AVATAR_SIZE, resize_avatar, resize_emoji, resize_logo from zerver.lib.thumbnail import MEDIUM_AVATAR_SIZE, resize_avatar, resize_logo
from zerver.lib.upload.base import ( from zerver.lib.upload.base import (
INLINE_MIME_TYPES, INLINE_MIME_TYPES,
ZulipUploadBackend, ZulipUploadBackend,
@@ -442,48 +442,17 @@ class S3UploadBackend(ZulipUploadBackend):
return self.get_public_upload_url(emoji_path) return self.get_public_upload_url(emoji_path)
@override @override
def upload_emoji_image( def upload_single_emoji_image(
self, emoji_file: IO[bytes], emoji_file_name: str, user_profile: UserProfile self, path: str, content_type: Optional[str], user_profile: UserProfile, image_data: bytes
) -> bool: ) -> None:
content_type = guess_type(emoji_file_name)[0]
emoji_path = RealmEmoji.PATH_ID_TEMPLATE.format(
realm_id=user_profile.realm_id,
emoji_file_name=emoji_file_name,
)
image_data = emoji_file.read()
upload_image_to_s3( upload_image_to_s3(
self.avatar_bucket, self.avatar_bucket,
f"{emoji_path}.original", path,
content_type, content_type,
user_profile, user_profile,
image_data, image_data,
) )
resized_image_data, is_animated, still_image_data = resize_emoji(image_data)
upload_image_to_s3(
self.avatar_bucket,
emoji_path,
content_type,
user_profile,
resized_image_data,
)
if is_animated:
still_path = RealmEmoji.STILL_PATH_ID_TEMPLATE.format(
realm_id=user_profile.realm_id,
emoji_filename_without_extension=os.path.splitext(emoji_file_name)[0],
)
assert still_image_data is not None
upload_image_to_s3(
self.avatar_bucket,
still_path,
"image/png",
user_profile,
still_image_data,
)
return is_animated
@override @override
def get_export_tarball_url(self, realm: Realm, export_path: str) -> str: def get_export_tarball_url(self, realm: Realm, export_path: str) -> str:
# export_path has a leading / # export_path has a leading /

View File

@@ -483,9 +483,7 @@ class S3Test(ZulipTestCase):
user_profile = self.example_user("hamlet") user_profile = self.example_user("hamlet")
emoji_name = "emoji.png" emoji_name = "emoji.png"
with get_test_image_file("img.png") as image_file: with get_test_image_file("img.png") as image_file:
zerver.lib.upload.upload_backend.upload_emoji_image( zerver.lib.upload.upload_emoji_image(image_file, emoji_name, user_profile)
image_file, emoji_name, user_profile
)
emoji_path = RealmEmoji.PATH_ID_TEMPLATE.format( emoji_path = RealmEmoji.PATH_ID_TEMPLATE.format(
realm_id=user_profile.realm_id, realm_id=user_profile.realm_id,