Skip to content

Commit

Permalink
feat: add query filter for unaired episodes
Browse files Browse the repository at this point in the history
  • Loading branch information
revam committed Oct 22, 2024
1 parent 7c760e2 commit db9a9a6
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 11 deletions.
15 changes: 13 additions & 2 deletions Shoko.Server/API/v3/Controllers/EpisodeController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ TmdbMetadataService tmdbMetadataService
/// <param name="pageSize">The page size. Set to <code>0</code> to disable pagination.</param>
/// <param name="page">The page index.</param>
/// <param name="includeMissing">Include missing episodes in the list.</param>
/// <param name="includeUnaired">Include unaired episodes in the list.</param>
/// <param name="includeHidden">Include hidden episodes in the list.</param>
/// <param name="includeDataFrom">Include data from selected <see cref="DataSource"/>s.</param>
/// <param name="includeWatched">Include watched episodes in the list.</param>
Expand All @@ -104,6 +105,7 @@ public ActionResult<ListResult<Episode>> GetAllEpisodes(
[FromQuery, Range(0, 1000)] int pageSize = 20,
[FromQuery, Range(1, int.MaxValue)] int page = 1,
[FromQuery] IncludeOnlyFilter includeMissing = IncludeOnlyFilter.False,
[FromQuery] IncludeOnlyFilter includeUnaired = IncludeOnlyFilter.False,
[FromQuery] IncludeOnlyFilter includeHidden = IncludeOnlyFilter.False,
[FromQuery, ModelBinder(typeof(CommaDelimitedModelBinder))] HashSet<DataSource> includeDataFrom = null,
[FromQuery] IncludeOnlyFilter includeWatched = IncludeOnlyFilter.True,
Expand Down Expand Up @@ -160,8 +162,17 @@ public ActionResult<ListResult<Episode>> GetAllEpisodes(
// If we should hide missing episodes and the episode has no files, then hide it.
// Or if we should only show missing episodes and the episode has files, the hide it.
var shouldHideMissing = includeMissing == IncludeOnlyFilter.False;
var noFiles = shoko.VideoLocals.Count == 0;
if (shouldHideMissing == noFiles)
var isMissing = shoko.VideoLocals.Count == 0 && anidb.HasAired;
if (shouldHideMissing == isMissing)
return false;
}
if (includeUnaired != IncludeOnlyFilter.True)
{
// If we should hide unaired episodes and the episode has no files, then hide it.
// Or if we should only show unaired episodes and the episode has files, the hide it.
var shouldHideUnaired = includeUnaired == IncludeOnlyFilter.False;
var isUnaired = shoko.VideoLocals.Count == 0 && !anidb.HasAired;
if (shouldHideUnaired == isUnaired)
return false;
}

Expand Down
41 changes: 34 additions & 7 deletions Shoko.Server/API/v3/Controllers/SeriesController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1780,6 +1780,7 @@ public ActionResult<List<TmdbSeason>> GetTMDBSeasonsBySeriesID(
/// <param name="pageSize">The page size. Set to <code>0</code> to disable pagination.</param>
/// <param name="page">The page index.</param>
/// <param name="includeMissing">Include missing episodes in the list.</param>
/// <param name="includeUnaired">Include unaired episodes in the list.</param>
/// <param name="includeHidden">Include hidden episodes in the list.</param>
/// <param name="includeWatched">Include watched episodes in the list.</param>
/// <param name="includeManuallyLinked">Include manually linked episodes in the list.</param>
Expand All @@ -1798,6 +1799,7 @@ public ActionResult<ListResult<Episode>> GetEpisodes(
[FromQuery, Range(0, 1000)] int pageSize = 20,
[FromQuery, Range(1, int.MaxValue)] int page = 1,
[FromQuery] IncludeOnlyFilter includeMissing = IncludeOnlyFilter.False,
[FromQuery] IncludeOnlyFilter includeUnaired = IncludeOnlyFilter.False,
[FromQuery] IncludeOnlyFilter includeHidden = IncludeOnlyFilter.False,
[FromQuery] IncludeOnlyFilter includeWatched = IncludeOnlyFilter.True,
[FromQuery] IncludeOnlyFilter includeManuallyLinked = IncludeOnlyFilter.True,
Expand All @@ -1818,7 +1820,7 @@ public ActionResult<ListResult<Episode>> GetEpisodes(
if (!User.AllowedSeries(series))
return Forbid(SeriesForbiddenForUser);

return GetEpisodesInternal(series, includeMissing, includeHidden, includeWatched, includeManuallyLinked, type, search, fuzzy)
return GetEpisodesInternal(series, includeMissing, includeUnaired, includeHidden, includeWatched, includeManuallyLinked, type, search, fuzzy)
.ToListResult(a => new Episode(HttpContext, a, includeDataFrom, includeFiles, includeMediaInfo, includeAbsolutePaths, includeXRefs), page, pageSize);
}

Expand All @@ -1828,6 +1830,7 @@ public ActionResult<ListResult<Episode>> GetEpisodes(
/// <param name="seriesID">Series ID</param>
/// <param name="value">The new watched state.</param>
/// <param name="includeMissing">Include missing episodes in the list.</param>
/// <param name="includeUnaired">Include unaired episodes in the list.</param>
/// <param name="includeHidden">Include hidden episodes in the list.</param>
/// <param name="includeWatched">Include watched episodes in the list.</param>
/// <param name="type">Filter episodes by the specified <see cref="EpisodeType"/>s.</param>
Expand All @@ -1839,6 +1842,7 @@ public async Task<ActionResult> MarkSeriesWatched(
[FromRoute, Range(1, int.MaxValue)] int seriesID,
[FromQuery] bool value = true,
[FromQuery] IncludeOnlyFilter includeMissing = IncludeOnlyFilter.False,
[FromQuery] IncludeOnlyFilter includeUnaired = IncludeOnlyFilter.False,
[FromQuery] IncludeOnlyFilter includeHidden = IncludeOnlyFilter.False,
[FromQuery] IncludeOnlyFilter includeWatched = IncludeOnlyFilter.True,
[FromQuery, ModelBinder(typeof(CommaDelimitedModelBinder))] HashSet<EpisodeType> type = null,
Expand All @@ -1855,7 +1859,7 @@ public async Task<ActionResult> MarkSeriesWatched(
var userId = User.JMMUserID;
var now = DateTime.Now;
// this has a parallel query to evaluate filters and data in parallel, but that makes awaiting the SetWatchedStatus calls more difficult, so we ToList() it
await Task.WhenAll(GetEpisodesInternal(series, includeMissing, includeHidden, includeWatched, IncludeOnlyFilter.True, type, search, fuzzy).ToList()
await Task.WhenAll(GetEpisodesInternal(series, includeMissing, includeUnaired, includeHidden, includeWatched, IncludeOnlyFilter.True, type, search, fuzzy).ToList()
.Select(episode => _watchedService.SetWatchedStatus(episode, value, true, now, false, userId, true)));

_seriesService.UpdateStats(series, true, false);
Expand All @@ -1867,6 +1871,7 @@ await Task.WhenAll(GetEpisodesInternal(series, includeMissing, includeHidden, in
public ParallelQuery<SVR_AnimeEpisode> GetEpisodesInternal(
SVR_AnimeSeries series,
IncludeOnlyFilter includeMissing,
IncludeOnlyFilter includeUnaired,
IncludeOnlyFilter includeHidden,
IncludeOnlyFilter includeWatched,
IncludeOnlyFilter includeManuallyLinked,
Expand Down Expand Up @@ -1912,8 +1917,17 @@ public ParallelQuery<SVR_AnimeEpisode> GetEpisodesInternal(
// If we should hide missing episodes and the episode has no files, then hide it.
// Or if we should only show missing episodes and the episode has files, the hide it.
var shouldHideMissing = includeMissing == IncludeOnlyFilter.False;
var noFiles = shoko.VideoLocals.Count == 0;
if (shouldHideMissing == noFiles)
var isMissing = shoko.VideoLocals.Count == 0 && anidb.HasAired;
if (shouldHideMissing == isMissing)
return false;
}
if (includeUnaired != IncludeOnlyFilter.True)
{
// If we should hide unaired episodes and the episode has no files, then hide it.
// Or if we should only show unaired episodes and the episode has files, the hide it.
var shouldHideUnaired = includeUnaired == IncludeOnlyFilter.False;
var isUnaired = shoko.VideoLocals.Count == 0 && !anidb.HasAired;
if (shouldHideUnaired == isUnaired)
return false;
}

Expand Down Expand Up @@ -1979,6 +1993,7 @@ public ParallelQuery<SVR_AnimeEpisode> GetEpisodesInternal(
/// <param name="pageSize">The page size. Set to <code>0</code> to disable pagination.</param>
/// <param name="page">The page index.</param>
/// <param name="includeMissing">Include missing episodes in the list.</param>
/// <param name="includeUnaired">Include unaired episodes in the list.</param>
/// <param name="includeHidden">Include hidden episodes in the list.</param>
/// <param name="includeWatched">Include watched episodes in the list.</param>
/// <param name="type">Filter episodes by the specified <see cref="EpisodeType"/>s.</param>
Expand All @@ -1991,6 +2006,7 @@ public ParallelQuery<SVR_AnimeEpisode> GetEpisodesInternal(
[FromQuery, Range(0, 1000)] int pageSize = 20,
[FromQuery, Range(1, int.MaxValue)] int page = 1,
[FromQuery] IncludeOnlyFilter includeMissing = IncludeOnlyFilter.False,
[FromQuery] IncludeOnlyFilter includeUnaired = IncludeOnlyFilter.False,
[FromQuery] IncludeOnlyFilter includeHidden = IncludeOnlyFilter.False,
[FromQuery] IncludeOnlyFilter includeWatched = IncludeOnlyFilter.True,
[FromQuery, ModelBinder(typeof(CommaDelimitedModelBinder))] HashSet<EpisodeType> type = null,
Expand Down Expand Up @@ -2043,9 +2059,17 @@ public ParallelQuery<SVR_AnimeEpisode> GetEpisodesInternal(
// If we should hide missing episodes and the episode has no files, then hide it.
// Or if we should only show missing episodes and the episode has files, the hide it.
var shouldHideMissing = includeMissing == IncludeOnlyFilter.False;
var files = shoko?.VideoLocals.Count ?? 0;
var noFiles = files == 0;
if (shouldHideMissing == noFiles)
var isMissing = shoko.VideoLocals.Count == 0 && anidb.HasAired;
if (shouldHideMissing == isMissing)
return false;
}
if (includeUnaired != IncludeOnlyFilter.True)
{
// If we should hide unaired episodes and the episode has no files, then hide it.
// Or if we should only show unaired episodes and the episode has files, the hide it.
var shouldHideUnaired = includeUnaired == IncludeOnlyFilter.False;
var isUnaired = shoko.VideoLocals.Count == 0 && !anidb.HasAired;
if (shouldHideUnaired == isUnaired)
return false;
}

Expand Down Expand Up @@ -2101,6 +2125,7 @@ public ParallelQuery<SVR_AnimeEpisode> GetEpisodesInternal(
/// <param name="includeSpecials">Include specials in the search.</param>
/// <param name="includeOthers">Include other type episodes in the search.</param>
/// <param name="includeMissing">Include missing episodes in the list.</param>
/// <param name="includeUnaired">Include unaired episodes in the list.</param>
/// <param name="includeRewatching">Include already watched episodes in the
/// search if we determine the user is "re-watching" the series.</param>
/// <param name="includeFiles">Include files with the episodes.</param>
Expand All @@ -2115,6 +2140,7 @@ public ActionResult<Episode> GetNextUnwatchedEpisode([FromRoute, Range(1, int.Ma
[FromQuery] bool includeSpecials = true,
[FromQuery] bool includeOthers = false,
[FromQuery] bool includeMissing = true,
[FromQuery] bool includeUnaired = false,
[FromQuery] bool includeRewatching = false,
[FromQuery] bool includeFiles = false,
[FromQuery] bool includeMediaInfo = false,
Expand All @@ -2133,6 +2159,7 @@ public ActionResult<Episode> GetNextUnwatchedEpisode([FromRoute, Range(1, int.Ma
{
IncludeCurrentlyWatching = !onlyUnwatched,
IncludeMissing = includeMissing,
IncludeUnaired = includeUnaired,
IncludeRewatching = includeRewatching,
IncludeSpecials = includeSpecials,
IncludeOthers = includeOthers,
Expand Down
9 changes: 7 additions & 2 deletions Shoko.Server/Services/AnimeSeriesService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -893,6 +893,11 @@ public class NextUpQueryOptions
/// </summary>
public bool IncludeMissing { get; set; } = false;

/// <summary>
/// Include unaired episodes in the search.
/// </summary>
public bool IncludeUnaired { get; set; } = false;

/// <summary>
/// Include already watched episodes in the search if we determine the
/// user is "re-watching" the series.
Expand Down Expand Up @@ -981,7 +986,7 @@ public SVR_AnimeEpisode GetNextEpisode(SVR_AnimeSeries series, int userID, NextU

var (nextEpisode, _) = episodeList
.Skip(nextIndex)
.FirstOrDefault(options.IncludeMissing ? _ => true : tuple => tuple.episode.VideoLocals.Count > 0);
.FirstOrDefault(options.IncludeUnaired ? _ => true : options.IncludeMissing ? tuple => tuple.AniDB_Episode.HasAired : tuple => tuple.episode.VideoLocals.Count > 0 && tuple.AniDB_Episode.HasAired);
return nextEpisode;
}
}
Expand All @@ -996,7 +1001,7 @@ public SVR_AnimeEpisode GetNextEpisode(SVR_AnimeSeries series, int userID, NextU

return !episodeUserRecord.WatchedDate.HasValue;
})
.FirstOrDefault(options.IncludeMissing ? _ => true : tuple => tuple.episode.VideoLocals.Count > 0);
.FirstOrDefault(options.IncludeUnaired ? _ => true : options.IncludeMissing ? tuple => tuple.AniDB_Episode.HasAired : tuple => tuple.episode.VideoLocals.Count > 0 && tuple.AniDB_Episode.HasAired);

// Disable first episode from showing up in the search.
if (options.DisableFirstEpisode && anidbEpisode is not null && anidbEpisode.EpisodeType == (int)EpisodeType.Episode && anidbEpisode.EpisodeNumber == 1)
Expand Down

0 comments on commit db9a9a6

Please sign in to comment.