extensions-website/files/utils.py

84 lines
2.7 KiB
Python

from pathlib import Path
import hashlib
import io
import os
import logging
import zipfile
import toml
from lxml import etree
logger = logging.getLogger(__name__)
MODULE_DIR = Path(__file__).resolve().parent
THEME_SCHEMA = []
def _get_theme_schema():
if not THEME_SCHEMA:
with open(MODULE_DIR / 'theme.xsd', 'rb') as f:
THEME_SCHEMA.append(etree.XMLSchema(etree.XML(f.read())))
return THEME_SCHEMA[0]
def get_sha256(file_obj):
"""Calculate a sha256 hash for `file_obj`.
`file_obj` must either be be an open file descriptor, in which case the
caller needs to take care of closing it properly, or a django File-like
object with a chunks() method to iterate over its contents.
"""
hash_ = hashlib.sha256()
if hasattr(file_obj, 'chunks') and callable(file_obj.chunks):
iterator = file_obj.chunks()
else:
iterator = iter(lambda: file_obj.read(io.DEFAULT_BUFFER_SIZE), b'')
for chunk in iterator:
hash_.update(chunk)
# This file might be read again by validation or other utilities
file_obj.seek(0)
return hash_.hexdigest()
def get_sha256_from_value(value: str):
"""Calculate a sha256 hash for a given string value."""
hash_ = hashlib.sha256()
hash_.update(str(value).encode())
return hash_.hexdigest()
def find_file_inside_zip_list(file_to_read: str, name_list: list) -> str:
"""Return the first occurance of file_to_read insize a zip name_list"""
for file_path in name_list:
# Remove leading/trailing whitespace from file path
file_path_stripped = file_path.strip()
# Check if the basename of the stripped path is equal to the target file name
if os.path.basename(file_path_stripped) == file_to_read:
return file_path_stripped
return None
def read_manifest_from_zip(archive_path):
file_to_read = 'blender_manifest.toml'
try:
with zipfile.ZipFile(archive_path) as myzip:
manifest_filepath = find_file_inside_zip_list(file_to_read, myzip.namelist())
if manifest_filepath is None:
logger.info(f"File '{file_to_read}' not found in the archive.")
return None
# Extract the file content
with myzip.open(manifest_filepath) as file_content:
# TODO: handle TOML loading error
toml_content = toml.loads(file_content.read().decode())
return toml_content
except toml.decoder.TomlDecodeError as e:
# TODO: This error should be propagate to the user
logger.error(f"Manifest Error: {e.msg}")
except Exception as e:
logger.error(f"Error extracting from archive: {e}")
return None