61 lines
1.8 KiB
Python

"""Shared, presentation-independent helpers for document ZIP exports."""
import posixpath
from zipfile import ZipFile
def safe_archive_part(value: object, fallback: str) -> str:
"""Sanitize one ZIP path component while preserving Unicode names."""
raw = str(value or fallback)
cleaned = "".join(
"_" if char in {"/", "\\"} or ord(char) < 32 else char for char in raw
).strip(" .")
return cleaned or fallback
def unique_archive_path(archive_path: str, used_paths: set[str]) -> str:
"""Reserve a deterministic, collision-free path inside an archive."""
if archive_path not in used_paths:
used_paths.add(archive_path)
return archive_path
directory = posixpath.dirname(archive_path)
filename = posixpath.basename(archive_path)
stem, extension = posixpath.splitext(filename)
counter = 2
while True:
candidate_name = f"{stem} ({counter}){extension}"
candidate = (
posixpath.join(directory, candidate_name) if directory else candidate_name
)
if candidate not in used_paths:
used_paths.add(candidate)
return candidate
counter += 1
def document_archive_filename(document) -> str:
"""Return the best available filename for a document asset."""
return safe_archive_part(
getattr(document.file, "original_filename", "")
or getattr(document.file, "name", "")
or document.title
or f"document-{document.id}",
f"document-{document.id}",
)
def write_document_to_archive(
archive: ZipFile,
*,
document,
archive_path: str,
) -> None:
"""Read a filer asset safely and write it to an open ZIP archive."""
file_obj = document.file.file
try:
file_obj.open("rb")
archive.writestr(archive_path, file_obj.read())
finally:
file_obj.close()