From 341b1d96499155d368ab9fbbc59aac596a6a779f Mon Sep 17 00:00:00 2001 From: yblzhua <1963225049730818048+yblzhua@users.noreply.github.com> Date: Thu, 12 Mar 2026 12:31:01 +0800 Subject: [PATCH 1/2] Fix suffix byte range parsing --- app/services/media_server.py | 11 ++++++++--- tests/test_media_server.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 3 deletions(-) create mode 100644 tests/test_media_server.py diff --git a/app/services/media_server.py b/app/services/media_server.py index 428ca76..140d49e 100644 --- a/app/services/media_server.py +++ b/app/services/media_server.py @@ -16,12 +16,17 @@ def build_range_response(file_path: str, range_header: str) -> Response: file_size = os.path.getsize(file_path) stat = os.stat(file_path) - # Parse range header: "bytes=start-end" + # Parse range header: "bytes=start-end" or suffix-byte-range "bytes=-N" range_spec = range_header.replace("bytes=", "").strip() parts = range_spec.split("-") - start = int(parts[0]) if parts[0] else 0 - end = int(parts[1]) if len(parts) > 1 and parts[1] else file_size - 1 + if not parts[0] and len(parts) > 1 and parts[1]: + suffix_length = int(parts[1]) + start = max(0, file_size - suffix_length) + end = file_size - 1 + else: + start = int(parts[0]) if parts[0] else 0 + end = int(parts[1]) if len(parts) > 1 and parts[1] else file_size - 1 # Clamp values start = max(0, start) diff --git a/tests/test_media_server.py b/tests/test_media_server.py new file mode 100644 index 0000000..cef17b9 --- /dev/null +++ b/tests/test_media_server.py @@ -0,0 +1,29 @@ +from pathlib import Path + +from app.services.media_server import build_range_response + + +def test_build_range_response_supports_suffix_byte_range(tmp_path: Path): + file_path = tmp_path / "sample.bin" + content = b"0123456789" + file_path.write_bytes(content) + + response = build_range_response(str(file_path), "bytes=-4") + + assert response.status_code == 206 + assert response.body == b"6789" + assert response.headers["content-range"] == "bytes 6-9/10" + assert response.headers["content-length"] == "4" + + +def test_build_range_response_keeps_standard_range_behavior(tmp_path: Path): + file_path = tmp_path / "sample.bin" + content = b"0123456789" + file_path.write_bytes(content) + + response = build_range_response(str(file_path), "bytes=2-5") + + assert response.status_code == 206 + assert response.body == b"2345" + assert response.headers["content-range"] == "bytes 2-5/10" + assert response.headers["content-length"] == "4" From 4d5174c477d74ffb3f24188b9c3567a626869b83 Mon Sep 17 00:00:00 2001 From: yblzhua <1963225049730818048+yblzhua@users.noreply.github.com> Date: Thu, 12 Mar 2026 12:31:34 +0800 Subject: [PATCH 2/2] Mark terminal download failures as failed --- app/tasks/download_tasks.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/app/tasks/download_tasks.py b/app/tasks/download_tasks.py index 7fc569f..10dbe47 100644 --- a/app/tasks/download_tasks.py +++ b/app/tasks/download_tasks.py @@ -213,10 +213,15 @@ def progress_cb(percentage: float) -> None: except Exception as exc: logger.exception("Download failed for video %s", video_id) - # Mark as FAILED if we've exhausted retries + # Mark as FAILED on terminal failures: + # - retries exhausted for retryable RuntimeError exceptions + # - any non-retryable exception that will not be retried by Celery try: video = db.get(Video, video_id) - if video and self.request.retries >= self.max_retries: + is_retryable = isinstance(exc, RuntimeError) + retries_exhausted = self.request.retries >= self.max_retries + terminal_failure = (is_retryable and retries_exhausted) or (not is_retryable) + if video and terminal_failure: video.status = "FAILED" db.commit() except Exception: