zulip/zerver/lib/storage.py

116 lines
4.5 KiB
Python

# Useful reading is https://zulip.readthedocs.io/en/latest/subsystems/front-end-build-process.html
import os
import shutil
from typing import Any, Dict, List, Optional, Tuple
from django.conf import settings
from django.contrib.staticfiles.storage import ManifestStaticFilesStorage
from pipeline.storage import PipelineMixin
from zerver.lib.str_utils import force_str
class AddHeaderMixin:
def post_process(self, paths: Dict[str, Tuple['ZulipStorage', str]], dry_run: bool=False,
**kwargs: Any) -> List[Tuple[str, str, bool]]:
if dry_run:
return []
with open(settings.STATIC_HEADER_FILE, 'rb') as header_file:
header = header_file.read().decode(settings.FILE_CHARSET)
# A dictionary of path to tuples of (old_path, new_path,
# processed). The return value of this method is the values
# of this dictionary
ret_dict = {}
for name in paths:
storage, path = paths[name]
if not path.startswith('min/') or not path.endswith('.css'):
ret_dict[path] = (path, path, False)
continue
# Prepend the header
with storage.open(path, 'rb') as orig_file:
orig_contents = orig_file.read().decode(settings.FILE_CHARSET)
storage.delete(path)
with storage.open(path, 'w') as new_file:
new_file.write(force_str(header + orig_contents, encoding=settings.FILE_CHARSET))
ret_dict[path] = (path, path, True)
super_class = super()
if hasattr(super_class, 'post_process'):
super_ret = super_class.post_process(paths, dry_run, **kwargs) # type: ignore # https://github.com/python/mypy/issues/2956
else:
super_ret = []
# Merge super class's return value with ours
for val in super_ret:
old_path, new_path, processed = val
if processed:
ret_dict[old_path] = val
return list(ret_dict.values())
class RemoveUnminifiedFilesMixin:
def post_process(self, paths: Dict[str, Tuple['ZulipStorage', str]], dry_run: bool=False,
**kwargs: Any) -> List[Tuple[str, str, bool]]:
if dry_run:
return []
root = settings.STATIC_ROOT
to_remove = ['js']
for tree in to_remove:
shutil.rmtree(os.path.join(root, tree))
is_valid = lambda p: all([not p.startswith(k) for k in to_remove])
paths = {k: v for k, v in paths.items() if is_valid(k)}
super_class = super()
if hasattr(super_class, 'post_process'):
return super_class.post_process(paths, dry_run, **kwargs) # type: ignore # https://github.com/python/mypy/issues/2956
return []
class IgnoreBundlesManifestStaticFilesStorage(ManifestStaticFilesStorage):
def hashed_name(self, name: str, content: Optional[str]=None, filename: Optional[str]=None) -> str:
if (name.startswith("webpack-bundles") and
os.path.splitext(name)[1] in ['.js', '.css', '.map']):
# 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
return super().hashed_name(name, content, filename)
if settings.PRODUCTION:
# 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.
ManifestStaticFilesStorage.manifest_name = os.path.join(settings.DEPLOY_ROOT,
"staticfiles.json")
orig_path = ManifestStaticFilesStorage.path
def path(self: ManifestStaticFilesStorage, name: str) -> str:
if name == ManifestStaticFilesStorage.manifest_name:
return name
return orig_path(self, name)
ManifestStaticFilesStorage.path = path
class ZulipStorage(PipelineMixin,
AddHeaderMixin, RemoveUnminifiedFilesMixin,
IgnoreBundlesManifestStaticFilesStorage):
pass