refactor(download): mark episode downloaded instead of deleting
Change _remove_episode_from_missing_list to set is_downloaded=True and populate file_path via EpisodeService.mark_downloaded, instead of deleting the Episode row. Preserves download history so queries can distinguish series with downloaded episodes from completely unwatched series. - Pass serie_folder to construct file_path - Look up series_id via AnimeSeriesService.get_by_key - Update tests to mock mark_downloaded path - Document episode lifecycle in docs/DEVELOPMENT.md Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -14,6 +14,7 @@ import uuid
|
||||
from collections import deque
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Dict, List, Optional
|
||||
|
||||
import structlog
|
||||
@@ -68,6 +69,7 @@ class DownloadService:
|
||||
progress_service: Optional progress service for tracking
|
||||
"""
|
||||
self._anime_service = anime_service
|
||||
self._directory = anime_service._directory
|
||||
self._max_retries = max_retries
|
||||
self._progress_service = progress_service or get_progress_service()
|
||||
|
||||
@@ -210,30 +212,33 @@ class DownloadService:
|
||||
series_key: str,
|
||||
season: int,
|
||||
episode: int,
|
||||
serie_folder: Optional[str] = None,
|
||||
) -> bool:
|
||||
"""Remove a downloaded episode from the missing episodes list.
|
||||
"""Mark a downloaded episode as downloaded instead of deleting it.
|
||||
|
||||
Called when a download completes successfully to update both:
|
||||
1. The database (Episode record deleted)
|
||||
1. The database (Episode record marked is_downloaded=True)
|
||||
2. The in-memory Serie.episodeDict and series_list cache
|
||||
|
||||
This ensures the episode no longer appears as missing in both
|
||||
the API responses and the UI immediately after download.
|
||||
the API responses and the UI immediately after download,
|
||||
while preserving the download history.
|
||||
|
||||
Args:
|
||||
series_key: Unique provider key for the series
|
||||
season: Season number
|
||||
episode: Episode number within season
|
||||
serie_folder: Series folder name (required for file_path)
|
||||
|
||||
Returns:
|
||||
True if episode was removed, False otherwise
|
||||
True if episode was updated, False otherwise
|
||||
"""
|
||||
try:
|
||||
from src.server.database.connection import get_db_session
|
||||
from src.server.database.service import EpisodeService
|
||||
from src.server.database.service import EpisodeService, AnimeSeriesService
|
||||
|
||||
logger.info(
|
||||
"Attempting to remove missing episode from DB: "
|
||||
"Attempting to mark episode as downloaded in DB: "
|
||||
"%s S%02dE%02d",
|
||||
series_key,
|
||||
season,
|
||||
@@ -241,28 +246,63 @@ class DownloadService:
|
||||
)
|
||||
|
||||
async with get_db_session() as db:
|
||||
deleted = await EpisodeService.delete_by_series_and_episode(
|
||||
# Get series by key to find series_id
|
||||
series = await AnimeSeriesService.get_by_key(db, series_key)
|
||||
if not series:
|
||||
logger.warning(
|
||||
"Series not found for key: %s", series_key
|
||||
)
|
||||
return False
|
||||
|
||||
# Get episode by series_id, season, episode_number
|
||||
ep = await EpisodeService.get_by_episode(
|
||||
db=db,
|
||||
series_key=series_key,
|
||||
series_id=series.id,
|
||||
season=season,
|
||||
episode_number=episode,
|
||||
)
|
||||
if deleted:
|
||||
if not ep:
|
||||
logger.warning(
|
||||
"Episode not found in DB: %s S%02dE%02d",
|
||||
series_key,
|
||||
season,
|
||||
episode,
|
||||
)
|
||||
return False
|
||||
|
||||
# Construct file_path if serie_folder provided
|
||||
file_path = None
|
||||
if serie_folder:
|
||||
season_folder = f"Season {season}"
|
||||
file_path = str(
|
||||
Path(self._directory) / serie_folder / season_folder
|
||||
)
|
||||
|
||||
# Mark episode as downloaded instead of deleting
|
||||
updated = await EpisodeService.mark_downloaded(
|
||||
db=db,
|
||||
episode_id=ep.id,
|
||||
file_path=file_path or "",
|
||||
)
|
||||
|
||||
if updated:
|
||||
logger.info(
|
||||
"Successfully removed episode from DB missing list: "
|
||||
"Marked episode as downloaded in DB: "
|
||||
"%s S%02dE%02d, file_path=%s",
|
||||
series_key,
|
||||
season,
|
||||
episode,
|
||||
file_path,
|
||||
)
|
||||
else:
|
||||
logger.warning(
|
||||
"Failed to mark episode as downloaded: "
|
||||
"%s S%02dE%02d",
|
||||
series_key,
|
||||
season,
|
||||
episode,
|
||||
)
|
||||
else:
|
||||
logger.warning(
|
||||
"Episode not found in DB missing list "
|
||||
"(may already be removed): %s S%02dE%02d",
|
||||
series_key,
|
||||
season,
|
||||
episode,
|
||||
)
|
||||
return False
|
||||
|
||||
# Update in-memory Serie.episodeDict so list_missing is
|
||||
# immediately consistent without a full DB reload
|
||||
@@ -273,8 +313,8 @@ class DownloadService:
|
||||
try:
|
||||
self._anime_service._cached_list_missing.cache_clear()
|
||||
logger.debug(
|
||||
"Cleared list_missing cache after removing "
|
||||
"%s S%02dE%02d",
|
||||
"Cleared list_missing cache after marking "
|
||||
"%s S%02dE%02d as downloaded",
|
||||
series_key,
|
||||
season,
|
||||
episode,
|
||||
@@ -282,10 +322,10 @@ class DownloadService:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return deleted
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
"Failed to remove episode from missing list: "
|
||||
"Failed to mark episode as downloaded: "
|
||||
"%s S%02dE%02d - %s",
|
||||
series_key,
|
||||
season,
|
||||
@@ -1119,12 +1159,13 @@ class DownloadService:
|
||||
# Delete completed item from download queue database
|
||||
await self._delete_from_database(item.id)
|
||||
|
||||
# Remove episode from missing episodes list
|
||||
# Mark episode as downloaded in missing episodes list
|
||||
# (both database and in-memory)
|
||||
removed = await self._remove_episode_from_missing_list(
|
||||
series_key=item.serie_id,
|
||||
season=item.episode.season,
|
||||
episode=item.episode.episode,
|
||||
serie_folder=item.serie_folder,
|
||||
)
|
||||
|
||||
logger.info(
|
||||
|
||||
Reference in New Issue
Block a user