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 @@ public interface IRssItemProcessingProvider { //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 @@ public RssItemProcessingProvider(ISeriesProvider seriesProvider, ISeasonProvider #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 @@ public bool DownloadIfWanted(NzbInfoModel nzb, Indexer indexer) if (nzb.IsPassworded()) { Logger.Debug("Skipping Passworded Report {0}", nzb.Title); - return false; + return; } var episodeParseResults = Parser.ParseEpisodeInfo(nzb.Title); @@ -56,24 +56,26 @@ public bool DownloadIfWanted(NzbInfoModel nzb, Indexer indexer) 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 @@ private void ProcessStandardItem(NzbInfoModel nzb, Indexer indexer, List 1) { if (_sabProvider.IsInQueue(nzb.TitleFix)) @@ -199,18 +181,140 @@ private void ProcessStandardItem(NzbInfoModel nzb, Indexer indexer, List 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 @@ private void AddToHistory(List episodeParseResults, Series s _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 @@ public bool AddById(string id, string title) 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 @@ public Season GetSeason(int seasonId) 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 @@ public bool IsIgnored(int seriesId, int seasonNumber) if (season == null) return true; - return season.Monitored; + return !season.Monitored; } public void DeleteSeason(int seasonId)