diff --git a/NzbDrone.Core/Model/SeasonModel.cs b/NzbDrone.Core/Model/SeasonModel.cs new file mode 100644 index 000000000..c23be62cf --- /dev/null +++ b/NzbDrone.Core/Model/SeasonModel.cs @@ -0,0 +1,15 @@ +using NzbDrone.Core.Repository.Quality; +using SubSonic.SqlGeneration.Schema; + +namespace NzbDrone.Core.Model +{ + public class SeasonModel + { + public string SeriesTitle { get; set; } + public int SeriesId { get; set; } + public int SeasonNumber { get; set; } + public QualityTypes Quality { get; set; } + public long Size { get; set; } + public bool Proper { get; set; } + } +} \ No newline at end of file diff --git a/NzbDrone.Core/Model/SeasonParseResult.cs b/NzbDrone.Core/Model/SeasonParseResult.cs new file mode 100644 index 000000000..4f45ce305 --- /dev/null +++ b/NzbDrone.Core/Model/SeasonParseResult.cs @@ -0,0 +1,18 @@ +using NzbDrone.Core.Repository.Quality; +using SubSonic.SqlGeneration.Schema; + +namespace NzbDrone.Core.Model +{ + public class SeasonParseResult + { + internal string SeriesTitle { get; set; } + internal int SeasonNumber { get; set; } + internal int Year { get; set; } + + public override string ToString() + { + return string.Format("Series:{0} Season:{1}", SeriesTitle, SeasonNumber); + } + + } +} \ No newline at end of file diff --git a/NzbDrone.Core/NzbDrone.Core.csproj b/NzbDrone.Core/NzbDrone.Core.csproj index 0a70748e9..f91a198ed 100644 --- a/NzbDrone.Core/NzbDrone.Core.csproj +++ b/NzbDrone.Core/NzbDrone.Core.csproj @@ -166,6 +166,8 @@ + + diff --git a/NzbDrone.Core/Providers/IRssItemProcessingProvider.cs b/NzbDrone.Core/Providers/IRssItemProcessingProvider.cs index fa536ddae..32911e591 100644 --- a/NzbDrone.Core/Providers/IRssItemProcessingProvider.cs +++ b/NzbDrone.Core/Providers/IRssItemProcessingProvider.cs @@ -11,7 +11,7 @@ namespace NzbDrone.Core.Providers { //This interface will contain methods to process individual RSS Feed Items (Queue if wanted) - bool DownloadIfWanted(NzbInfoModel nzb, Indexer indexer); + void DownloadIfWanted(NzbInfoModel nzb, Indexer indexer); string GetTitleFix(List episodes, int seriesId); } } diff --git a/NzbDrone.Core/Providers/ISeasonProvider.cs b/NzbDrone.Core/Providers/ISeasonProvider.cs index 20b8ffcb9..4e4b27cb7 100644 --- a/NzbDrone.Core/Providers/ISeasonProvider.cs +++ b/NzbDrone.Core/Providers/ISeasonProvider.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using NzbDrone.Core.Model; using NzbDrone.Core.Repository; namespace NzbDrone.Core.Providers @@ -6,6 +7,7 @@ namespace NzbDrone.Core.Providers public interface ISeasonProvider { Season GetSeason(int seasonId); + Season GetSeason(int seriesId, int seasonNumber); List GetSeasons(int seriesId); Season GetLatestSeason(int seriesId); void EnsureSeason(int seriesId, int seasonId, int seasonNumber); diff --git a/NzbDrone.Core/Providers/RssItemProcessingProvider.cs b/NzbDrone.Core/Providers/RssItemProcessingProvider.cs index e3e9008c6..6d0c3db0b 100644 --- a/NzbDrone.Core/Providers/RssItemProcessingProvider.cs +++ b/NzbDrone.Core/Providers/RssItemProcessingProvider.cs @@ -40,7 +40,7 @@ namespace NzbDrone.Core.Providers #region IRssItemProcessingProvider Members - public bool DownloadIfWanted(NzbInfoModel nzb, Indexer indexer) + public void DownloadIfWanted(NzbInfoModel nzb, Indexer indexer) { //Do we want this item? try @@ -48,7 +48,7 @@ namespace NzbDrone.Core.Providers if (nzb.IsPassworded()) { Logger.Debug("Skipping Passworded Report {0}", nzb.Title); - return false; + return; } var episodeParseResults = Parser.ParseEpisodeInfo(nzb.Title); @@ -56,24 +56,26 @@ namespace NzbDrone.Core.Providers if (episodeParseResults.Count() > 0) { ProcessStandardItem(nzb, indexer, episodeParseResults); - return false; + return; } - //Todo: Try to handle Season X style naming + //Handles Full Season NZBs + var seasonParsedResult = Parser.ParseSeasonInfo(nzb.Title); - if (episodeParseResults.Count() < 1) + if (seasonParsedResult != null) { - Logger.Debug("Unsupported Title: {0}", nzb.Title); - return false; + //ProcessFullSeasonItem + return; } + + Logger.Debug("Unsupported Title: {0}", nzb.Title); } catch (Exception ex) { Logger.Error("Unsupported Title: {0}", nzb.Title); - Logger.ErrorException("Error Parsing NZB: " + ex.Message, ex); + Logger.ErrorException("Error Parsing/Processing NZB: " + ex.Message, ex); } - return false; } public string GetTitleFix(List episodes, int seriesId) @@ -157,39 +159,19 @@ namespace NzbDrone.Core.Providers return; } - //If their is more than one episode in this NZB check to see if it has been added as a single NZB - - //Do we want to download the NZB Directly or Send to SABnzbd? - nzb.TitleFix = GetTitleFix(episodeParseResults, series.SeriesId); //Get the TitleFix so we can use it later nzb.TitleFix = String.Format("{0} [{1}]", nzb.TitleFix, nzb.Quality); //Add Quality to the titleFix if (Convert.ToBoolean(_configProvider.GetValue("UseBlackHole", true, true))) { - var path = _configProvider.GetValue("BlackholeDirectory", String.Empty, true); - - if (String.IsNullOrEmpty(path)) - { - //Use the NZBDrone root Directory + /NZBs - //path = CentralDispatch.StartupPath + "NZBs"; - path = @"C:\Test\NZBs"; - } - - if (_diskProvider.FolderExists(path)) - { - var filename = path + Path.DirectorySeparatorChar + nzb.TitleFix + ".nzb"; - - if (_httpProvider.DownloadFile(nzb.Link.ToString(), filename)) - AddToHistory(episodeParseResults, series, nzb, indexer); - } - - else - Logger.Error("Blackhole Directory doesn't exist, not saving NZB: '{0}'", path); + if (DownloadNzb(nzb)) + AddToHistory(episodeParseResults, series, nzb, indexer); } //Send it to SABnzbd else { + //Only need to do this check if it contains more than one episode (because we already checked individually before) if (episodeParseResults.Count > 1) { if (_sabProvider.IsInQueue(nzb.TitleFix)) @@ -199,18 +181,140 @@ namespace NzbDrone.Core.Providers if (indexer.IndexerName != "Newzbin") { if (AddByUrl(nzb)) - { AddToHistory(episodeParseResults, series, nzb, indexer); - } - } - + else { - //AddById(episodeParseResults, series, nzb, indexer); + //Send to SAB using Newzbin ID } } - + } + + private void ProcessFullSeasonItem(NzbInfoModel nzb, Indexer indexer, SeasonParseResult seasonParseResult) + { + //Will try to match via NormalizeTitle, if that fails it will look for a scene name and do a lookup for your shows + var series = _seriesProvider.FindSeries(seasonParseResult.SeriesTitle); + + if (series == null) + { + //If we weren't able to find a title using the clean name, lets try again looking for a scene name + + var sceneId = SceneNameHelper.FindByName(seasonParseResult.SeriesTitle); + + if (sceneId != 0) + series = _seriesProvider.GetSeries(sceneId); + + if (series == null) + { + Logger.Debug("Show is not being watched: {0}", seasonParseResult.SeriesTitle); + return; + } + } + + Logger.Debug("Show is being watched: {0}", series.Title); + + nzb.Proper = Parser.ParseProper(nzb.Title); + nzb.Quality = Parser.ParseQuality(nzb.Title); + + if (!_seriesProvider.QualityWanted(series.SeriesId, nzb.Quality)) + { + Logger.Info("Quality [{0}] is not wanted for: {1}", nzb.Quality, series.Title); + return; + } + + var season = _seasonProvider.GetSeason(series.SeriesId, seasonParseResult.SeasonNumber); + + if (season == null) + return; + + if (_seasonProvider.IsIgnored(season.SeriesId)) + return; + + //Check to see if this is an upgrade for all our files + + var episodesWithoutFiles = season.Episodes.Where(e => e.EpisodeFileId == 0); + + var downloadWholeSeason = false; + + if (season.Episodes.Count() == episodesWithoutFiles.Count()) + { + //We don't have any episodes for this season, so as it stands right now we need the entire NZB + //Download! + downloadWholeSeason = true; + } + + else + { + var episodesNeeded = season.Episodes.Count; + + foreach (var episode in season.Episodes) + { + var episodeModel = new EpisodeModel(); + episodeModel.Proper = nzb.Proper; + episodeModel.SeriesId = series.SeriesId; + episodeModel.SeriesTitle = series.Title; + episodeModel.Quality = nzb.Quality; + episodeModel.SeasonNumber = episode.SeasonNumber; + episodeModel.EpisodeNumber = episode.EpisodeNumber; + + if (!_episodeProvider.IsNeeded(episodeModel)) + { + downloadWholeSeason = false; + episodesNeeded--; //Decrement the number of downloads we need, used if we want to replace all existing episodes if this will upgrade over X% of files + break; //We only want to download this NZB if ALL episodes can be upgraded by this Season NZB + } + } + } + + if (downloadWholeSeason) + { + //Do the final check to ensure we should download this NZB + + if (Convert.ToBoolean(_configProvider.GetValue("UseBlackHole", true, true))) + { + if (DownloadNzb(nzb)) + { + var episodeParseResults = new List(); + episodeParseResults.AddRange( + season.Episodes.Select( + e => + new EpisodeParseResult {EpisodeNumber = e.EpisodeNumber, SeasonNumber = e.SeasonNumber})); + + AddToHistory(episodeParseResults, series, nzb, indexer); + } + } + + //Send it to SABnzbd + else + { + if (_sabProvider.IsInQueue(nzb.TitleFix)) + return; + + if (indexer.IndexerName != "Newzbin") + { + if (AddByUrl(nzb)) + { + var episodeParseResults = new List(); + episodeParseResults.AddRange( + season.Episodes.Select( + e => + new EpisodeParseResult { EpisodeNumber = e.EpisodeNumber, SeasonNumber = e.SeasonNumber })); + + AddToHistory(episodeParseResults, series, nzb, indexer); + } + + } + + else + { + //Send to SAB using Newzbin ID + } + } + } + + //Possibly grab the whole season if a certain % of the season is missing, rather than for 1 or 2 episodes + throw new NotImplementedException("NzbDrone is currently not able to handle downloadinga whole season when less than a whole season it missing"); } private bool AddByUrl(NzbInfoModel nzb) @@ -246,5 +350,28 @@ namespace NzbDrone.Core.Providers _historyProvider.Insert(history); } } + + private bool DownloadNzb(NzbInfoModel nzb) + { + var path = _configProvider.GetValue("BlackholeDirectory", String.Empty, true); + + if (String.IsNullOrEmpty(path)) + { + //Use the NZBDrone root Directory + /NZBs + //path = CentralDispatch.StartupPath + "NZBs"; + path = @"C:\Test\NZBs"; + } + + if (_diskProvider.FolderExists(path)) + { + var filename = path + Path.DirectorySeparatorChar + nzb.TitleFix + ".nzb"; + + if (_httpProvider.DownloadFile(nzb.Link.ToString(), filename)) + return true; + } + + Logger.Error("Blackhole Directory doesn't exist, not saving NZB: '{0}'", path); + return false; + } } } diff --git a/NzbDrone.Core/Providers/SabProvider.cs b/NzbDrone.Core/Providers/SabProvider.cs index 1b60d60f3..9d49a76f8 100644 --- a/NzbDrone.Core/Providers/SabProvider.cs +++ b/NzbDrone.Core/Providers/SabProvider.cs @@ -92,8 +92,6 @@ namespace NzbDrone.Core.Providers return true; return false; - - throw new NotImplementedException(); } #endregion diff --git a/NzbDrone.Core/Providers/SeasonProvider.cs b/NzbDrone.Core/Providers/SeasonProvider.cs index 21eaa2a8c..eea1a5370 100644 --- a/NzbDrone.Core/Providers/SeasonProvider.cs +++ b/NzbDrone.Core/Providers/SeasonProvider.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using NLog; +using NzbDrone.Core.Model; using NzbDrone.Core.Repository; using SubSonic.Repository; using System.Linq; @@ -10,11 +11,14 @@ namespace NzbDrone.Core.Providers class SeasonProvider : ISeasonProvider { private readonly IRepository _sonicRepo; + private readonly ISeriesProvider _seriesProvider; + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); - public SeasonProvider(IRepository dataRepository) + public SeasonProvider(IRepository dataRepository, ISeriesProvider seriesProvider) { _sonicRepo = dataRepository; + _seriesProvider = seriesProvider; } public Season GetSeason(int seasonId) @@ -22,6 +26,11 @@ namespace NzbDrone.Core.Providers return _sonicRepo.Single(seasonId); } + public Season GetSeason(int seriesId, int seasonNumber) + { + return _sonicRepo.Single(s => s.SeriesId == seriesId && s.SeasonNumber == seasonNumber); + } + public List GetSeasons(int seriesId) { return _sonicRepo.All().Where(s => s.SeriesId == seriesId).ToList(); @@ -70,7 +79,7 @@ namespace NzbDrone.Core.Providers if (season == null) return true; - return season.Monitored; + return !season.Monitored; } public void DeleteSeason(int seasonId)