using System; using System.Collections.Generic; using System.Linq; using Ninject; using NLog; using NzbDrone.Core.Model; using NzbDrone.Core.Repository; using NzbDrone.Core.Repository.Quality; namespace NzbDrone.Core.Providers { public class InventoryProvider { private readonly SeriesProvider _seriesProvider; private readonly EpisodeProvider _episodeProvider; private readonly HistoryProvider _historyProvider; private readonly QualityTypeProvider _qualityTypeProvider; private readonly QualityProvider _qualityProvider; private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); [Inject] public InventoryProvider(SeriesProvider seriesProvider, EpisodeProvider episodeProvider, HistoryProvider historyProvider, QualityTypeProvider qualityTypeProvider, QualityProvider qualityProvider) { _seriesProvider = seriesProvider; _episodeProvider = episodeProvider; _historyProvider = historyProvider; _qualityTypeProvider = qualityTypeProvider; _qualityProvider = qualityProvider; } public InventoryProvider() { } public virtual bool IsMonitored(EpisodeParseResult parseResult) { var series = _seriesProvider.FindSeries(parseResult.CleanTitle); if (series == null) { Logger.Trace("{0} is not mapped to any series in DB. skipping", parseResult.CleanTitle); return false; } parseResult.Series = series; if (!series.Monitored) { Logger.Debug("{0} is present in the DB but not tracked. skipping.", parseResult.CleanTitle); return false; } var episodes = _episodeProvider.GetEpisodesByParseResult(parseResult, true); //return monitored if any of the episodes are monitored if (episodes.Any(episode => !episode.Ignored)) { return true; } Logger.Debug("All episodes are ignored. skipping."); return false; } /// /// Comprehensive check on whether or not this episode is needed. /// /// Episode that needs to be checked /// False unless called by a manual search job /// Whether or not the file quality meets the requirements /// for multi episode files, all episodes need to meet the requirement /// before the report is downloaded public virtual bool IsQualityNeeded(EpisodeParseResult parsedReport, bool skipHistory = false) { Logger.Trace("Checking if report meets quality requirements. {0}", parsedReport.Quality); if (!parsedReport.Series.QualityProfile.Allowed.Contains(parsedReport.Quality.QualityType)) { Logger.Trace("Quality {0} rejected by Series' quality profile", parsedReport.Quality); return false; } var cutoff = parsedReport.Series.QualityProfile.Cutoff; if (!IsAcceptableSize(parsedReport)) { Logger.Trace("Size: {0} is not acceptable for Quality: {1}", parsedReport.Size, parsedReport.Quality); return false; } foreach (var episode in _episodeProvider.GetEpisodesByParseResult(parsedReport, true)) { //Checking File var file = episode.EpisodeFile; if (file != null) { Logger.Trace("Comparing file quality with report. Existing file is {0} proper:{1}", file.Quality, file.Proper); if (!IsUpgrade(new Quality { QualityType = file.Quality, Proper = file.Proper }, parsedReport.Quality, cutoff)) return false; } //Checking History (If not a manual search) if (!skipHistory) { var bestQualityInHistory = _historyProvider.GetBestQualityInHistory(episode.EpisodeId); if(bestQualityInHistory != null) { Logger.Trace("Comparing history quality with report. History is {0}", bestQualityInHistory); if(!IsUpgrade(bestQualityInHistory, parsedReport.Quality, cutoff)) return false; } } } Logger.Debug("Episode {0} is needed", parsedReport); return true; //If we get to this point and the file has not yet been rejected then accept it } public static bool IsUpgrade(Quality currentQuality, Quality newQuality, QualityTypes cutOff) { if (currentQuality.QualityType >= cutOff) { if (newQuality.QualityType > currentQuality.QualityType || (newQuality.QualityType == currentQuality.QualityType && newQuality.Proper == currentQuality.Proper)) { Logger.Trace("Existing item meets cut-off. skipping."); return false; } } if (newQuality > currentQuality) return true; if (currentQuality > newQuality) { Logger.Trace("existing item has better quality. skipping"); return false; } if (currentQuality == newQuality && !newQuality.Proper) { Logger.Trace("Same quality, not proper skipping"); return false; } Logger.Debug("New item has better quality than existing item"); return true; } public virtual bool IsAcceptableSize(EpisodeParseResult parseResult) { var qualityType = _qualityTypeProvider.Get((int) parseResult.Quality.QualityType); //Need to determine if this is a 30 or 60 minute episode //Is it a multi-episode release? //Is it the first or last series of a season? //0 will be treated as unlimited if (qualityType.MaxSize == 0) return true; var maxSize = qualityType.MaxSize.Megabytes(); var series = parseResult.Series; //Multiply maxSize by Series.Runtime maxSize = maxSize * series.Runtime; //Multiply maxSize by the number of episodes parsed (if EpisodeNumbers is null it will be treated as a single episode) if (parseResult.EpisodeNumbers != null) maxSize = maxSize * parseResult.EpisodeNumbers.Count; //Check if there was only one episode parsed //and it is the first or last episode of the season if (parseResult.EpisodeNumbers != null && parseResult.EpisodeNumbers.Count == 1 && _episodeProvider.IsFirstOrLastEpisodeOfSeason(series.SeriesId, parseResult.SeasonNumber, parseResult.EpisodeNumbers[0])) { maxSize = maxSize * 2; } //If the parsed size is greater than maxSize we don't want it if (parseResult.Size > maxSize) return false; return true; } public virtual bool IsUpgradePossible(Episode episode) { //Used to check if the existing episode can be upgraded by searching (Before we search) if (episode.EpisodeFileId == 0) return true; var profile = _qualityProvider.Get(episode.Series.QualityProfileId); if (episode.EpisodeFile.Quality >= profile.Cutoff) return false; return true; } } }