fix: remove missing episode from DB and memory after download completes

- Fixed _remove_episode_from_missing_list to also update in-memory
  Serie.episodeDict and refresh series_list
- Added _remove_episode_from_memory helper method
- Enhanced logging for download completion and episode removal
- Added 5 unit tests for missing episode removal
This commit is contained in:
2026-02-26 21:02:08 +01:00
parent 624c0db16e
commit b34ee59bca
3 changed files with 363 additions and 19 deletions

View File

@@ -210,8 +210,12 @@ class DownloadService:
) -> bool:
"""Remove a downloaded episode from the missing episodes list.
Called when a download completes successfully to update the
database so the episode no longer appears as missing.
Called when a download completes successfully to update both:
1. The database (Episode record deleted)
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.
Args:
series_key: Unique provider key for the series
@@ -225,6 +229,14 @@ class DownloadService:
from src.server.database.connection import get_db_session
from src.server.database.service import EpisodeService
logger.info(
"Attempting to remove missing episode from DB: "
"%s S%02dE%02d",
series_key,
season,
episode,
)
async with get_db_session() as db:
deleted = await EpisodeService.delete_by_series_and_episode(
db=db,
@@ -234,25 +246,136 @@ class DownloadService:
)
if deleted:
logger.info(
"Removed episode from missing list: "
"Successfully removed episode from DB missing list: "
"%s S%02dE%02d",
series_key,
season,
episode,
)
# Clear the anime service cache so list_missing
# returns updated data
try:
self._anime_service._cached_list_missing.cache_clear()
except Exception:
pass
return deleted
else:
logger.warning(
"Episode not found in DB missing list "
"(may already be removed): %s S%02dE%02d",
series_key,
season,
episode,
)
# Update in-memory Serie.episodeDict so list_missing is
# immediately consistent without a full DB reload
self._remove_episode_from_memory(series_key, season, episode)
# Clear the anime service cache so list_missing
# re-reads from the (now updated) in-memory state
try:
self._anime_service._cached_list_missing.cache_clear()
logger.debug(
"Cleared list_missing cache after removing "
"%s S%02dE%02d",
series_key,
season,
episode,
)
except Exception:
pass
return deleted
except Exception as e:
logger.error(
"Failed to remove episode from missing list: %s", e
"Failed to remove episode from missing list: "
"%s S%02dE%02d - %s",
series_key,
season,
episode,
e,
)
return False
def _remove_episode_from_memory(
self,
series_key: str,
season: int,
episode: int,
) -> None:
"""Remove an episode from the in-memory Serie.episodeDict.
Updates the SeriesApp's keyDict so that list_missing and
series_list reflect the removal immediately without needing
a full database reload.
Args:
series_key: Unique provider key for the series
season: Season number
episode: Episode number within season
"""
try:
app = self._anime_service._app
serie = app.list.keyDict.get(series_key)
if not serie:
logger.debug(
"Series %s not found in keyDict, skipping "
"in-memory removal",
series_key,
)
return
ep_dict = serie.episodeDict
if season not in ep_dict:
logger.debug(
"Season %d not in episodeDict for %s, "
"skipping in-memory removal",
season,
series_key,
)
return
if episode in ep_dict[season]:
ep_dict[season].remove(episode)
logger.info(
"Removed episode from in-memory episodeDict: "
"%s S%02dE%02d (remaining in season: %s)",
series_key,
season,
episode,
ep_dict[season],
)
# Remove the season key if no episodes remain
if not ep_dict[season]:
del ep_dict[season]
logger.info(
"Removed empty season %d from episodeDict "
"for %s",
season,
series_key,
)
# Refresh series_list so GetMissingEpisode()
# reflects the change
app.series_list = app.list.GetMissingEpisode()
logger.info(
"Refreshed series_list: %d series with "
"missing episodes remaining",
len(app.series_list),
)
else:
logger.debug(
"Episode %d not in season %d for %s, "
"already removed from memory",
episode,
season,
series_key,
)
except Exception as e:
logger.warning(
"Failed to remove episode from in-memory state: "
"%s S%02dE%02d - %s",
series_key,
season,
episode,
e,
)
async def _init_queue_progress(self) -> None:
"""Initialize the download queue progress tracking.
@@ -933,18 +1056,35 @@ class DownloadService:
self._completed_items.append(item)
# Delete completed item from database (status is in-memory)
logger.info(
"Download succeeded, cleaning up: item_id=%s, "
"serie_key=%s, S%02dE%02d",
item.id,
item.serie_id,
item.episode.season,
item.episode.episode,
)
# Delete completed item from download queue database
await self._delete_from_database(item.id)
# Remove episode from missing episodes list in database
await self._remove_episode_from_missing_list(
# Remove episode from 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,
)
logger.info(
"Download completed successfully: item_id=%s", item.id
"Download completed successfully: item_id=%s, "
"serie_key=%s, S%02dE%02d, "
"missing_episode_removed=%s",
item.id,
item.serie_id,
item.episode.season,
item.episode.episode,
removed,
)
else:
raise AnimeServiceError("Download returned False")