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:
parent
08f816a954
commit
4780f68a23
@ -419,6 +419,29 @@ class SeriesApp:
|
||||
|
||||
return download_success
|
||||
|
||||
except InterruptedError:
|
||||
# Download was cancelled - propagate the cancellation
|
||||
logger.info(
|
||||
"Download cancelled: %s (key: %s) S%02dE%02d",
|
||||
serie_folder,
|
||||
key,
|
||||
season,
|
||||
episode,
|
||||
)
|
||||
# Fire download cancelled event
|
||||
self._events.download_status(
|
||||
DownloadStatusEventArgs(
|
||||
serie_folder=serie_folder,
|
||||
key=key,
|
||||
season=season,
|
||||
episode=episode,
|
||||
status="cancelled",
|
||||
message="Download cancelled by user",
|
||||
item_id=item_id,
|
||||
)
|
||||
)
|
||||
raise # Re-raise to propagate cancellation
|
||||
|
||||
except Exception as e: # pylint: disable=broad-except
|
||||
logger.error(
|
||||
"Download error: %s (key: %s) S%02dE%02d - %s",
|
||||
|
||||
@ -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}"
|
||||
|
||||
@ -846,6 +846,7 @@ class AnimeService:
|
||||
|
||||
Raises:
|
||||
AnimeServiceError: If download fails
|
||||
InterruptedError: If download was cancelled
|
||||
|
||||
Note:
|
||||
The 'key' parameter is the primary identifier used for all
|
||||
@ -864,6 +865,10 @@ class AnimeService:
|
||||
key=key,
|
||||
item_id=item_id,
|
||||
)
|
||||
except InterruptedError:
|
||||
# Download was cancelled - re-raise for proper handling
|
||||
logger.info("Download cancelled, propagating cancellation")
|
||||
raise
|
||||
except Exception as exc:
|
||||
logger.exception("download failed")
|
||||
raise AnimeServiceError("Download failed") from exc
|
||||
|
||||
@ -952,7 +952,7 @@ class DownloadService:
|
||||
except asyncio.CancelledError:
|
||||
# Handle task cancellation during shutdown
|
||||
logger.info(
|
||||
"Download cancelled during shutdown: item_id=%s",
|
||||
"Download task cancelled: item_id=%s",
|
||||
item.id,
|
||||
)
|
||||
item.status = DownloadStatus.CANCELLED
|
||||
@ -965,6 +965,23 @@ class DownloadService:
|
||||
# Re-save to database as pending
|
||||
await self._save_to_database(item)
|
||||
raise # Re-raise to properly cancel the task
|
||||
|
||||
except InterruptedError:
|
||||
# Handle download cancellation from provider
|
||||
logger.info(
|
||||
"Download interrupted/cancelled: item_id=%s",
|
||||
item.id,
|
||||
)
|
||||
item.status = DownloadStatus.CANCELLED
|
||||
item.completed_at = datetime.now(timezone.utc)
|
||||
# Delete cancelled item from database
|
||||
await self._delete_from_database(item.id)
|
||||
# Return item to pending queue if not shutting down
|
||||
if not self._is_shutting_down:
|
||||
self._add_to_pending_queue(item, front=True)
|
||||
# Re-save to database as pending
|
||||
await self._save_to_database(item)
|
||||
# Don't re-raise - this is handled gracefully
|
||||
|
||||
except Exception as e:
|
||||
# Handle failure
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user