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:
130
zerver/lib/storage.py
Normal file
130
zerver/lib/storage.py
Normal file
@@ -0,0 +1,130 @@
|
||||
# Useful reading is:
|
||||
# https://zulip.readthedocs.io/en/latest/subsystems/html-css.html#front-end-build-process
|
||||
|
||||
import os
|
||||
from typing import Optional
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.staticfiles.storage import ManifestStaticFilesStorage
|
||||
from django.core.files.base import File
|
||||
from django.core.files.storage import FileSystemStorage
|
||||
from typing_extensions import override
|
||||
|
||||
from zerver.lib.avatar import STATIC_AVATARS_DIR
|
||||
|
||||
if settings.DEBUG:
|
||||
from django.contrib.staticfiles.finders import find
|
||||
|
||||
def static_path(path: str) -> str:
|
||||
return find(path) or "/nonexistent"
|
||||
|
||||
else:
|
||||
|
||||
def static_path(path: str) -> str:
|
||||
return os.path.join(settings.STATIC_ROOT, path)
|
||||
|
||||
|
||||
class IgnoreBundlesManifestStaticFilesStorage(ManifestStaticFilesStorage):
|
||||
def process_static_avatars_name(
|
||||
self,
|
||||
name: str,
|
||||
content: Optional["File[bytes]"] = None,
|
||||
filename: str | None = None,
|
||||
) -> str:
|
||||
"""
|
||||
Because the protocol for getting medium-size avatar URLs
|
||||
was never fully documented, the mobile apps use a
|
||||
substitution of the form s/.png/-medium.png/ to get the
|
||||
medium-size avatar URLs.
|
||||
|
||||
This function hashes system bots' avatar files in a way
|
||||
that follows the pattern used for user-uploaded avatars.
|
||||
|
||||
It ensures the following:
|
||||
|
||||
* Hashed filenames for system bot avatars follow this
|
||||
naming convention:
|
||||
- avatar.png -> avatar-medium.png
|
||||
|
||||
* The system bots' default avatar file and its medium
|
||||
version share the same hash:
|
||||
- bot.36f721bad3d0.png -> bot.36f721bad3d0-medium.png
|
||||
"""
|
||||
|
||||
def reformat_medium_filename(hashed_name: str) -> str:
|
||||
name_parts = hashed_name.rsplit(".", 1)
|
||||
base_name = name_parts[0]
|
||||
|
||||
if len(name_parts) != 2 or "-medium" not in base_name:
|
||||
return hashed_name
|
||||
extension = name_parts[1].replace("png", "medium.png")
|
||||
base_name = base_name.replace("-medium", "")
|
||||
return f"{base_name}-{extension}"
|
||||
|
||||
if name.endswith("-medium.png"):
|
||||
hashed_medium_file = reformat_medium_filename(
|
||||
super().hashed_name(name, content, filename)
|
||||
)
|
||||
return hashed_medium_file
|
||||
else:
|
||||
medium_name = name.replace(".png", "-medium.png")
|
||||
from django.core.files import File
|
||||
|
||||
with File(open(self.path(medium_name), "rb")) as medium_content:
|
||||
hashed_medium_file = reformat_medium_filename(
|
||||
super().hashed_name(medium_name, medium_content, filename)
|
||||
)
|
||||
hashed_default_file = hashed_medium_file.replace("-medium.png", ".png")
|
||||
return hashed_default_file
|
||||
|
||||
@override
|
||||
def hashed_name(
|
||||
self, name: str, content: Optional["File[bytes]"] = None, filename: str | None = None
|
||||
) -> str:
|
||||
ext = os.path.splitext(name)[1]
|
||||
if name.startswith("webpack-bundles"):
|
||||
# Hack to avoid renaming already-hashnamed webpack bundles
|
||||
# when minifying; this was causing every bundle to have
|
||||
# two hashes appended to its name, one by webpack and one
|
||||
# here. We can't just skip processing of these bundles,
|
||||
# since we do need the Django storage to add these to the
|
||||
# manifest for django_webpack_loader to work. So, we just
|
||||
# use a no-op hash function for these already-hashed
|
||||
# assets.
|
||||
return name
|
||||
if name.startswith(STATIC_AVATARS_DIR):
|
||||
# For these avatar files, we want to make sure they are
|
||||
# so they can hit our Nginx caching block for static files.
|
||||
# We don't need to worry about stale caches since these are
|
||||
# only used by the system bots.
|
||||
return self.process_static_avatars_name(name, content, filename)
|
||||
|
||||
if name == "generated/emoji/emoji_api.json":
|
||||
# Unlike most .json files, we do want to hash this file;
|
||||
# its hashed URL is returned as part of the API. See
|
||||
# data_url() in zerver/lib/emoji.py.
|
||||
return super().hashed_name(name, content, filename)
|
||||
if ext in [".png", ".gif", ".jpg", ".svg"]:
|
||||
# Similarly, don't hash-rename image files; we only serve
|
||||
# the original file paths (not the hashed file paths), and
|
||||
# so the only effect of hash-renaming these is to increase
|
||||
# the size of release tarballs with duplicate copies of these.
|
||||
#
|
||||
# One could imagine a future world in which we instead
|
||||
# used the hashed paths for these; in that case, though,
|
||||
# we should instead be removing the non-hashed paths.
|
||||
return name
|
||||
if ext in [".json", ".po", ".mo", ".mp3", ".ogg", ".html", ".md"]:
|
||||
# And same story for translation files, sound files, etc.
|
||||
return name
|
||||
return super().hashed_name(name, content, filename)
|
||||
|
||||
|
||||
class ZulipStorage(IgnoreBundlesManifestStaticFilesStorage):
|
||||
# This is a hack to use staticfiles.json from within the
|
||||
# deployment, rather than a directory under STATIC_ROOT. By doing
|
||||
# so, we can use a different copy of staticfiles.json for each
|
||||
# deployment, which ensures that we always use the correct static
|
||||
# assets for each deployment.
|
||||
def __init__(self) -> None:
|
||||
super().__init__(manifest_storage=FileSystemStorage(location=settings.DEPLOY_ROOT))
|
||||
Reference in New Issue
Block a user