private Series GetSeries(ParsedEpisodeInfo parsedEpisodeInfo, int tvdbId, int tvRageId, SearchCriteriaBase searchCriteria) { Series series = null; var sceneMappingTvdbId = _sceneMappingService.FindTvdbId(parsedEpisodeInfo.SeriesTitle, parsedEpisodeInfo.ReleaseTitle); if (sceneMappingTvdbId.HasValue) { if (searchCriteria != null && searchCriteria.Series.TvdbId == sceneMappingTvdbId.Value) { return(searchCriteria.Series); } series = _seriesService.FindByTvdbId(sceneMappingTvdbId.Value); if (series == null) { _logger.Debug("No matching series {0}", parsedEpisodeInfo.SeriesTitle); return(null); } return(series); } if (searchCriteria != null) { if (searchCriteria.Series.CleanTitle == parsedEpisodeInfo.SeriesTitle.CleanSeriesTitle()) { return(searchCriteria.Series); } if (tvdbId > 0 && tvdbId == searchCriteria.Series.TvdbId) { //TODO: If series is found by TvdbId, we should report it as a scene naming exception, since it will fail to import return(searchCriteria.Series); } if (tvRageId > 0 && tvRageId == searchCriteria.Series.TvRageId) { //TODO: If series is found by TvRageId, we should report it as a scene naming exception, since it will fail to import return(searchCriteria.Series); } } series = _seriesService.FindByTitle(parsedEpisodeInfo.SeriesTitle); if (series == null && parsedEpisodeInfo.SeriesTitleInfo.Year > 0) { series = _seriesService.FindByTitle(parsedEpisodeInfo.SeriesTitleInfo.TitleWithoutYear, parsedEpisodeInfo.SeriesTitleInfo.Year); } if (series == null && tvdbId > 0) { //TODO: If series is found by TvdbId, we should report it as a scene naming exception, since it will fail to import series = _seriesService.FindByTvdbId(tvdbId); } if (series == null && tvRageId > 0) { //TODO: If series is found by TvRageId, we should report it as a scene naming exception, since it will fail to import series = _seriesService.FindByTvRageId(tvRageId); } if (series == null) { _logger.Debug("No matching series {0}", parsedEpisodeInfo.SeriesTitle); return(null); } return(series); }
private bool TryGetMovieBySearchCriteria(ParsedMovieInfo parsedMovieInfo, SearchCriteriaBase searchCriteria, out MappingResult result) { Movie possibleMovie = null; List <string> possibleTitles = new List <string>(); possibleTitles.Add(searchCriteria.Movie.CleanTitle); foreach (AlternativeTitle altTitle in searchCriteria.Movie.AlternativeTitles) { possibleTitles.Add(altTitle.CleanTitle); } string cleanTitle = parsedMovieInfo.MovieTitle.CleanSeriesTitle(); foreach (string title in possibleTitles) { if (title == parsedMovieInfo.MovieTitle.CleanSeriesTitle()) { possibleMovie = searchCriteria.Movie; } foreach (ArabicRomanNumeral numeralMapping in _arabicRomanNumeralMappings) { string arabicNumeral = numeralMapping.ArabicNumeralAsString; string romanNumeral = numeralMapping.RomanNumeralLowerCase; //_logger.Debug(cleanTitle); if (title.Replace(arabicNumeral, romanNumeral) == parsedMovieInfo.MovieTitle.CleanSeriesTitle()) { possibleMovie = searchCriteria.Movie; } if (title == parsedMovieInfo.MovieTitle.CleanSeriesTitle().Replace(arabicNumeral, romanNumeral)) { possibleMovie = searchCriteria.Movie; } } } if (possibleMovie != null) { if (parsedMovieInfo.Year < 1800 || possibleMovie.Year == parsedMovieInfo.Year || possibleMovie.SecondaryYear == parsedMovieInfo.Year) { result = new MappingResult { Movie = possibleMovie, MappingResultType = MappingResultType.Success }; return(true); } result = new MappingResult { Movie = possibleMovie, MappingResultType = MappingResultType.WrongYear }; return(false); } if (_config.ParsingLeniency == ParsingLeniencyType.MappingLenient) { if (searchCriteria.Movie.CleanTitle.Contains(cleanTitle) || cleanTitle.Contains(searchCriteria.Movie.CleanTitle)) { possibleMovie = searchCriteria.Movie; if (parsedMovieInfo.Year > 1800 && parsedMovieInfo.Year == possibleMovie.Year || possibleMovie.SecondaryYear == parsedMovieInfo.Year) { result = new MappingResult { Movie = possibleMovie, MappingResultType = MappingResultType.SuccessLenientMapping }; return(true); } if (parsedMovieInfo.Year < 1800) { result = new MappingResult { Movie = possibleMovie, MappingResultType = MappingResultType.SuccessLenientMapping }; return(true); } result = new MappingResult { Movie = possibleMovie, MappingResultType = MappingResultType.WrongYear }; return(false); } } result = new MappingResult { Movie = searchCriteria.Movie, MappingResultType = MappingResultType.WrongTitle }; return(false); }
private List <Episode> GetStandardEpisodes(Series series, ParsedEpisodeInfo parsedEpisodeInfo, bool sceneSource, SearchCriteriaBase searchCriteria) { var result = new List <Episode>(); var seasonNumber = parsedEpisodeInfo.SeasonNumber; if (sceneSource) { var sceneMapping = _sceneMappingService.FindSceneMapping(parsedEpisodeInfo.SeriesTitle); if (sceneMapping != null && sceneMapping.SeasonNumber.HasValue && sceneMapping.SeasonNumber.Value >= 0 && sceneMapping.SceneSeasonNumber == seasonNumber) { seasonNumber = sceneMapping.SeasonNumber.Value; } } if (parsedEpisodeInfo.EpisodeNumbers == null) { return(new List <Episode>()); } foreach (var episodeNumber in parsedEpisodeInfo.EpisodeNumbers) { if (series.UseSceneNumbering && sceneSource) { List <Episode> episodes = new List <Episode>(); if (searchCriteria != null) { episodes = searchCriteria.Episodes.Where(e => e.SceneSeasonNumber == parsedEpisodeInfo.SeasonNumber && e.SceneEpisodeNumber == episodeNumber).ToList(); } if (!episodes.Any()) { episodes = _episodeService.FindEpisodesBySceneNumbering(series.Id, seasonNumber, episodeNumber); } if (episodes != null && episodes.Any()) { _logger.Debug("Using Scene to TVDB Mapping for: {0} - Scene: {1}x{2:00} - TVDB: {3}", series.Title, episodes.First().SceneSeasonNumber, episodes.First().SceneEpisodeNumber, string.Join(", ", episodes.Select(e => string.Format("{0}x{1:00}", e.SeasonNumber, e.EpisodeNumber)))); result.AddRange(episodes); continue; } } Episode episodeInfo = null; if (searchCriteria != null) { episodeInfo = searchCriteria.Episodes.SingleOrDefault(e => e.SeasonNumber == seasonNumber && e.EpisodeNumber == episodeNumber); } if (episodeInfo == null) { episodeInfo = _episodeService.FindEpisode(series.Id, seasonNumber, episodeNumber); } if (episodeInfo != null) { result.Add(episodeInfo); } else { _logger.Debug("Unable to find {0}", parsedEpisodeInfo); } } return(result); }
public RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, int tvdbId, int tvRageId, SearchCriteriaBase searchCriteria = null) { var remoteEpisode = new RemoteEpisode { ParsedEpisodeInfo = parsedEpisodeInfo, }; var series = GetSeries(parsedEpisodeInfo, tvdbId, tvRageId, searchCriteria); if (series == null) { return(remoteEpisode); } remoteEpisode.Series = series; remoteEpisode.Episodes = GetEpisodes(parsedEpisodeInfo, series, true, searchCriteria); return(remoteEpisode); }
public ParsedEpisodeInfo ParseSpecialEpisodeTitle(string title, int tvdbId, int tvRageId, SearchCriteriaBase searchCriteria = null) { if (searchCriteria != null) { if (tvdbId == 0) { tvdbId = _sceneMappingService.FindTvdbId(title) ?? 0; } if (tvdbId != 0 && tvdbId == searchCriteria.Series.TvdbId) { return(ParseSpecialEpisodeTitle(title, searchCriteria.Series)); } if (tvRageId != 0 && tvRageId == searchCriteria.Series.TvRageId) { return(ParseSpecialEpisodeTitle(title, searchCriteria.Series)); } } var series = GetSeries(title); if (series == null) { series = _seriesService.FindByTitleInexact(title); } if (series == null && tvdbId > 0) { series = _seriesService.FindByTvdbId(tvdbId); } if (series == null && tvRageId > 0) { series = _seriesService.FindByTvRageId(tvRageId); } if (series == null) { _logger.Debug("No matching series {0}", title); return(null); } return(ParseSpecialEpisodeTitle(title, series)); }
public Decision IsSatisfiedBy(RemoteAlbum subject, SearchCriteriaBase searchCriteria) { throw new NotImplementedException(); // TODO: Rework for Tracks if we can parse from release details. }
private void AddTvIdPageableRequests(IndexerPageableRequestChain chain, IEnumerable <int> categories, SearchCriteriaBase searchCriteria, string parameters) { var includeTvdbSearch = SupportsTvdbSearch && searchCriteria.Series.TvdbId > 0; var includeImdbSearch = SupportsImdbSearch && searchCriteria.Series.ImdbId.IsNotNullOrWhiteSpace(); var includeTvRageSearch = SupportsTvRageSearch && searchCriteria.Series.TvRageId > 0; var includeTvMazeSearch = SupportsTvMazeSearch && searchCriteria.Series.TvMazeId > 0; if (SupportsAggregatedIdSearch && (includeTvdbSearch || includeTvRageSearch || includeTvMazeSearch)) { var ids = ""; if (includeTvdbSearch) { ids += "&tvdbid=" + searchCriteria.Series.TvdbId; } if (includeImdbSearch) { ids += "&imdbid=" + searchCriteria.Series.ImdbId; } if (includeTvRageSearch) { ids += "&rid=" + searchCriteria.Series.TvRageId; } if (includeTvMazeSearch) { ids += "&tvmazeid=" + searchCriteria.Series.TvMazeId; } chain.Add(GetPagedRequests(MaxPages, categories, "tvsearch", ids + parameters)); } else { if (includeTvdbSearch) { chain.Add(GetPagedRequests(MaxPages, categories, "tvsearch", string.Format("&tvdbid={0}{1}", searchCriteria.Series.TvdbId, parameters))); } else if (includeImdbSearch) { chain.Add(GetPagedRequests(MaxPages, categories, "tvsearch", string.Format("&imdbid={0}{1}", searchCriteria.Series.ImdbId, parameters))); } else if (includeTvRageSearch) { chain.Add(GetPagedRequests(MaxPages, categories, "tvsearch", string.Format("&rid={0}{1}", searchCriteria.Series.TvRageId, parameters))); } else if (includeTvMazeSearch) { chain.Add(GetPagedRequests(MaxPages, categories, "tvsearch", string.Format("&tvmazeid={0}{1}", searchCriteria.Series.TvMazeId, parameters))); } } }
public List <Episode> GetEpisodes(ParsedEpisodeInfo parsedEpisodeInfo, Series series, bool sceneSource, SearchCriteriaBase searchCriteria = null) { var result = new List <Episode>(); if (parsedEpisodeInfo.AirDate.HasValue) { if (series.SeriesType == SeriesTypes.Standard) { _logger.Warn("Found daily-style episode for non-daily series: {0}.", series); return(null); } var episodeInfo = GetDailyEpisode(series, parsedEpisodeInfo.AirDate.Value, searchCriteria); if (episodeInfo != null) { result.Add(episodeInfo); } return(result); } if (parsedEpisodeInfo.EpisodeNumbers == null) { return(result); } foreach (var episodeNumber in parsedEpisodeInfo.EpisodeNumbers) { Episode episodeInfo = null; if (series.UseSceneNumbering && sceneSource) { if (searchCriteria != null) { episodeInfo = searchCriteria.Episodes.SingleOrDefault(e => e.SceneSeasonNumber == parsedEpisodeInfo.SeasonNumber && e.SceneEpisodeNumber == episodeNumber); } if (episodeInfo == null) { episodeInfo = _episodeService.FindEpisode(series.Id, parsedEpisodeInfo.SeasonNumber, episodeNumber, true); } if (episodeInfo != null) { _logger.Info("Using Scene to TVDB Mapping for: {0} - Scene: {1}x{2:00} - TVDB: {3}x{4:00}", series.Title, episodeInfo.SceneSeasonNumber, episodeInfo.SceneEpisodeNumber, episodeInfo.SeasonNumber, episodeInfo.EpisodeNumber); } } if (episodeInfo == null && searchCriteria != null) { episodeInfo = searchCriteria.Episodes.SingleOrDefault(e => e.SeasonNumber == parsedEpisodeInfo.SeasonNumber && e.EpisodeNumber == episodeNumber); } if (episodeInfo == null) { episodeInfo = _episodeService.FindEpisode(series.Id, parsedEpisodeInfo.SeasonNumber, episodeNumber); } if (episodeInfo != null) { result.Add(episodeInfo); } else { _logger.Debug("Unable to find {0}", parsedEpisodeInfo); } } return(result); }
public List <Book> GetBooks(ParsedBookInfo parsedBookInfo, Author author, SearchCriteriaBase searchCriteria = null) { var bookTitle = parsedBookInfo.BookTitle; var result = new List <Book>(); if (parsedBookInfo.BookTitle == null) { return(new List <Book>()); } Book bookInfo = null; if (parsedBookInfo.Discography) { if (parsedBookInfo.DiscographyStart > 0) { return(_bookService.AuthorBooksBetweenDates(author, new DateTime(parsedBookInfo.DiscographyStart, 1, 1), new DateTime(parsedBookInfo.DiscographyEnd, 12, 31), false)); } if (parsedBookInfo.DiscographyEnd > 0) { return(_bookService.AuthorBooksBetweenDates(author, new DateTime(1800, 1, 1), new DateTime(parsedBookInfo.DiscographyEnd, 12, 31), false)); } return(_bookService.GetBooksByAuthor(author.Id)); } if (searchCriteria != null) { var cleanTitle = Parser.CleanAuthorName(parsedBookInfo.BookTitle); bookInfo = searchCriteria.Books.ExclusiveOrDefault(e => e.Title == bookTitle || e.CleanTitle == cleanTitle); } if (bookInfo == null) { // TODO: Search by Title and Year instead of just Title when matching bookInfo = _bookService.FindByTitle(author.AuthorMetadataId, parsedBookInfo.BookTitle); } if (bookInfo == null) { _logger.Debug("Trying inexact book match for {0}", parsedBookInfo.BookTitle); bookInfo = _bookService.FindByTitleInexact(author.AuthorMetadataId, parsedBookInfo.BookTitle); } if (bookInfo != null) { result.Add(bookInfo); } else { _logger.Debug("Unable to find {0}", parsedBookInfo); } return(result); }
public List <Episode> GetEpisodes(ParsedEpisodeInfo parsedEpisodeInfo, Series series, bool sceneSource, SearchCriteriaBase searchCriteria = null) { var result = new List <Episode>(); if (parsedEpisodeInfo.FullSeason) { return(_episodeService.GetEpisodesBySeason(series.Id, parsedEpisodeInfo.SeasonNumber)); } if (parsedEpisodeInfo.IsDaily) { if (series.SeriesType == SeriesTypes.Standard) { _logger.Warn("Found daily-style episode for non-daily series: {0}.", series); return(result); } var episodeInfo = GetDailyEpisode(series, parsedEpisodeInfo.AirDate, searchCriteria); if (episodeInfo != null) { result.Add(episodeInfo); } return(result); } if (parsedEpisodeInfo.IsAbsoluteNumbering) { var sceneSeasonNumber = _sceneMappingService.GetSeasonNumber(parsedEpisodeInfo.SeriesTitle); foreach (var absoluteEpisodeNumber in parsedEpisodeInfo.AbsoluteEpisodeNumbers) { Episode episode = null; if (parsedEpisodeInfo.Special) { episode = _episodeService.FindEpisode(series.Id, 0, absoluteEpisodeNumber); } else if (sceneSource) { if (sceneSeasonNumber.HasValue && (sceneSeasonNumber == 0 || sceneSeasonNumber > 1)) { var episodes = _episodeService.FindEpisodesBySceneNumbering(series.Id, sceneSeasonNumber.Value, absoluteEpisodeNumber); if (episodes.Count == 1) { episode = episodes.First(); } if (episode == null) { episode = _episodeService.FindEpisode(series.Id, sceneSeasonNumber.Value, absoluteEpisodeNumber); } } else { episode = _episodeService.FindEpisodeBySceneNumbering(series.Id, absoluteEpisodeNumber); } } if (episode == null) { episode = _episodeService.FindEpisode(series.Id, absoluteEpisodeNumber); } if (episode != null) { _logger.Debug("Using absolute episode number {0} for: {1} - TVDB: {2}x{3:00}", absoluteEpisodeNumber, series.Title, episode.SeasonNumber, episode.EpisodeNumber); result.Add(episode); } } return(result); } if (parsedEpisodeInfo.EpisodeNumbers == null) { return(result); } foreach (var episodeNumber in parsedEpisodeInfo.EpisodeNumbers) { if (series.UseSceneNumbering && sceneSource) { List <Episode> episodes = new List <Episode>(); if (searchCriteria != null) { episodes = searchCriteria.Episodes.Where(e => e.SceneSeasonNumber == parsedEpisodeInfo.SeasonNumber && e.SceneEpisodeNumber == episodeNumber).ToList(); } if (!episodes.Any()) { episodes = _episodeService.FindEpisodesBySceneNumbering(series.Id, parsedEpisodeInfo.SeasonNumber, episodeNumber); } if (episodes != null && episodes.Any()) { _logger.Debug("Using Scene to TVDB Mapping for: {0} - Scene: {1}x{2:00} - TVDB: {3}", series.Title, episodes.First().SceneSeasonNumber, episodes.First().SceneEpisodeNumber, String.Join(", ", episodes.Select(e => String.Format("{0}x{1:00}", e.SeasonNumber, e.EpisodeNumber)))); result.AddRange(episodes); continue; } } Episode episodeInfo = null; if (searchCriteria != null) { episodeInfo = searchCriteria.Episodes.SingleOrDefault(e => e.SeasonNumber == parsedEpisodeInfo.SeasonNumber && e.EpisodeNumber == episodeNumber); } if (episodeInfo == null) { episodeInfo = _episodeService.FindEpisode(series.Id, parsedEpisodeInfo.SeasonNumber, episodeNumber); } if (episodeInfo != null) { result.Add(episodeInfo); } else { _logger.Debug("Unable to find {0}", parsedEpisodeInfo); } } return(result); }
public virtual Decision IsSatisfiedBy(RemoteAlbum subject, SearchCriteriaBase searchCriteria) { if (searchCriteria != null && searchCriteria.UserInvokedSearch) { _logger.Debug("Ignoring delay for user invoked search"); return(Decision.Accept()); } var qualityProfile = subject.Artist.QualityProfile.Value; var delayProfile = _delayProfileService.BestForTags(subject.Artist.Tags); var delay = delayProfile.GetProtocolDelay(subject.Release.DownloadProtocol); var isPreferredProtocol = subject.Release.DownloadProtocol == delayProfile.PreferredProtocol; if (delay == 0) { _logger.Debug("Profile does not require a waiting period before download for {0}.", subject.Release.DownloadProtocol); return(Decision.Accept()); } var qualityComparer = new QualityModelComparer(qualityProfile); if (isPreferredProtocol) { foreach (var album in subject.Albums) { var trackFiles = _mediaFileService.GetFilesByAlbum(album.Id); if (trackFiles.Any()) { var currentQualities = trackFiles.Select(c => c.Quality).Distinct().ToList(); var upgradable = _upgradableSpecification.IsUpgradable(qualityProfile, currentQualities, _preferredWordServiceCalculator.Calculate(subject.Artist, trackFiles[0].GetSceneOrFileName()), subject.ParsedAlbumInfo.Quality, subject.PreferredWordScore); if (upgradable) { _logger.Debug("New quality is a better revision for existing quality, skipping delay"); return(Decision.Accept()); } } } } // If quality meets or exceeds the best allowed quality in the profile accept it immediately var bestQualityInProfile = qualityProfile.LastAllowedQuality(); var isBestInProfile = qualityComparer.Compare(subject.ParsedAlbumInfo.Quality.Quality, bestQualityInProfile) >= 0; if (isBestInProfile && isPreferredProtocol) { _logger.Debug("Quality is highest in profile for preferred protocol, will not delay"); return(Decision.Accept()); } var albumIds = subject.Albums.Select(e => e.Id); var oldest = _pendingReleaseService.OldestPendingRelease(subject.Artist.Id, albumIds.ToArray()); if (oldest != null && oldest.Release.AgeMinutes > delay) { return(Decision.Accept()); } if (subject.Release.AgeMinutes < delay) { _logger.Debug("Waiting for better quality release, There is a {0} minute delay on {1}", delay, subject.Release.DownloadProtocol); return(Decision.Reject("Waiting for better quality release")); } return(Decision.Accept()); }
private void AddTvIdPageableRequests(IndexerPageableRequestChain chain, int maxPages, IEnumerable <int> categories, SearchCriteriaBase searchCriteria, string parameters) { var includeTvdbSearch = SupportsTvdbSearch && searchCriteria.Series.TvdbId > 0; var includeTvRageSearch = SupportsTvRageSearch && searchCriteria.Series.TvRageId > 0; var includeTvMazeSearch = SupportsTvMazeSearch && searchCriteria.Series.TvMazeId > 0; if (SupportsAggregatedIdSearch && (includeTvdbSearch || includeTvRageSearch || includeTvMazeSearch)) { var ids = ""; if (includeTvdbSearch) { ids += "&tvdbid=" + searchCriteria.Series.TvdbId; } if (includeTvRageSearch) { ids += "&rid=" + searchCriteria.Series.TvRageId; } if (includeTvMazeSearch) { ids += "&tvmazeid=" + searchCriteria.Series.TvMazeId; } chain.Add(GetPagedRequests(maxPages, categories, "tvsearch", ids + parameters)); } else { if (includeTvdbSearch) { chain.Add(GetPagedRequests(maxPages, categories, "tvsearch", string.Format("&tvdbid={0}{1}", searchCriteria.Series.TvdbId, parameters))); } else if (includeTvRageSearch) { chain.Add(GetPagedRequests(maxPages, categories, "tvsearch", string.Format("&rid={0}{1}", searchCriteria.Series.TvRageId, parameters))); } else if (includeTvMazeSearch) { chain.Add(GetPagedRequests(maxPages, categories, "tvsearch", string.Format("&tvmazeid={0}{1}", searchCriteria.Series.TvMazeId, parameters))); } } if (SupportsTvSearch) { chain.AddTier(); foreach (var queryTitle in searchCriteria.QueryTitles) { chain.Add(GetPagedRequests(MaxPages, Settings.Categories, "tvsearch", string.Format("&q={0}{1}", NewsnabifyTitle(queryTitle), parameters))); } } }
private IEnumerable <DownloadDecision> GetDecisions(List <ReleaseInfo> reports, SearchCriteriaBase searchCriteria = null) { if (reports.Any()) { _logger.ProgressInfo("Processing {0} releases", reports.Count); } else { _logger.ProgressInfo("No results found"); } var reportNumber = 1; foreach (var report in reports) { DownloadDecision decision = null; _logger.ProgressTrace("Processing release {0}/{1}", reportNumber, reports.Count); _logger.Debug("Processing release '{0}' from '{1}'", report.Title, report.Indexer); try { var parsedEpisodeInfo = Parser.Parser.ParseTitle(report.Title); if (parsedEpisodeInfo == null || parsedEpisodeInfo.IsPossibleSpecialEpisode) { var specialEpisodeInfo = _parsingService.ParseSpecialEpisodeTitle(parsedEpisodeInfo, report.Title, report.TvdbId, report.TvRageId, searchCriteria); if (specialEpisodeInfo != null) { parsedEpisodeInfo = specialEpisodeInfo; } } if (parsedEpisodeInfo != null && !parsedEpisodeInfo.SeriesTitle.IsNullOrWhiteSpace()) { var remoteEpisode = _parsingService.Map(parsedEpisodeInfo, report.TvdbId, report.TvRageId, searchCriteria); remoteEpisode.Release = report; if (remoteEpisode.Series == null) { var reason = "Unknown Series"; var matchingTvdbId = _sceneMappingService.FindTvdbId(parsedEpisodeInfo.SeriesTitle, parsedEpisodeInfo.ReleaseTitle); if (matchingTvdbId.HasValue) { reason = $"{parsedEpisodeInfo.SeriesTitle} matches an alias for series with TVDB ID: {matchingTvdbId}"; } decision = new DownloadDecision(remoteEpisode, new Rejection(reason)); } else if (remoteEpisode.Episodes.Empty()) { decision = new DownloadDecision(remoteEpisode, new Rejection("Unable to parse episodes from release name")); } else { _aggregationService.Augment(remoteEpisode); remoteEpisode.DownloadAllowed = remoteEpisode.Episodes.Any(); decision = GetDecisionForReport(remoteEpisode, searchCriteria); } } if (searchCriteria != null) { if (parsedEpisodeInfo == null) { parsedEpisodeInfo = new ParsedEpisodeInfo { Language = LanguageParser.ParseLanguage(report.Title), Quality = QualityParser.ParseQuality(report.Title) }; } if (parsedEpisodeInfo.SeriesTitle.IsNullOrWhiteSpace()) { var remoteEpisode = new RemoteEpisode { Release = report, ParsedEpisodeInfo = parsedEpisodeInfo }; decision = new DownloadDecision(remoteEpisode, new Rejection("Unable to parse release")); } } } catch (Exception e) { _logger.Error(e, "Couldn't process release."); var remoteEpisode = new RemoteEpisode { Release = report }; decision = new DownloadDecision(remoteEpisode, new Rejection("Unexpected error processing release")); } reportNumber++; if (decision != null) { if (decision.Rejections.Any()) { _logger.Debug("Release rejected for the following reasons: {0}", string.Join(", ", decision.Rejections)); } else { _logger.Debug("Release accepted"); } yield return(decision); } } }
private void AddAudioPageableRequests(IndexerPageableRequestChain chain, SearchCriteriaBase searchCriteria, string parameters) { chain.AddTier(); chain.Add(GetPagedRequests(MaxPages, Settings.Categories, "music", $"&q={parameters}")); }
public Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) { var cdhEnabled = _configService.EnableCompletedDownloadHandling; if (!cdhEnabled) { _logger.Debug("Skipping already imported check because CDH is disabled"); return(Decision.Accept()); } _logger.Debug("Performing already imported check on report"); foreach (var episode in subject.Episodes) { if (!episode.HasFile) { _logger.Debug("Skipping already imported check for episode without file"); continue; } var historyForEpisode = _historyService.FindByEpisodeId(episode.Id); var lastGrabbed = historyForEpisode.FirstOrDefault(h => h.EventType == HistoryEventType.Grabbed); if (lastGrabbed == null) { continue; } var imported = historyForEpisode.FirstOrDefault(h => h.EventType == HistoryEventType.DownloadFolderImported && h.DownloadId == lastGrabbed.DownloadId); if (imported == null) { continue; } // This is really only a guard against redownloading the same release over // and over when the grabbed and imported qualities do not match, if they do // match skip this check. if (lastGrabbed.Quality.Equals(imported.Quality)) { continue; } var release = subject.Release; if (release.DownloadProtocol == DownloadProtocol.Torrent) { var torrentInfo = release as TorrentInfo; if (torrentInfo?.InfoHash != null && torrentInfo.InfoHash.ToUpper() == lastGrabbed.DownloadId) { _logger.Debug("Has same torrent hash as a grabbed and imported release"); return(Decision.Reject("Has same torrent hash as a grabbed and imported release")); } } // Only based on title because a release with the same title on another indexer/released at // a different time very likely has the exact same content and we don't need to also try it. if (release.Title.Equals(lastGrabbed.SourceTitle, StringComparison.InvariantCultureIgnoreCase)) { _logger.Debug("Has same release name as a grabbed and imported release"); return(Decision.Reject("Has same release name as a grabbed and imported release")); } } return(Decision.Accept()); }
private List <DownloadDecision> SearchSpecial(Series series, List <Episode> episodes) { var searchSpec = Get <SpecialEpisodeSearchCriteria>(series, episodes); // build list of queries for each episode in the form: "<series> <episode-title>" searchSpec.EpisodeQueryTitles = episodes.Where(e => !string.IsNullOrWhiteSpace(e.Title)) .SelectMany(e => searchSpec.QueryTitles.Select(title => title + " " + SearchCriteriaBase.GetQueryTitle(e.Title))) .ToArray(); return(Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec)); }
public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) { //How do we want to handle drone being off and the automatic search being triggered? //TODO: Add a flag to the search to state it is a "scheduled" search if (searchCriteria != null) { _logger.Debug("Ignore delay for searches"); return(Decision.Accept()); } var profile = subject.Series.Profile.Value; if (profile.GrabDelay == 0) { _logger.Debug("Profile does not delay before download"); return(Decision.Accept()); } var comparer = new QualityModelComparer(profile); foreach (var file in subject.Episodes.Where(c => c.EpisodeFileId != 0).Select(c => c.EpisodeFile.Value)) { var upgradable = _qualityUpgradableSpecification.IsUpgradable(profile, file.Quality, subject.ParsedEpisodeInfo.Quality); if (upgradable) { var revisionUpgrade = _qualityUpgradableSpecification.IsRevisionUpgrade(file.Quality, subject.ParsedEpisodeInfo.Quality); if (revisionUpgrade) { _logger.Debug("New quality is a better revision for existing quality, skipping delay"); return(Decision.Accept()); } } } //If quality meets or exceeds the best allowed quality in the profile accept it immediately var bestQualityInProfile = new QualityModel(profile.Items.Last(q => q.Allowed).Quality); var bestCompare = comparer.Compare(subject.ParsedEpisodeInfo.Quality, bestQualityInProfile); if (bestCompare >= 0) { _logger.Debug("Quality is highest in profile, will not delay"); return(Decision.Accept()); } if (profile.GrabDelayMode == GrabDelayMode.Cutoff) { var cutoff = new QualityModel(profile.Cutoff); var cutoffCompare = comparer.Compare(subject.ParsedEpisodeInfo.Quality, cutoff); if (cutoffCompare >= 0) { _logger.Debug("Quality meets or exceeds the cutoff, will not delay"); return(Decision.Accept()); } } if (profile.GrabDelayMode == GrabDelayMode.First) { var episodeIds = subject.Episodes.Select(e => e.Id); var oldest = _pendingReleaseService.GetPendingRemoteEpisodes(subject.Series.Id) .Where(r => r.Episodes.Select(e => e.Id).Intersect(episodeIds).Any()) .OrderByDescending(p => p.Release.AgeHours) .FirstOrDefault(); if (oldest != null && oldest.Release.AgeHours > profile.GrabDelay) { return(Decision.Accept()); } } if (subject.Release.AgeHours < profile.GrabDelay) { _logger.Debug("Age ({0}) is less than delay {1}, delaying", subject.Release.AgeHours, profile.GrabDelay); return(Decision.Reject("Waiting for better quality release")); } return(Decision.Accept()); }
private List <DownloadDecision> Dispatch(Func <IIndexer, IEnumerable <ReleaseInfo> > searchAction, SearchCriteriaBase criteriaBase) { var indexers = _indexerFactory.SearchEnabled(); var reports = new List <ReleaseInfo>(); _logger.ProgressInfo("Searching {0} indexers for {1}", indexers.Count, criteriaBase); var taskList = new List <Task>(); var taskFactory = new TaskFactory(TaskCreationOptions.LongRunning, TaskContinuationOptions.None); foreach (var indexer in indexers) { var indexerLocal = indexer; taskList.Add(taskFactory.StartNew(() => { try { var indexerReports = searchAction(indexerLocal); lock (reports) { reports.AddRange(indexerReports); } } catch (Exception e) { _logger.Error(e, "Error while searching for " + criteriaBase); } }).LogExceptions()); } Task.WaitAll(taskList.ToArray()); _logger.Debug("Total of {0} reports were found for {1} from {2} indexers", reports.Count, criteriaBase, indexers.Count); return(_makeDownloadDecision.GetSearchDecision(reports, criteriaBase).ToList()); }
private List <DownloadDecision> Dispatch(Func <IIndexer, IEnumerable <ReleaseInfo> > searchAction, SearchCriteriaBase criteriaBase) { var indexers = criteriaBase.InteractiveSearch ? _indexerFactory.InteractiveSearchEnabled() : _indexerFactory.AutomaticSearchEnabled(); // Filter indexers to untagged indexers and indexers with intersecting tags indexers = indexers.Where(i => i.Definition.Tags.Empty() || i.Definition.Tags.Intersect(criteriaBase.Movie.Tags).Any()).ToList(); var reports = new List <ReleaseInfo>(); _logger.ProgressInfo("Searching indexers for {0}. {1} active indexers", criteriaBase, indexers.Count); var taskList = new List <Task>(); var taskFactory = new TaskFactory(TaskCreationOptions.LongRunning, TaskContinuationOptions.None); foreach (var indexer in indexers) { var indexerLocal = indexer; taskList.Add(taskFactory.StartNew(() => { try { var indexerReports = searchAction(indexerLocal); lock (reports) { reports.AddRange(indexerReports); } } catch (Exception e) { _logger.Error(e, "Error while searching for {0}", criteriaBase); } }).LogExceptions()); } Task.WaitAll(taskList.ToArray()); _logger.Debug("Total of {0} reports were found for {1} from {2} indexers", reports.Count, criteriaBase, indexers.Count); return(_makeDownloadDecision.GetSearchDecision(reports, criteriaBase).ToList()); }
public virtual Decision IsSatisfiedBy(RemoteAlbum subject, SearchCriteriaBase searchCriteria) { if (searchCriteria != null) { _logger.Debug("Skipping history check during search"); return(Decision.Accept()); } var cdhEnabled = _configService.EnableCompletedDownloadHandling; _logger.Debug("Performing history status check on report"); foreach (var album in subject.Albums) { _logger.Debug("Checking current status of album [{0}] in history", album.Id); var mostRecent = _historyService.MostRecentForAlbum(album.Id); if (mostRecent != null && mostRecent.EventType == HistoryEventType.Grabbed) { var recent = mostRecent.Date.After(DateTime.UtcNow.AddHours(-12)); // The artist will be the same as the one in history since it's the same album. // Instead of fetching the artist from the DB reuse the known artist. var preferredWordScore = _preferredWordServiceCalculator.Calculate(subject.Artist, mostRecent.SourceTitle); var cutoffUnmet = _upgradableSpecification.CutoffNotMet( subject.Artist.QualityProfile, new List <QualityModel> { mostRecent.Quality }, preferredWordScore, subject.ParsedAlbumInfo.Quality, subject.PreferredWordScore); var upgradeable = _upgradableSpecification.IsUpgradable( subject.Artist.QualityProfile, new List <QualityModel> { mostRecent.Quality }, preferredWordScore, subject.ParsedAlbumInfo.Quality, subject.PreferredWordScore); if (!recent && cdhEnabled) { continue; } if (!cutoffUnmet) { if (recent) { return(Decision.Reject("Recent grab event in history already meets cutoff: {0}", mostRecent.Quality)); } return(Decision.Reject("CDH is disabled and grab event in history already meets cutoff: {0}", mostRecent.Quality)); } if (!upgradeable) { if (recent) { return(Decision.Reject("Recent grab event in history is of equal or higher quality: {0}", mostRecent.Quality)); } return(Decision.Reject("CDH is disabled and grab event in history is of equal or higher quality: {0}", mostRecent.Quality)); } } } return(Decision.Accept()); }
private void AddTitlePageableRequests(IndexerPageableRequestChain chain, IEnumerable <int> categories, SearchCriteriaBase searchCriteria, string parameters) { if (SupportsTvTitleSearch) { foreach (var searchTerm in searchCriteria.SceneTitles) { chain.Add(GetPagedRequests(MaxPages, Settings.Categories, "tvsearch", string.Format("&title={0}{1}", Uri.EscapeDataString(searchTerm), parameters))); } } else if (SupportsTvSearch) { var queryTitles = TvTextSearchEngine == "raw" ? searchCriteria.SceneTitles : searchCriteria.QueryTitles; foreach (var queryTitle in queryTitles) { chain.Add(GetPagedRequests(MaxPages, Settings.Categories, "tvsearch", string.Format("&q={0}{1}", NewsnabifyTitle(queryTitle), parameters))); } } }
public Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) { _logger.Debug("Beginning size check for: {0}", subject); var quality = subject.ParsedEpisodeInfo.Quality.Quality; if (subject.ParsedEpisodeInfo.Special) { _logger.Debug("Special release found, skipping size check."); return(Decision.Accept()); } if (subject.Release.Size == 0) { _logger.Debug("Release has unknown size, skipping size check."); return(Decision.Accept()); } var qualityDefinition = _qualityDefinitionService.Get(quality); if (qualityDefinition.MinSize.HasValue) { var minSize = qualityDefinition.MinSize.Value.Megabytes(); //Multiply maxSize by Series.Runtime minSize = minSize * subject.Series.Runtime * subject.Episodes.Count; //If the parsed size is smaller than minSize we don't want it if (subject.Release.Size < minSize) { var runtimeMessage = subject.Episodes.Count == 1 ? $"{subject.Series.Runtime}min" : $"{subject.Episodes.Count}x {subject.Series.Runtime}min"; _logger.Debug("Item: {0}, Size: {1} is smaller than minimum allowed size ({2} bytes for {3}), rejecting.", subject, subject.Release.Size, minSize, runtimeMessage); return(Decision.Reject("{0} is smaller than minimum allowed {1} (for {2})", subject.Release.Size.SizeSuffix(), minSize.SizeSuffix(), runtimeMessage)); } } if (!qualityDefinition.MaxSize.HasValue || qualityDefinition.MaxSize.Value == 0) { _logger.Debug("Max size is unlimited - skipping check."); } else { var maxSize = qualityDefinition.MaxSize.Value.Megabytes(); //Multiply maxSize by Series.Runtime maxSize = maxSize * subject.Series.Runtime * subject.Episodes.Count; if (subject.Episodes.Count == 1) { Episode episode = subject.Episodes.First(); List <Episode> seasonEpisodes; var seasonSearchCriteria = searchCriteria as SeasonSearchCriteria; if (seasonSearchCriteria != null && !seasonSearchCriteria.Series.UseSceneNumbering && seasonSearchCriteria.Episodes.Any(v => v.Id == episode.Id)) { seasonEpisodes = (searchCriteria as SeasonSearchCriteria).Episodes; } else { seasonEpisodes = _episodeService.GetEpisodesBySeason(episode.SeriesId, episode.SeasonNumber); } //Ensure that this is either the first episode //or is the last episode in a season that has 10 or more episodes if (seasonEpisodes.First().Id == episode.Id || (seasonEpisodes.Count() >= 10 && seasonEpisodes.Last().Id == episode.Id)) { _logger.Debug("Possible double episode, doubling allowed size."); maxSize = maxSize * 2; } } //If the parsed size is greater than maxSize we don't want it if (subject.Release.Size > maxSize) { var runtimeMessage = subject.Episodes.Count == 1 ? $"{subject.Series.Runtime}min" : $"{subject.Episodes.Count}x {subject.Series.Runtime}min"; _logger.Debug("Item: {0}, Size: {1} is greater than maximum allowed size ({2} for {3}), rejecting.", subject, subject.Release.Size, maxSize, runtimeMessage); return(Decision.Reject("{0} is larger than maximum allowed {1} (for {2})", subject.Release.Size.SizeSuffix(), maxSize.SizeSuffix(), runtimeMessage)); } } _logger.Debug("Item: {0}, meets size constraints.", subject); return(Decision.Accept()); }
public List <Episode> GetEpisodes(ParsedEpisodeInfo parsedEpisodeInfo, Series series, bool sceneSource, SearchCriteriaBase searchCriteria = null) { if (parsedEpisodeInfo.FullSeason) { return(_episodeService.GetEpisodesBySeason(series.Id, parsedEpisodeInfo.SeasonNumber)); } if (parsedEpisodeInfo.IsDaily) { if (series.SeriesType == SeriesTypes.Standard) { _logger.Warn("Found daily-style episode for non-daily series: {0}.", series); return(new List <Episode>()); } var episodeInfo = GetDailyEpisode(series, parsedEpisodeInfo.AirDate, searchCriteria); if (episodeInfo != null) { return(new List <Episode> { episodeInfo }); } return(new List <Episode>()); } if (parsedEpisodeInfo.IsAbsoluteNumbering) { return(GetAnimeEpisodes(series, parsedEpisodeInfo, sceneSource)); } return(GetStandardEpisodes(series, parsedEpisodeInfo, sceneSource, searchCriteria)); }
private Rejection EvaluateSpec(IDecisionEngineSpecification spec, RemoteEpisode remoteEpisode, SearchCriteriaBase searchCriteriaBase = null) { try { var result = spec.IsSatisfiedBy(remoteEpisode, searchCriteriaBase); if (!result.Accepted) { return(new Rejection(result.Reason, spec.Type)); } } catch (Exception e) { e.Data.Add("report", remoteEpisode.Release.ToJson()); e.Data.Add("parsed", remoteEpisode.ParsedEpisodeInfo.ToJson()); _logger.ErrorException("Couldn't evaluate decision on " + remoteEpisode.Release.Title, e); return(new Rejection(string.Format("{0}: {1}", spec.GetType().Name, e.Message))); } return(null); }
private MappingResult GetMovie(ParsedMovieInfo parsedMovieInfo, string imdbId, SearchCriteriaBase searchCriteria) { // TODO: Answer me this: Wouldn't it be smarter to start out looking for a movie if we have an ImDb Id? MappingResult result = null; if (!String.IsNullOrWhiteSpace(imdbId) && imdbId != "0") { if (TryGetMovieByImDbId(parsedMovieInfo, imdbId, out result)) { return(result); } } if (searchCriteria != null) { if (TryGetMovieBySearchCriteria(parsedMovieInfo, searchCriteria, out result)) { return(result); } } else { TryGetMovieByTitleAndOrYear(parsedMovieInfo, out result); return(result); } // nothing found up to here => logging that and returning null _logger.Debug($"No matching movie {parsedMovieInfo.MovieTitle}"); return(result); }
public List <DownloadDecision> GetSearchDecision(List <ReleaseInfo> reports, SearchCriteriaBase searchCriteriaBase) { return(GetDecisions(reports, searchCriteriaBase).ToList()); }
private Series GetSeries(ParsedEpisodeInfo parsedEpisodeInfo, int tvdbId, int tvRageId, SearchCriteriaBase searchCriteria) { Series series = null; /*var localEpisode = _seriesService.FindByTitle(parsedEpisodeInfo.SeriesTitle); * * var sceneMappingTvdbId = _sceneMappingService.FindTvdbId(parsedEpisodeInfo.SeriesTitle); * if (localEpisode != null) * { * if (searchCriteria != null && searchCriteria.Series.TvdbId == localEpisode.TvdbId) * { * return searchCriteria.Series; * } * * series = _seriesService.FindByTvdbId(sceneMappingTvdbId.Value); * * if (series == null) * { * _logger.Debug("No matching series {0}", parsedEpisodeInfo.SeriesTitle); * return null; * } * * return series; * }*///This is only to find scene mapping should not be necessary for movies. if (searchCriteria != null) { if (searchCriteria.Series.CleanTitle == parsedEpisodeInfo.SeriesTitle.CleanSeriesTitle()) { return(searchCriteria.Series); } if (tvdbId > 0 && tvdbId == searchCriteria.Series.TvdbId) { //TODO: If series is found by TvdbId, we should report it as a scene naming exception, since it will fail to import return(searchCriteria.Series); } if (tvRageId > 0 && tvRageId == searchCriteria.Series.TvRageId) { //TODO: If series is found by TvRageId, we should report it as a scene naming exception, since it will fail to import return(searchCriteria.Series); } } series = _seriesService.FindByTitle(parsedEpisodeInfo.SeriesTitle); if (series == null && tvdbId > 0) { //TODO: If series is found by TvdbId, we should report it as a scene naming exception, since it will fail to import series = _seriesService.FindByTvdbId(tvdbId); } if (series == null && tvRageId > 0) { //TODO: If series is found by TvRageId, we should report it as a scene naming exception, since it will fail to import series = _seriesService.FindByTvRageId(tvRageId); } if (series == null) { _logger.Debug("No matching series {0}", parsedEpisodeInfo.SeriesTitle); return(null); } return(series); }
private IEnumerable <DownloadDecision> GetDecisions(List <ReleaseInfo> reports, SearchCriteriaBase searchCriteria = null) { if (reports.Any()) { _logger.ProgressInfo("Processing {0} releases", reports.Count); } else { _logger.ProgressInfo("No results found"); } var reportNumber = 1; foreach (var report in reports) { DownloadDecision decision = null; _logger.ProgressTrace("Processing release {0}/{1}", reportNumber, reports.Count); try { var parsedEpisodeInfo = Parser.Parser.ParseTitle(report.Title); if (parsedEpisodeInfo == null || parsedEpisodeInfo.IsPossibleSpecialEpisode) { var specialEpisodeInfo = _parsingService.ParseSpecialEpisodeTitle(report.Title, report.TvdbId, report.TvRageId, searchCriteria); if (specialEpisodeInfo != null) { parsedEpisodeInfo = specialEpisodeInfo; } } if (parsedEpisodeInfo != null && !parsedEpisodeInfo.SeriesTitle.IsNullOrWhiteSpace()) { var remoteEpisode = _parsingService.Map(parsedEpisodeInfo, report.TvdbId, report.TvRageId, searchCriteria); remoteEpisode.Release = report; if (remoteEpisode.Series != null) { remoteEpisode.DownloadAllowed = remoteEpisode.Episodes.Any(); decision = GetDecisionForReport(remoteEpisode, searchCriteria); } else { decision = new DownloadDecision(remoteEpisode, new Rejection("Unknown Series")); } } } catch (Exception e) { _logger.ErrorException("Couldn't process release.", e); } reportNumber++; if (decision != null) { if (decision.Rejections.Any()) { _logger.Debug("Release rejected for the following reasons: {0}", string.Join(", ", decision.Rejections)); } else { _logger.Debug("Release accepted"); } yield return(decision); } } }
public Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria) { throw new NotImplementedException(); }
private List <DownloadDecision> SearchSpecial(Series series, List <Episode> episodes, bool monitoredOnly, bool userInvokedSearch, bool interactiveSearch) { var downloadDecisions = new List <DownloadDecision>(); var searchSpec = Get <SpecialEpisodeSearchCriteria>(series, episodes, monitoredOnly, userInvokedSearch, interactiveSearch); // build list of queries for each episode in the form: "<series> <episode-title>" searchSpec.EpisodeQueryTitles = episodes.Where(e => !string.IsNullOrWhiteSpace(e.Title)) .SelectMany(e => searchSpec.CleanSceneTitles.Select(title => title + " " + SearchCriteriaBase.GetCleanSceneTitle(e.Title))) .ToArray(); downloadDecisions.AddRange(Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec)); // Search for each episode by season/episode number as well foreach (var episode in episodes) { // Episode needs to be monitored if it's not an interactive search if (!interactiveSearch && monitoredOnly && !episode.Monitored) { continue; } downloadDecisions.AddRange(SearchSingle(series, episode, monitoredOnly, userInvokedSearch, interactiveSearch)); } return(DeDupeDecisions(downloadDecisions)); }