snapshot of https://github.com/zulip/zulip.git @ 48c69911c7dd4e03b20552eb3bd77ad8151c1357 (2026-09-15)
Some checks failed
Code scanning / CodeQL (push) Failing after 15s
Zulip production suite / Ubuntu 22.04 production build (push) Failing after 26s
Zulip production suite / Debian 12 production install with custom db name and user (push) Has been skipped
Zulip production suite / Ubuntu 22.04 production install and PostgreSQL upgrade with pgroonga (push) Has been skipped
Zulip production suite / Ubuntu 24.04 production install (push) Has been skipped
Zulip production suite / Ubuntu 26.04 production install (push) Has been skipped
Zulip production suite / Debian 13 production install (push) Has been skipped
Zulip production suite / 7.0 Version Upgrade (push) Has been skipped
Zulip production suite / 8.0 Version Upgrade (push) Has been skipped
Zulip production suite / 6.0 Version Upgrade (push) Has been skipped
Zulip production suite / 10.0 Version Upgrade (push) Has been skipped
Zulip production suite / 9.0 Version Upgrade (push) Has been skipped
Zulip production suite / 12.0 Version Upgrade (push) Has been skipped
Zulip production suite / 11.0 Version Upgrade (push) Has been skipped
GitHub Actions Security Analysis with zizmor / zizmor (push) Failing after 3s
Zulip CI / Debian 12 (Python 3.11, backend + documentation) (push) Failing after 26s
Zulip CI / Ubuntu 22.04 (Python 3.10, backend + frontend) (push) Failing after 3s
Zulip CI / Ubuntu 24.04 (Python 3.12, backend) (push) Failing after 25s
Zulip CI / Ubuntu 26.04 (Python 3.14, backend) (push) Failing after 25s
Zulip CI / Debian 13 (Python 3.13, backend) (push) Failing after 28s
Zulip production suite / Required jobs (push) Has been cancelled
Zulip CI / Required jobs (push) Has been cancelled
API Documentation Update Check / check-feature-level-updated (push) Has been cancelled
API Documentation Update Check / notify-if-api-docs-changed (push) Has been cancelled
Some checks failed
Code scanning / CodeQL (push) Failing after 15s
Zulip production suite / Ubuntu 22.04 production build (push) Failing after 26s
Zulip production suite / Debian 12 production install with custom db name and user (push) Has been skipped
Zulip production suite / Ubuntu 22.04 production install and PostgreSQL upgrade with pgroonga (push) Has been skipped
Zulip production suite / Ubuntu 24.04 production install (push) Has been skipped
Zulip production suite / Ubuntu 26.04 production install (push) Has been skipped
Zulip production suite / Debian 13 production install (push) Has been skipped
Zulip production suite / 7.0 Version Upgrade (push) Has been skipped
Zulip production suite / 8.0 Version Upgrade (push) Has been skipped
Zulip production suite / 6.0 Version Upgrade (push) Has been skipped
Zulip production suite / 10.0 Version Upgrade (push) Has been skipped
Zulip production suite / 9.0 Version Upgrade (push) Has been skipped
Zulip production suite / 12.0 Version Upgrade (push) Has been skipped
Zulip production suite / 11.0 Version Upgrade (push) Has been skipped
GitHub Actions Security Analysis with zizmor / zizmor (push) Failing after 3s
Zulip CI / Debian 12 (Python 3.11, backend + documentation) (push) Failing after 26s
Zulip CI / Ubuntu 22.04 (Python 3.10, backend + frontend) (push) Failing after 3s
Zulip CI / Ubuntu 24.04 (Python 3.12, backend) (push) Failing after 25s
Zulip CI / Ubuntu 26.04 (Python 3.14, backend) (push) Failing after 25s
Zulip CI / Debian 13 (Python 3.13, backend) (push) Failing after 28s
Zulip production suite / Required jobs (push) Has been cancelled
Zulip CI / Required jobs (push) Has been cancelled
API Documentation Update Check / check-feature-level-updated (push) Has been cancelled
API Documentation Update Check / notify-if-api-docs-changed (push) Has been cancelled
This commit is contained in:
157
zerver/lib/upload/base.py
Normal file
157
zerver/lib/upload/base.py
Normal file
@@ -0,0 +1,157 @@
|
||||
import os
|
||||
from collections.abc import Callable, Iterator
|
||||
from contextlib import contextmanager
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from typing import IO, Any, Protocol
|
||||
|
||||
import pyvips
|
||||
|
||||
from zerver.models import Realm, UserProfile
|
||||
|
||||
|
||||
class ReadableStream(Protocol):
|
||||
def read(self, size: int = -1) -> bytes: ...
|
||||
|
||||
def close(self) -> None: ...
|
||||
|
||||
|
||||
@dataclass
|
||||
class StreamingSourceWithSize:
|
||||
size: int
|
||||
vips_source: pyvips.Source
|
||||
reader: Callable[[], ReadableStream]
|
||||
|
||||
|
||||
class ZulipUploadBackend:
|
||||
# Message attachment uploads
|
||||
def get_public_upload_root_url(self) -> str:
|
||||
raise NotImplementedError
|
||||
|
||||
def generate_message_upload_path(self, realm_id: str, uploaded_file_name: str) -> str:
|
||||
raise NotImplementedError
|
||||
|
||||
def store_message_attachment(
|
||||
self,
|
||||
path_id: str,
|
||||
filename: str,
|
||||
content_type: str,
|
||||
file_data: bytes,
|
||||
user_profile: UserProfile | None,
|
||||
target_realm: Realm | None,
|
||||
) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
def save_attachment_contents(self, path_id: str, filehandle: IO[bytes]) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
def attachment_source(self, path_id: str) -> StreamingSourceWithSize:
|
||||
raise NotImplementedError
|
||||
|
||||
def delete_message_attachment_from_storage(
|
||||
self, path_id: str, *, raw_path: bool = False
|
||||
) -> None:
|
||||
"""This must delete the attachment, any adjacent .info files, and any thumbnails.
|
||||
|
||||
If raw_path is passed, just delete the one path provided.
|
||||
This is used in rare cases where we're iterating _files_, not
|
||||
path_ids.
|
||||
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@contextmanager
|
||||
def delete_message_attachments_from_storage(
|
||||
self,
|
||||
*,
|
||||
raw_paths: bool = False,
|
||||
flush: Callable[[list[str]], None] | None = None,
|
||||
) -> Iterator[Callable[[str], None]]:
|
||||
def delete_one(path_id: str) -> None:
|
||||
self.delete_message_attachment_from_storage(path_id, raw_path=raw_paths)
|
||||
if flush:
|
||||
flush([path_id])
|
||||
|
||||
yield delete_one
|
||||
|
||||
def all_message_attachments(
|
||||
self,
|
||||
include_thumbnails: bool = False,
|
||||
prefix: str = "",
|
||||
) -> Iterator[tuple[str, datetime]]:
|
||||
raise NotImplementedError
|
||||
|
||||
# Avatar image uploads
|
||||
def get_avatar_url(self, hash_key: str, medium: bool = False) -> str:
|
||||
raise NotImplementedError
|
||||
|
||||
def get_avatar_contents(self, file_path: str, avatar_source: str) -> tuple[bytes, str]:
|
||||
raise NotImplementedError
|
||||
|
||||
def get_avatar_path(self, hash_key: str, medium: bool = False) -> str:
|
||||
if medium:
|
||||
return f"{hash_key}-medium.png"
|
||||
else:
|
||||
return f"{hash_key}.png"
|
||||
|
||||
def store_single_avatar_image(
|
||||
self,
|
||||
file_path: str,
|
||||
*,
|
||||
user_profile: UserProfile,
|
||||
image_data: bytes,
|
||||
content_type: str | None,
|
||||
future: bool = True,
|
||||
) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
def delete_avatar_image_from_storage(self, path_id: str) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
# Realm icon and logo uploads
|
||||
def realm_avatar_and_logo_path(self, realm: Realm) -> str:
|
||||
return os.path.join(str(realm.id), "realm")
|
||||
|
||||
def get_realm_icon_url(self, realm_id: int, version: int) -> str:
|
||||
raise NotImplementedError
|
||||
|
||||
def store_realm_icon_image(
|
||||
self, icon_file: IO[bytes], user_profile: UserProfile, content_type: str
|
||||
) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
def get_realm_logo_url(self, realm_id: int, version: int, night: bool) -> str:
|
||||
raise NotImplementedError
|
||||
|
||||
def store_realm_logo_image(
|
||||
self, logo_file: IO[bytes], user_profile: UserProfile, night: bool, content_type: str
|
||||
) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
# Realm emoji uploads
|
||||
def get_emoji_url(self, emoji_file_name: str, realm_id: int, still: bool = False) -> str:
|
||||
raise NotImplementedError
|
||||
|
||||
def store_single_emoji_image(
|
||||
self,
|
||||
path: str,
|
||||
content_type: str | None,
|
||||
user_profile: UserProfile,
|
||||
image_data: bytes,
|
||||
) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
# Export tarballs
|
||||
def get_export_tarball_url(self, realm: Realm, export_path: str) -> str:
|
||||
raise NotImplementedError
|
||||
|
||||
def store_export_tarball(
|
||||
self,
|
||||
realm: Realm,
|
||||
tarball_path: str,
|
||||
percent_callback: Callable[[Any], None] | None = None,
|
||||
) -> str:
|
||||
raise NotImplementedError
|
||||
|
||||
def delete_export_tarball_from_storage(self, export_path: str) -> None:
|
||||
raise NotImplementedError
|
||||
Reference in New Issue
Block a user