From b7f03561e246ae4796b0f751330de44f13639488 Mon Sep 17 00:00:00 2001 From: Mikal Stordal Date: Sat, 14 Dec 2024 22:03:45 +0100 Subject: [PATCH] feat: add release management missing episodes controller --- .../ReleaseManagementController.cs | 14 +-- ...leaseManagementDuplicateFilesController.cs | 4 +- ...easeManagementMissingEpisodesController.cs | 115 ++++++++++++++++++ ...aseManagementMultipleReleasesController.cs | 14 +-- Shoko.Server/API/v3/Models/Shoko/Series.cs | 23 +--- .../Cached/AnimeEpisodeRepository.cs | 79 +++++++++++- .../Cached/AnimeSeriesRepository.cs | 27 +++- Shoko.Server/Services/AnimeSeriesService.cs | 9 +- 8 files changed, 237 insertions(+), 48 deletions(-) create mode 100644 Shoko.Server/API/v3/Controllers/ReleaseManagementMissingEpisodesController.cs diff --git a/Shoko.Server/API/v3/Controllers/ReleaseManagementController.cs b/Shoko.Server/API/v3/Controllers/ReleaseManagementController.cs index 813c69973..8b7838b81 100644 --- a/Shoko.Server/API/v3/Controllers/ReleaseManagementController.cs +++ b/Shoko.Server/API/v3/Controllers/ReleaseManagementController.cs @@ -33,20 +33,20 @@ public class ReleaseManagementController(ISettingsProvider settingsProvider) : B /// Page number. /// [HttpGet("Series")] - public ActionResult> GetSeriesWithMultipleReleases( + public ActionResult> GetSeriesWithMultipleReleases( [FromQuery, ModelBinder(typeof(CommaDelimitedModelBinder))] HashSet includeDataFrom = null, [FromQuery] bool ignoreVariations = true, [FromQuery] bool onlyFinishedSeries = false, [FromQuery, Range(0, 1000)] int pageSize = 100, [FromQuery, Range(1, int.MaxValue)] int page = 1) { - IEnumerable enumerable = RepoFactory.AnimeSeries.GetWithMultipleReleases(ignoreVariations); + var enumerable = RepoFactory.AnimeSeries.GetWithMultipleReleases(ignoreVariations); if (onlyFinishedSeries) enumerable = enumerable.Where(a => a.AniDB_Anime.GetFinishedAiring()); return enumerable .OrderBy(series => series.PreferredTitle) .ThenBy(series => series.AniDB_ID) - .ToListResult(series => new Series.WithMultipleReleasesResult(series, User.JMMUserID, includeDataFrom, ignoreVariations), page, pageSize); + .ToListResult(series => new Series.WithEpisodeCount(RepoFactory.AnimeEpisode.GetWithMultipleReleases(ignoreVariations, series.AniDB_ID).Count(), series, User.JMMUserID, includeDataFrom), page, pageSize); } /// @@ -81,7 +81,7 @@ public ActionResult> GetEpisodesForSeries( if (!User.AllowedSeries(series)) return new ListResult(); - IEnumerable enumerable = RepoFactory.AnimeEpisode.GetWithMultipleReleases(ignoreVariations, series.AniDB_ID); + var enumerable = RepoFactory.AnimeEpisode.GetWithMultipleReleases(ignoreVariations, series.AniDB_ID); return enumerable .ToListResult(episode => new Episode(HttpContext, episode, includeDataFrom, includeFiles, includeMediaInfo, includeAbsolutePaths, includeXRefs), page, pageSize); @@ -110,7 +110,7 @@ public ActionResult> GetEpisodes( [FromQuery, Range(0, 1000)] int pageSize = 100, [FromQuery, Range(1, int.MaxValue)] int page = 1) { - IEnumerable enumerable = RepoFactory.AnimeEpisode.GetWithMultipleReleases(ignoreVariations); + var enumerable = RepoFactory.AnimeEpisode.GetWithMultipleReleases(ignoreVariations); return enumerable .ToListResult(episode => new Episode(HttpContext, episode, includeDataFrom, includeFiles, includeMediaInfo, includeAbsolutePaths, includeXRefs), page, pageSize); @@ -136,7 +136,7 @@ public ActionResult> GetFileIdsWithPreference( if (!User.AllowedSeries(series)) return new List(); - IEnumerable enumerable = RepoFactory.AnimeEpisode.GetWithMultipleReleases(ignoreVariations, series.AniDB_ID); + var enumerable = RepoFactory.AnimeEpisode.GetWithMultipleReleases(ignoreVariations, series.AniDB_ID); return enumerable .SelectMany(episode => @@ -163,7 +163,7 @@ public ActionResult> GetFileIdsWithPreference( [FromQuery] bool ignoreVariations = true ) { - IEnumerable enumerable = RepoFactory.AnimeEpisode.GetWithMultipleReleases(ignoreVariations); + var enumerable = RepoFactory.AnimeEpisode.GetWithMultipleReleases(ignoreVariations); return enumerable .SelectMany(episode => diff --git a/Shoko.Server/API/v3/Controllers/ReleaseManagementDuplicateFilesController.cs b/Shoko.Server/API/v3/Controllers/ReleaseManagementDuplicateFilesController.cs index ceae9d1d6..a7a044a96 100644 --- a/Shoko.Server/API/v3/Controllers/ReleaseManagementDuplicateFilesController.cs +++ b/Shoko.Server/API/v3/Controllers/ReleaseManagementDuplicateFilesController.cs @@ -83,7 +83,7 @@ public ActionResult> GetFileIdsWithPreference() /// Page number. /// [HttpGet("Series")] - public ActionResult> GetSeriesWithDuplicateFiles( + public ActionResult> GetSeriesWithDuplicateFiles( [FromQuery, ModelBinder(typeof(CommaDelimitedModelBinder))] HashSet includeDataFrom = null, [FromQuery] bool onlyFinishedSeries = false, [FromQuery, Range(0, 1000)] int pageSize = 100, @@ -96,7 +96,7 @@ public ActionResult> GetFileIdsWithPreference() return enumerable .OrderBy(series => series.PreferredTitle) .ThenBy(series => series.AniDB_ID) - .ToListResult(series => new Series.WithDuplicateFilesResult(series, User.JMMUserID, includeDataFrom), page, pageSize); + .ToListResult(series => new Series.WithEpisodeCount(RepoFactory.AnimeEpisode.GetWithDuplicateFiles(series.AniDB_ID).Count(), series, User.JMMUserID, includeDataFrom), page, pageSize); } /// diff --git a/Shoko.Server/API/v3/Controllers/ReleaseManagementMissingEpisodesController.cs b/Shoko.Server/API/v3/Controllers/ReleaseManagementMissingEpisodesController.cs new file mode 100644 index 000000000..f6379f4cd --- /dev/null +++ b/Shoko.Server/API/v3/Controllers/ReleaseManagementMissingEpisodesController.cs @@ -0,0 +1,115 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using Microsoft.AspNetCore.Mvc; +using Shoko.Commons.Extensions; +using Shoko.Server.API.Annotations; +using Shoko.Server.API.ModelBinders; +using Shoko.Server.API.v3.Helpers; +using Shoko.Server.API.v3.Models.Common; +using Shoko.Server.API.v3.Models.Shoko; +using Shoko.Server.Repositories; +using Shoko.Server.Settings; + +#pragma warning disable CA1822 +namespace Shoko.Server.API.v3.Controllers; + +[ApiController] +[Route("/api/v{version:apiVersion}/ReleaseManagement/MissingEpisodes")] +[ApiV3] +public class ReleaseManagementMissingEpisodesController(ISettingsProvider settingsProvider) : BaseController(settingsProvider) +{ + /// + /// Get missing episodes, be it collecting or otherwise. + /// + /// Include data from selected s. + /// Include files with the episodes. + /// Include media info data. + /// Include absolute paths for the file locations. + /// Include file/episode cross-references with the episodes. + /// Only show missing episodes from release groups we're collecting. + /// Limits the number of results per page. Set to 0 to disable the limit. + /// Page number. + /// + [HttpGet("Episodes")] + public ActionResult> GetEpisodes( + [FromQuery, ModelBinder(typeof(CommaDelimitedModelBinder))] HashSet includeDataFrom = null, + [FromQuery] bool includeFiles = true, + [FromQuery] bool includeMediaInfo = true, + [FromQuery] bool includeAbsolutePaths = false, + [FromQuery] bool includeXRefs = false, + [FromQuery] bool collecting = false, + [FromQuery, Range(0, 1000)] int pageSize = 100, + [FromQuery, Range(1, int.MaxValue)] int page = 1) + { + var enumerable = RepoFactory.AnimeEpisode.GetMissing(collecting); + + return enumerable + .ToListResult(episode => new Episode(HttpContext, episode, includeDataFrom, includeFiles, includeMediaInfo, includeAbsolutePaths, includeXRefs), page, pageSize); + } + + /// + /// Get series with missing episodes, collecting or otherwise. + /// + /// Include data from selected s. + /// Only show series with missing episodes from release groups we're collecting. + /// Only show finished series. + /// Limits the number of results per page. Set to 0 to disable the limit. + /// Page number. + /// + [HttpGet("Series")] + public ActionResult> GetSeriesWithMultipleReleases( + [FromQuery, ModelBinder(typeof(CommaDelimitedModelBinder))] HashSet includeDataFrom = null, + [FromQuery] bool collecting = false, + [FromQuery] bool onlyFinishedSeries = false, + [FromQuery, Range(0, 1000)] int pageSize = 100, + [FromQuery, Range(1, int.MaxValue)] int page = 1) + { + var enumerable = RepoFactory.AnimeSeries.GetWithMissingEpisodes(collecting); + if (onlyFinishedSeries) + enumerable = enumerable.Where(a => a.AniDB_Anime.GetFinishedAiring()); + + return enumerable + .OrderBy(series => series.PreferredTitle) + .ThenBy(series => series.AniDB_ID) + .ToListResult(series => new Series.WithEpisodeCount(collecting ? series.MissingEpisodeCountGroups : series.MissingEpisodeCount, series, User.JMMUserID, includeDataFrom), page, pageSize); + } + + /// + /// Get missing episodes, be it collecting or otherwise, for a specific series. + /// + /// Shoko Series ID + /// Include data from selected s. + /// Include files with the episodes. + /// Include media info data. + /// Include absolute paths for the file locations. + /// Include file/episode cross-references with the episodes. + /// Only show missing episodes from release groups we're collecting. + /// Limits the number of results per page. Set to 0 to disable the limit. + /// Page number. + /// + [HttpGet("Series/{seriesID}/Episodes")] + public ActionResult> GetEpisodesForSeries( + [FromRoute, Range(1, int.MaxValue)] int seriesID, + [FromQuery, ModelBinder(typeof(CommaDelimitedModelBinder))] HashSet includeDataFrom = null, + [FromQuery] bool includeFiles = true, + [FromQuery] bool includeMediaInfo = true, + [FromQuery] bool includeAbsolutePaths = false, + [FromQuery] bool includeXRefs = false, + [FromQuery] bool collecting = false, + [FromQuery, Range(0, 1000)] int pageSize = 100, + [FromQuery, Range(1, int.MaxValue)] int page = 1) + { + var series = RepoFactory.AnimeSeries.GetByID(seriesID); + if (series == null) + return new ListResult(); + + if (!User.AllowedSeries(series)) + return new ListResult(); + + var enumerable = RepoFactory.AnimeEpisode.GetMissing(collecting, series.AniDB_ID); + + return enumerable + .ToListResult(episode => new Episode(HttpContext, episode, includeDataFrom, includeFiles, includeMediaInfo, includeAbsolutePaths, includeXRefs), page, pageSize); + } +} diff --git a/Shoko.Server/API/v3/Controllers/ReleaseManagementMultipleReleasesController.cs b/Shoko.Server/API/v3/Controllers/ReleaseManagementMultipleReleasesController.cs index 3b65e6fc2..7da0cce5c 100644 --- a/Shoko.Server/API/v3/Controllers/ReleaseManagementMultipleReleasesController.cs +++ b/Shoko.Server/API/v3/Controllers/ReleaseManagementMultipleReleasesController.cs @@ -44,7 +44,7 @@ public ActionResult> GetEpisodes( [FromQuery, Range(0, 1000)] int pageSize = 100, [FromQuery, Range(1, int.MaxValue)] int page = 1) { - IEnumerable enumerable = RepoFactory.AnimeEpisode.GetWithMultipleReleases(ignoreVariations); + var enumerable = RepoFactory.AnimeEpisode.GetWithMultipleReleases(ignoreVariations); return enumerable .ToListResult(episode => new Episode(HttpContext, episode, includeDataFrom, includeFiles, includeMediaInfo, includeAbsolutePaths, includeXRefs), page, pageSize); @@ -61,7 +61,7 @@ public ActionResult> GetFileIdsWithPreference( [FromQuery] bool ignoreVariations = true ) { - IEnumerable enumerable = RepoFactory.AnimeEpisode.GetWithMultipleReleases(ignoreVariations); + var enumerable = RepoFactory.AnimeEpisode.GetWithMultipleReleases(ignoreVariations); return enumerable .SelectMany(episode => @@ -87,21 +87,21 @@ public ActionResult> GetFileIdsWithPreference( /// Page number. /// [HttpGet("Series")] - public ActionResult> GetSeriesWithMultipleReleases( + public ActionResult> GetSeriesWithMultipleReleases( [FromQuery, ModelBinder(typeof(CommaDelimitedModelBinder))] HashSet includeDataFrom = null, [FromQuery] bool ignoreVariations = true, [FromQuery] bool onlyFinishedSeries = false, [FromQuery, Range(0, 1000)] int pageSize = 100, [FromQuery, Range(1, int.MaxValue)] int page = 1) { - IEnumerable enumerable = RepoFactory.AnimeSeries.GetWithMultipleReleases(ignoreVariations); + var enumerable = RepoFactory.AnimeSeries.GetWithMultipleReleases(ignoreVariations); if (onlyFinishedSeries) enumerable = enumerable.Where(a => a.AniDB_Anime.GetFinishedAiring()); return enumerable .OrderBy(series => series.PreferredTitle) .ThenBy(series => series.AniDB_ID) - .ToListResult(series => new Series.WithMultipleReleasesResult(series, User.JMMUserID, includeDataFrom, ignoreVariations), page, pageSize); + .ToListResult(series => new Series.WithEpisodeCount(RepoFactory.AnimeEpisode.GetWithMultipleReleases(ignoreVariations, series.AniDB_ID).Count(), series, User.JMMUserID, includeDataFrom), page, pageSize); } /// @@ -136,7 +136,7 @@ public ActionResult> GetEpisodesForSeries( if (!User.AllowedSeries(series)) return new ListResult(); - IEnumerable enumerable = RepoFactory.AnimeEpisode.GetWithMultipleReleases(ignoreVariations, series.AniDB_ID); + var enumerable = RepoFactory.AnimeEpisode.GetWithMultipleReleases(ignoreVariations, series.AniDB_ID); return enumerable .ToListResult(episode => new Episode(HttpContext, episode, includeDataFrom, includeFiles, includeMediaInfo, includeAbsolutePaths, includeXRefs), page, pageSize); @@ -162,7 +162,7 @@ public ActionResult> GetFileIdsWithPreference( if (!User.AllowedSeries(series)) return new List(); - IEnumerable enumerable = RepoFactory.AnimeEpisode.GetWithMultipleReleases(ignoreVariations, series.AniDB_ID); + var enumerable = RepoFactory.AnimeEpisode.GetWithMultipleReleases(ignoreVariations, series.AniDB_ID); return enumerable .SelectMany(episode => diff --git a/Shoko.Server/API/v3/Models/Shoko/Series.cs b/Shoko.Server/API/v3/Models/Shoko/Series.cs index 4d2097a4c..15106248d 100644 --- a/Shoko.Server/API/v3/Models/Shoko/Series.cs +++ b/Shoko.Server/API/v3/Models/Shoko/Series.cs @@ -838,34 +838,17 @@ public SearchResult(SeriesSearch.SearchResult result, int userI /// /// An extended model for use with the soft duplicate endpoint. /// - public class WithMultipleReleasesResult : Series + public class WithEpisodeCount : Series { /// /// Number of episodes in the series which have multiple releases. /// public int EpisodeCount { get; set; } - public WithMultipleReleasesResult(SVR_AnimeSeries ser, int userId = 0, HashSet? includeDataFrom = null, bool ignoreVariations = true) + public WithEpisodeCount(int episodeCount, SVR_AnimeSeries ser, int userId = 0, HashSet? includeDataFrom = null) : base(ser, userId, false, includeDataFrom) { - EpisodeCount = RepoFactory.AnimeEpisode.GetWithMultipleReleases(ignoreVariations, ser.AniDB_ID).Count; - } - } - - /// - /// An extended model for use with the hard duplicate endpoint. - /// - public class WithDuplicateFilesResult : Series - { - /// - /// Number of episodes in the series which have duplicate files. - /// - public int EpisodeCount { get; set; } - - public WithDuplicateFilesResult(SVR_AnimeSeries ser, int userId = 0, HashSet? includeDataFrom = null) - : base(ser, userId, false, includeDataFrom) - { - EpisodeCount = RepoFactory.AnimeEpisode.GetWithDuplicateFiles(ser.AniDB_ID).Count(); + EpisodeCount = episodeCount; } } } diff --git a/Shoko.Server/Repositories/Cached/AnimeEpisodeRepository.cs b/Shoko.Server/Repositories/Cached/AnimeEpisodeRepository.cs index 8807f5936..98739ef18 100644 --- a/Shoko.Server/Repositories/Cached/AnimeEpisodeRepository.cs +++ b/Shoko.Server/Repositories/Cached/AnimeEpisodeRepository.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -99,7 +99,7 @@ public List GetByHash(string hash) private const string MultipleReleasesCountVariationsQuery = @"SELECT ani.EpisodeID FROM VideoLocal AS vl JOIN CrossRef_File_Episode ani ON vl.Hash = ani.Hash WHERE vl.Hash != '' GROUP BY ani.EpisodeID HAVING COUNT(ani.EpisodeID) > 1"; - public List GetWithMultipleReleases(bool ignoreVariations, int? animeID = null) + public IEnumerable GetWithMultipleReleases(bool ignoreVariations, int? animeID = null) { var ids = Lock(() => { @@ -126,8 +126,7 @@ public List GetWithMultipleReleases(bool ignoreVariations, int .OrderBy(tuple => tuple.anidbEpisode!.AnimeID) .ThenBy(tuple => tuple.anidbEpisode!.EpisodeTypeEnum) .ThenBy(tuple => tuple.anidbEpisode!.EpisodeNumber) - .Select(tuple => tuple.episode!) - .ToList(); + .Select(tuple => tuple.episode!); } private const string DuplicateFilesWithAnimeQuery = @" @@ -217,6 +216,78 @@ public IEnumerable GetWithDuplicateFiles(int? animeID = null) .Select(tuple => tuple.episode!); } + public IEnumerable GetMissing(bool collecting, int? animeID = null) + { + // NOTE: For comments about this code, see the AnimeSeriesService. + var allSeries = animeID.HasValue + ? new List([RepoFactory.AnimeSeries.GetByAnimeID(animeID.Value)]).WhereNotNull() + : RepoFactory.AnimeSeries.GetWithMissingEpisodes(collecting); + foreach (var series in allSeries) + { + var animeType = (AnimeType)series.AniDB_Anime!.AnimeType; + var episodeReleasedList = new AnimeSeriesService.EpisodeList(animeType); + var episodeReleasedGroupList = new AnimeSeriesService.EpisodeList(animeType); + var animeGroupStatuses = RepoFactory.AniDB_GroupStatus.GetByAnimeID(series.AniDB_ID); + var allEpisodes = series.AllAnimeEpisodes + .Select(episode => (episode, anidbEpisode: episode.AniDB_Episode!, videos: episode.VideoLocals)) + .Where(tuple => tuple.anidbEpisode is not null) + .ToList(); + var localReleaseGroups = allEpisodes + .Where(tuple => tuple.anidbEpisode.EpisodeTypeEnum == EpisodeType.Episode) + .SelectMany(a => + { + var videos = a.videos; + if (videos.Count is 0) + return []; + + var aniFiles = videos + .Select(b => b.AniDBFile) + .WhereNotNull() + .ToList(); + if (aniFiles.Count is 0) + return []; + + return aniFiles + .Select(b => b.GroupID); + }) + .ToHashSet(); + foreach (var (episode, anidbEpisode, videos) in allEpisodes) + { + if (anidbEpisode.EpisodeTypeEnum is not EpisodeType.Episode || videos.Count is not 0 || !anidbEpisode.HasAired) + continue; + + if (animeGroupStatuses.Count is 0) + { + episodeReleasedList.Add(episode, videos.Count is not 0); + continue; + } + + var filteredGroups = animeGroupStatuses + .Where(status => + status.CompletionState is (int)Group_CompletionStatus.Complete or (int)Group_CompletionStatus.Finished || + status.HasGroupReleasedEpisode(anidbEpisode.EpisodeNumber) + ) + .ToList(); + if (filteredGroups.Count is 0) + continue; + + episodeReleasedList.Add(episode, videos.Count is not 0); + if (filteredGroups.Any(a => localReleaseGroups.Contains(a.GroupID))) + episodeReleasedGroupList.Add(episode, videos.Count is not 0); + } + + foreach (var episodeStats in collecting ? episodeReleasedGroupList : episodeReleasedList) + { + if (episodeStats.Available) + continue; + + foreach (var episodeStat in episodeStats) + if (!episodeStat.Episode.IsHidden) + yield return episodeStat.Episode; + } + } + } + public IReadOnlyList GetAllWatchedEpisodes(int userid, DateTime? after_date) => RepoFactory.AnimeEpisode_User.GetByUserID(userid) .Where(a => a.IsWatched() && a.WatchedDate > after_date).OrderBy(a => a.WatchedDate) diff --git a/Shoko.Server/Repositories/Cached/AnimeSeriesRepository.cs b/Shoko.Server/Repositories/Cached/AnimeSeriesRepository.cs index 9a0076563..2cca0bcd6 100644 --- a/Shoko.Server/Repositories/Cached/AnimeSeriesRepository.cs +++ b/Shoko.Server/Repositories/Cached/AnimeSeriesRepository.cs @@ -324,7 +324,7 @@ public List GetMostRecentlyAdded(int maxResults, int userID) private const string MultipleReleasesCountVariationsQuery = @"SELECT DISTINCT ani.AnimeID FROM VideoLocal AS vl JOIN CrossRef_File_Episode ani ON vl.Hash = ani.Hash WHERE vl.Hash != '' GROUP BY ani.AnimeID, ani.EpisodeID HAVING COUNT(ani.EpisodeID) > 1"; - public List GetWithMultipleReleases(bool ignoreVariations) + public IEnumerable GetWithMultipleReleases(bool ignoreVariations) { var ids = Lock(() => { @@ -339,8 +339,7 @@ public List GetWithMultipleReleases(bool ignoreVariations) return ids .Distinct() .Select(GetByAnimeID) - .WhereNotNull() - .ToList(); + .WhereNotNull(); } private const string DuplicateFilesQuery = @" @@ -389,6 +388,28 @@ public IEnumerable GetWithDuplicateFiles() .WhereNotNull(); } + public const string MissingEpisodesCollectingQuery = @"SELECT ser.AniDB_ID FROM AnimeSeries AS ser WHERE ser.MissingEpisodeCountGroups > 0"; + + public const string MissingEpisodesQuery = @"SELECT ser.AniDB_ID FROM AnimeSeries AS ser WHERE ser.MissingEpisodeCount > 0"; + + public IEnumerable GetWithMissingEpisodes(bool collecting) + { + var ids = Lock(() => + { + using var session = _databaseFactory.SessionFactory.OpenSession(); + + var query = collecting ? MissingEpisodesCollectingQuery : MissingEpisodesQuery; + return session.CreateSQLQuery(query) + .AddScalar("AniDB_ID", NHibernateUtil.Int32) + .List(); + }); + + return ids + .Distinct() + .Select(GetByAnimeID) + .WhereNotNull(); + } + public ImageEntityType[] GetAllImageTypes() => [ImageEntityType.Backdrop, ImageEntityType.Banner, ImageEntityType.Logo, ImageEntityType.Poster]; diff --git a/Shoko.Server/Services/AnimeSeriesService.cs b/Shoko.Server/Services/AnimeSeriesService.cs index c1265efa1..66c3f0679 100644 --- a/Shoko.Server/Services/AnimeSeriesService.cs +++ b/Shoko.Server/Services/AnimeSeriesService.cs @@ -1080,7 +1080,6 @@ public EpisodeList(AnimeType ept) public void Add(SVR_AnimeEpisode ep, bool available) { - var hidden = ep.IsHidden; if (AnimeType == AnimeType.OVA || AnimeType == AnimeType.Movie) { var ename = ep.PreferredTitle; @@ -1091,7 +1090,7 @@ public void Add(SVR_AnimeEpisode ep, bool available) m = partmatch.Match(ename); } - var s = new StatEpisodes.StatEpisode { Available = available, Hidden = hidden }; + var s = new StatEpisodes.StatEpisode { Available = available, Episode = ep }; if (m?.Success ?? false) { int.TryParse(m.Groups[1].Value, out var _); @@ -1154,7 +1153,7 @@ public void Add(SVR_AnimeEpisode ep, bool available) EpisodeType = StatEpisodes.StatEpisode.EpType.Complete, PartCount = 0, Available = available, - Hidden = hidden, + Episode = ep, }; eps.Add(es); Add(eps); @@ -1175,7 +1174,7 @@ public enum EpType public int PartCount; public EpType EpisodeType { get; set; } public bool Available { get; set; } - public bool Hidden { get; set; } + public SVR_AnimeEpisode Episode { get; set; } } public bool Available @@ -1206,7 +1205,7 @@ public bool Available } public bool Hidden - => this.Any(e => e.Hidden); + => this.Any(e => e.Episode.IsHidden); } } }