mirror of
https://github.com/rommapp/romm.git
synced 2026-06-29 07:16:28 +00:00
- Introduced a new fixture `multi_file_rom` in `conftest.py` to create a ROM with multiple files for testing purposes. - Updated `test_manual.py` and `test_soundtrack.py` to utilize the new fixture for testing manual uploads and soundtrack functionalities. - Enhanced the audio tag extraction logic in `audio_tags.py` to handle oversized audio files and added a function to check allowed audio file extensions. - Modified `nginx.py` to support inline file serving for audio files, allowing for better streaming capabilities. - Improved error handling in the Vue components for soundtrack management, including user feedback for playback errors and metadata loading issues. - Refactored the soundtrack player store to use local storage for volume and mute settings, simplifying state management. - Added new localization strings for soundtrack player actions and error messages in both English (US and GB) locale files.
85 lines
2.7 KiB
Python
85 lines
2.7 KiB
Python
import dataclasses
|
|
from collections.abc import Collection
|
|
from typing import Any
|
|
from urllib.parse import quote
|
|
|
|
from anyio import Path
|
|
from fastapi.responses import Response
|
|
|
|
|
|
@dataclasses.dataclass(frozen=True)
|
|
class ZipContentLine:
|
|
"""Dataclass for lines returned in the response body, for usage with the `mod_zip` module.
|
|
|
|
Reference:
|
|
https://github.com/evanmiller/mod_zip?tab=readme-ov-file#usage
|
|
"""
|
|
|
|
crc32: str | None
|
|
size_bytes: int
|
|
encoded_location: str
|
|
filename: str
|
|
|
|
def __str__(self) -> str:
|
|
crc32 = self.crc32 or "-"
|
|
return f"{crc32} {self.size_bytes} {self.encoded_location} {self.filename}"
|
|
|
|
|
|
class ZipResponse(Response):
|
|
"""Response class for returning a ZIP archive with multiple files, using the `mod_zip` module."""
|
|
|
|
def __init__(
|
|
self,
|
|
*,
|
|
content_lines: Collection[ZipContentLine],
|
|
filename: str,
|
|
**kwargs: Any,
|
|
):
|
|
if kwargs.get("content"):
|
|
raise ValueError(
|
|
"Argument 'content' must not be provided, as it is generated from 'content_lines'"
|
|
)
|
|
|
|
kwargs["content"] = "\n".join(str(line) for line in content_lines)
|
|
kwargs.setdefault("headers", {}).update(
|
|
{
|
|
"Content-Disposition": f"attachment; filename*=UTF-8''{filename}; filename=\"{filename}\"",
|
|
"X-Archive-Files": "zip",
|
|
"X-Archive-Charset": "UTF-8",
|
|
}
|
|
)
|
|
|
|
super().__init__(**kwargs)
|
|
|
|
|
|
class FileRedirectResponse(Response):
|
|
"""Response class for serving a file download by using the X-Accel-Redirect header."""
|
|
|
|
def __init__(
|
|
self,
|
|
*,
|
|
download_path: Path,
|
|
filename: str | None = None,
|
|
disposition: str = "attachment",
|
|
**kwargs: Any,
|
|
):
|
|
"""
|
|
Arguments:
|
|
- download_path: Path to the file to be served.
|
|
- filename: Name of the file to be served. If not provided, the file name from the
|
|
download_path is used.
|
|
- disposition: "attachment" (default) forces a download; "inline" lets the
|
|
browser render/stream the file (required for <audio> to issue Range
|
|
requests and seek). Nginx itself still handles the Range parsing.
|
|
"""
|
|
media_type = kwargs.pop("media_type", "application/octet-stream")
|
|
filename = filename or download_path.name
|
|
kwargs.setdefault("headers", {}).update(
|
|
{
|
|
"Content-Disposition": f"{disposition}; filename*=UTF-8''{quote(filename)}; filename=\"{quote(filename)}\"",
|
|
"X-Accel-Redirect": quote(str(download_path)),
|
|
}
|
|
)
|
|
|
|
super().__init__(media_type=media_type, **kwargs)
|