mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-04 14:03:30 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			131 lines
		
	
	
		
			5.4 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			131 lines
		
	
	
		
			5.4 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
# 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))
 |