From 20dec3c205a4831804955212c4a9d44ed48ef023 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Sat, 22 Feb 2014 19:19:05 -0800 Subject: [PATCH] Wanted is much much faster now. --- src/NzbDrone.Api/Wanted/CutoffModule.cs | 9 ++- src/NzbDrone.Core/NzbDrone.Core.csproj | 2 + .../Qualities/QualitiesBelowCutoff.cs | 17 +++++ src/NzbDrone.Core/Tv/EpisodeCutoffService.cs | 47 ++++++++++++ src/NzbDrone.Core/Tv/EpisodeRepository.cs | 71 ++++++++++++------- src/NzbDrone.Core/Tv/EpisodeService.cs | 29 -------- 6 files changed, 117 insertions(+), 58 deletions(-) create mode 100644 src/NzbDrone.Core/Qualities/QualitiesBelowCutoff.cs create mode 100644 src/NzbDrone.Core/Tv/EpisodeCutoffService.cs diff --git a/src/NzbDrone.Api/Wanted/CutoffModule.cs b/src/NzbDrone.Api/Wanted/CutoffModule.cs index 130feb308..ee0b9f219 100644 --- a/src/NzbDrone.Api/Wanted/CutoffModule.cs +++ b/src/NzbDrone.Api/Wanted/CutoffModule.cs @@ -3,19 +3,18 @@ using NzbDrone.Api.Episodes; using NzbDrone.Api.Extensions; using NzbDrone.Core.Datastore; using NzbDrone.Core.Tv; -using NzbDrone.Core.Qualities; namespace NzbDrone.Api.Wanted { public class CutoffModule : NzbDroneRestModule { - private readonly IEpisodeService _episodeService; + private readonly IEpisodeCutoffService _episodeCutoffService; private readonly SeriesRepository _seriesRepository; - public CutoffModule(IEpisodeService episodeService, SeriesRepository seriesRepository) + public CutoffModule(IEpisodeCutoffService episodeCutoffService, SeriesRepository seriesRepository) :base("wanted/cutoff") { - _episodeService = episodeService; + _episodeCutoffService = episodeCutoffService; _seriesRepository = seriesRepository; GetResourcePaged = GetCutoffUnmetEpisodes; } @@ -39,7 +38,7 @@ namespace NzbDrone.Api.Wanted pagingSpec.FilterExpression = v => v.Monitored == true && v.Series.Monitored == true; } - PagingResource resource = ApplyToPage(_episodeService.EpisodesWhereCutoffUnmet, pagingSpec); + PagingResource resource = ApplyToPage(_episodeCutoffService.EpisodesWhereCutoffUnmet, pagingSpec); resource.Records = resource.Records.LoadSubtype(e => e.SeriesId, _seriesRepository).ToList(); diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index 0c22a0648..e6194a412 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -512,6 +512,7 @@ + @@ -536,6 +537,7 @@ + diff --git a/src/NzbDrone.Core/Qualities/QualitiesBelowCutoff.cs b/src/NzbDrone.Core/Qualities/QualitiesBelowCutoff.cs new file mode 100644 index 000000000..7d1d2c498 --- /dev/null +++ b/src/NzbDrone.Core/Qualities/QualitiesBelowCutoff.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; + +namespace NzbDrone.Core.Qualities +{ + public class QualitiesBelowCutoff + { + public Int32 ProfileId { get; set; } + public IEnumerable QualityIds { get; set; } + + public QualitiesBelowCutoff(int profileId, IEnumerable qualityIds) + { + ProfileId = profileId; + QualityIds = qualityIds; + } + } +} diff --git a/src/NzbDrone.Core/Tv/EpisodeCutoffService.cs b/src/NzbDrone.Core/Tv/EpisodeCutoffService.cs new file mode 100644 index 000000000..f88dfc02e --- /dev/null +++ b/src/NzbDrone.Core/Tv/EpisodeCutoffService.cs @@ -0,0 +1,47 @@ +using System.Collections.Generic; +using System.Linq; +using NLog; +using NzbDrone.Core.Datastore; +using NzbDrone.Core.Qualities; + +namespace NzbDrone.Core.Tv +{ + public interface IEpisodeCutoffService + { + PagingSpec EpisodesWhereCutoffUnmet(PagingSpec pagingSpec); + } + + public class EpisodeCutoffService : IEpisodeCutoffService + { + private readonly IEpisodeRepository _episodeRepository; + private readonly IQualityProfileService _qualityProfileService; + private readonly Logger _logger; + + public EpisodeCutoffService(IEpisodeRepository episodeRepository, IQualityProfileService qualityProfileService, Logger logger) + { + _episodeRepository = episodeRepository; + _qualityProfileService = qualityProfileService; + _logger = logger; + } + + public PagingSpec EpisodesWhereCutoffUnmet(PagingSpec pagingSpec) + { + var qualitiesBelowCutoff = new List(); + var qualityProfiles = _qualityProfileService.All(); + + //Get all items less than the cutoff + foreach (var qualityProfile in qualityProfiles) + { + var cutoffIndex = qualityProfile.Items.FindIndex(v => v.Quality == qualityProfile.Cutoff); + var belowCutoff = qualityProfile.Items.Take(cutoffIndex).ToList(); + + if (belowCutoff.Any()) + { + qualitiesBelowCutoff.Add(new QualitiesBelowCutoff(qualityProfile.Id, belowCutoff.Select(i => i.Quality.Id))); + } + } + + return _episodeRepository.EpisodesWhereCutoffUnmet(pagingSpec, qualitiesBelowCutoff, false); + } + } +} diff --git a/src/NzbDrone.Core/Tv/EpisodeRepository.cs b/src/NzbDrone.Core/Tv/EpisodeRepository.cs index 32a9840da..d3d95fc8c 100644 --- a/src/NzbDrone.Core/Tv/EpisodeRepository.cs +++ b/src/NzbDrone.Core/Tv/EpisodeRepository.cs @@ -5,6 +5,7 @@ using Marr.Data.QGen; using NzbDrone.Core.Datastore; using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.MediaFiles; +using NzbDrone.Core.Qualities; namespace NzbDrone.Core.Tv { @@ -18,7 +19,7 @@ namespace NzbDrone.Core.Tv List GetEpisodes(int seriesId, int seasonNumber); List GetEpisodeByFileId(int fileId); PagingSpec EpisodesWithoutFiles(PagingSpec pagingSpec, bool includeSpecials); - List EpisodesWhereCutoffUnmet(PagingSpec pagingSpec, bool includeSpecials); + PagingSpec EpisodesWhereCutoffUnmet(PagingSpec pagingSpec, List qualitiesBelowCutoff, bool includeSpecials); Episode FindEpisodeBySceneNumbering(int seriesId, int seasonNumber, int episodeNumber); List EpisodesBetweenDates(DateTime startDate, DateTime endDate); void SetMonitoredFlat(Episode episode, bool monitored); @@ -98,7 +99,7 @@ namespace NzbDrone.Core.Tv return pagingSpec; } - public List EpisodesWhereCutoffUnmet(PagingSpec pagingSpec, bool includeSpecials) + public PagingSpec EpisodesWhereCutoffUnmet(PagingSpec pagingSpec, List qualitiesBelowCutoff, bool includeSpecials) { var currentTime = DateTime.UtcNow; var startingSeasonNumber = 1; @@ -108,29 +109,10 @@ namespace NzbDrone.Core.Tv startingSeasonNumber = 0; } - var query = Query.Join(JoinType.Inner, e => e.Series, (e, s) => e.SeriesId == s.Id) - .Join(JoinType.Left, e => e.EpisodeFile, (e, s) => e.EpisodeFileId == s.Id) - .Where(pagingSpec.FilterExpression) - .AndWhere(e => e.EpisodeFileId != 0) - .AndWhere(e => e.SeasonNumber >= startingSeasonNumber) - .AndWhere(e => e.AirDateUtc <= currentTime) - .OrderBy(pagingSpec.OrderByClause(), pagingSpec.ToSortDirection()); + pagingSpec.TotalRecords = EpisodesWhereCutoffUnmetQuery(pagingSpec, currentTime, qualitiesBelowCutoff, startingSeasonNumber).GetRowCount(); + pagingSpec.Records = EpisodesWhereCutoffUnmetQuery(pagingSpec, currentTime, qualitiesBelowCutoff, startingSeasonNumber).ToList(); - return query.ToList(); - } - - private SortBuilder GetMissingEpisodesQuery(PagingSpec pagingSpec, DateTime currentTime, int startingSeasonNumber) - { - var query = Query.Join(JoinType.Inner, e => e.Series, (e, s) => e.SeriesId == s.Id) - .Where(pagingSpec.FilterExpression) - .AndWhere(e => e.EpisodeFileId == 0) - .AndWhere(e => e.SeasonNumber >= startingSeasonNumber) - .AndWhere(e => e.AirDateUtc <= currentTime) - .OrderBy(pagingSpec.OrderByClause(), pagingSpec.ToSortDirection()) - .Skip(pagingSpec.PagingOffset()) - .Take(pagingSpec.PageSize); - - return query; + return pagingSpec; } public Episode FindEpisodeBySceneNumbering(int seriesId, int seasonNumber, int episodeNumber) @@ -177,5 +159,46 @@ namespace NzbDrone.Core.Tv { SetFields(new Episode { Id = episodeId, EpisodeFileId = fileId }, episode => episode.EpisodeFileId); } + + private SortBuilder GetMissingEpisodesQuery(PagingSpec pagingSpec, DateTime currentTime, int startingSeasonNumber) + { + return Query.Join(JoinType.Inner, e => e.Series, (e, s) => e.SeriesId == s.Id) + .Where(pagingSpec.FilterExpression) + .AndWhere(e => e.EpisodeFileId == 0) + .AndWhere(e => e.SeasonNumber >= startingSeasonNumber) + .AndWhere(e => e.AirDateUtc <= currentTime) + .OrderBy(pagingSpec.OrderByClause(), pagingSpec.ToSortDirection()) + .Skip(pagingSpec.PagingOffset()) + .Take(pagingSpec.PageSize); + } + + private SortBuilder EpisodesWhereCutoffUnmetQuery(PagingSpec pagingSpec, DateTime currentTime, List qualitiesBelowCutoff, int startingSeasonNumber) + { + return Query.Join(JoinType.Inner, e => e.Series, (e, s) => e.SeriesId == s.Id) + .Join(JoinType.Left, e => e.EpisodeFile, (e, s) => e.EpisodeFileId == s.Id) + .Where(pagingSpec.FilterExpression) + .AndWhere(e => e.EpisodeFileId != 0) + .AndWhere(e => e.SeasonNumber >= startingSeasonNumber) + .AndWhere(e => e.AirDateUtc <= currentTime) + .AndWhere(BuildQualityCutoffWhereClause(qualitiesBelowCutoff)) + .OrderBy(pagingSpec.OrderByClause(), pagingSpec.ToSortDirection()) + .Skip(pagingSpec.PagingOffset()) + .Take(pagingSpec.PageSize); + } + + private string BuildQualityCutoffWhereClause(List qualitiesBelowCutoff) + { + var clauses = new List(); + + foreach (var profile in qualitiesBelowCutoff) + { + foreach (var belowCutoff in profile.QualityIds) + { + clauses.Add(String.Format("([t1].[QualityProfileId] = {0} AND [t2].[Quality] LIKE '%_quality_:%{1}%')", profile.ProfileId, belowCutoff)); + } + } + + return String.Format("({0})", String.Join(" OR ", clauses)); + } } } diff --git a/src/NzbDrone.Core/Tv/EpisodeService.cs b/src/NzbDrone.Core/Tv/EpisodeService.cs index f974f5867..20a575884 100644 --- a/src/NzbDrone.Core/Tv/EpisodeService.cs +++ b/src/NzbDrone.Core/Tv/EpisodeService.cs @@ -22,7 +22,6 @@ namespace NzbDrone.Core.Tv List GetEpisodeBySeries(int seriesId); List GetEpisodesBySeason(int seriesId, int seasonNumber); PagingSpec EpisodesWithoutFiles(PagingSpec pagingSpec); - PagingSpec EpisodesWhereCutoffUnmet(PagingSpec pagingSpec); List GetEpisodesByFileId(int episodeFileId); void UpdateEpisode(Episode episode); void SetEpisodeMonitored(int episodeId, bool monitored); @@ -114,34 +113,6 @@ namespace NzbDrone.Core.Tv return episodeResult; } - public PagingSpec EpisodesWhereCutoffUnmet(PagingSpec pagingSpec) - { - var allSpec = new PagingSpec - { - SortKey = pagingSpec.SortKey, - SortDirection = pagingSpec.SortDirection, - FilterExpression = pagingSpec.FilterExpression - }; - - var allItems = _episodeRepository.EpisodesWhereCutoffUnmet(allSpec, false); - - var qualityProfileComparers = _qualityProfileRepository.All().ToDictionary(v => v.Id, v => new { Profile = v, Comparer = new QualityModelComparer(v) }); - - var filtered = allItems.Where(episode => - { - var profile = qualityProfileComparers[episode.Series.QualityProfileId]; - return profile.Comparer.Compare(episode.EpisodeFile.Value.Quality.Quality, profile.Profile.Cutoff) < 0; - }).ToList(); - - pagingSpec.Records = filtered - .Skip(pagingSpec.PagingOffset()) - .Take(pagingSpec.PageSize) - .ToList(); - pagingSpec.TotalRecords = filtered.Count; - - return pagingSpec; - } - public List GetEpisodesByFileId(int episodeFileId) { return _episodeRepository.GetEpisodeByFileId(episodeFileId);