Fix: Use yt_dlp.utils.DownloadCancelled for proper download cancellation

- Import and use DownloadCancelled exception which YT-DLP properly handles
- Add InterruptedError handling throughout the call chain
- Fire 'cancelled' status event when download is cancelled
- Handle InterruptedError in DownloadService to set CANCELLED status
This commit is contained in:
2025-12-27 19:38:12 +01:00
parent 08f816a954
commit 4780f68a23
4 changed files with 74 additions and 9 deletions

View File

@@ -4,6 +4,8 @@ import logging
import os
import re
import shutil
import signal
import sys
import threading
from pathlib import Path
from urllib.parse import quote
@@ -14,6 +16,7 @@ from fake_useragent import UserAgent
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
from yt_dlp import YoutubeDL
from yt_dlp.utils import DownloadCancelled
from ..interfaces.providers import Providers
from .base_provider import Loader
@@ -308,14 +311,19 @@ class AniworldLoader(Loader):
)
logging.debug("Direct link obtained from provider")
# Create a cancellation-aware progress hook
# Create a cancellation-aware progress hook using DownloadCancelled
# which YT-DLP properly handles
cancel_flag = self._cancel_flag
def cancellation_check_hook(d):
"""Progress hook that checks for cancellation."""
"""Progress hook that checks for cancellation.
Uses yt_dlp.utils.DownloadCancelled which is properly
handled by YT-DLP to abort downloads immediately.
"""
if cancel_flag.is_set():
logging.info("Cancellation detected in progress hook")
raise InterruptedError("Download cancelled")
raise DownloadCancelled("Download cancelled by user")
ydl_opts = {
'fragment_retries': float('inf'),
@@ -334,10 +342,10 @@ class AniworldLoader(Loader):
if progress_callback:
# Wrap the callback to add logging and keep cancellation check
def logged_progress_callback(d):
# Check cancellation first
# Check cancellation first - use DownloadCancelled
if cancel_flag.is_set():
logging.info("Cancellation detected in progress callback")
raise InterruptedError("Download cancelled")
raise DownloadCancelled("Download cancelled by user")
logging.debug(
f"YT-DLP progress: status={d.get('status')}, "
f"downloaded={d.get('downloaded_bytes')}, "
@@ -385,16 +393,19 @@ class AniworldLoader(Loader):
)
self.clear_cache()
return False
except InterruptedError:
except (InterruptedError, DownloadCancelled) as e:
# Re-raise cancellation errors
logging.info("Download interrupted, propagating cancellation")
logging.info(
"Download cancelled: %s, propagating cancellation",
type(e).__name__
)
# Clean up temp file if exists
if os.path.exists(temp_path):
try:
os.remove(temp_path)
except OSError:
pass
raise
raise InterruptedError("Download cancelled") from e
except BrokenPipeError as e:
logging.error(
f"Broken pipe error with provider {provider}: {e}. "
@@ -403,6 +414,15 @@ class AniworldLoader(Loader):
# Try next provider if available
continue
except Exception as e:
# Check if this is a cancellation wrapped in another exception
if self.is_cancelled():
logging.info("Download cancelled (detected in exception handler)")
if os.path.exists(temp_path):
try:
os.remove(temp_path)
except OSError:
pass
raise InterruptedError("Download cancelled") from e
logging.error(
f"YoutubeDL download failed with provider {provider}: "
f"{type(e).__name__}: {e}"