public void DownloadReport(RemoteEpisode remoteEpisode) { Ensure.That(remoteEpisode.Series, () => remoteEpisode.Series).IsNotNull(); Ensure.That(remoteEpisode.Episodes, () => remoteEpisode.Episodes).HasItems(); var downloadTitle = remoteEpisode.Release.Title; var downloadClient = _downloadClientProvider.GetDownloadClient(remoteEpisode.Release.DownloadProtocol); if (downloadClient == null) { _logger.Warn("{0} Download client isn't configured yet.", remoteEpisode.Release.DownloadProtocol); return; } // Limit grabs to 2 per second. if (remoteEpisode.Release.DownloadUrl.IsNotNullOrWhiteSpace() && !remoteEpisode.Release.DownloadUrl.StartsWith("magnet:")) { var uri = new Uri(remoteEpisode.Release.DownloadUrl); _rateLimitService.WaitAndPulse(uri.Host, TimeSpan.FromSeconds(2)); } var downloadClientId = downloadClient.Download(remoteEpisode); var episodeGrabbedEvent = new EpisodeGrabbedEvent(remoteEpisode); episodeGrabbedEvent.DownloadClient = downloadClient.GetType().Name; if (!String.IsNullOrWhiteSpace(downloadClientId)) { episodeGrabbedEvent.DownloadId = downloadClientId; } _logger.ProgressInfo("Report sent to download client. {0}", downloadTitle); _eventAggregator.PublishEvent(episodeGrabbedEvent); }
public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) { _logger.Debug("Checking if release meets restrictions: {0}", subject); var title = subject.Release.Title; var restrictions = _restrictionService.AllForTags(subject.Series.Tags); var required = restrictions.Where(r => r.Required.IsNotNullOrWhiteSpace()); var ignored = restrictions.Where(r => r.Ignored.IsNotNullOrWhiteSpace()); foreach (var r in required) { var split = r.Required.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries).ToList(); if (!ContainsAny(split, title)) { _logger.Debug("[{0}] does not contain one of the required terms: {1}", title, r.Required); return Decision.Reject("Does not contain one of the required terms: {0}", r.Required.Replace(",", ", ")); } } foreach (var r in ignored) { var split = r.Ignored.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(); if (ContainsAny(split, title)) { _logger.Debug("[{0}] contains one or more ignored terms: {1}", title, r.Ignored); return Decision.Reject("Contains one or more ignored terms: {0}", r.Ignored.Replace(",", ", ")); } } _logger.Debug("[{0}] No restrictions apply, allowing", subject); return Decision.Accept(); }
public void DownloadReport(RemoteEpisode remoteEpisode) { Ensure.That(remoteEpisode.Series, () => remoteEpisode.Series).IsNotNull(); Ensure.That(remoteEpisode.Episodes, () => remoteEpisode.Episodes).HasItems(); var downloadTitle = remoteEpisode.Release.Title; var downloadClient = _downloadClientProvider.GetDownloadClient(); if (downloadClient == null) { _logger.Warn("Download client isn't configured yet."); return; } var downloadClientId = downloadClient.DownloadNzb(remoteEpisode); var episodeGrabbedEvent = new EpisodeGrabbedEvent(remoteEpisode); episodeGrabbedEvent.DownloadClient = downloadClient.GetType().Name; if (!String.IsNullOrWhiteSpace(downloadClientId)) { episodeGrabbedEvent.DownloadClientId = downloadClientId; } _logger.ProgressInfo("Report sent to download client. {0}", downloadTitle); _eventAggregator.PublishEvent(episodeGrabbedEvent); }
public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) { var releaseGroup = subject.ParsedEpisodeInfo.ReleaseGroup; if (subject.Series.SeriesType != SeriesTypes.Anime) { return Decision.Accept(); } foreach (var file in subject.Episodes.Where(c => c.EpisodeFileId != 0).Select(c => c.EpisodeFile.Value)) { if (_qualityUpgradableSpecification.IsRevisionUpgrade(file.Quality, subject.ParsedEpisodeInfo.Quality)) { if (file.ReleaseGroup.IsNullOrWhiteSpace()) { _logger.Debug("Unable to compare release group, existing file's release group is unknown"); return Decision.Reject("Existing release group is unknown"); } if (releaseGroup.IsNullOrWhiteSpace()) { _logger.Debug("Unable to compare release group, release's release group is unknown"); return Decision.Reject("Release group is unknown"); } if (file.ReleaseGroup != releaseGroup) { _logger.Debug("Existing Release group is: {0} - release's release group is: {1}", file.ReleaseGroup, releaseGroup); return Decision.Reject("{0} does not match existing release group {1}", releaseGroup, file.ReleaseGroup); } } } return Decision.Accept(); }
public void DownloadNzb(RemoteEpisode remoteEpisode) { var url = remoteEpisode.Release.DownloadUrl; var title = remoteEpisode.Release.Title; if (remoteEpisode.ParsedEpisodeInfo.FullSeason) { throw new NotImplementedException("Full season Pneumatic releases are not supported."); } title = FileNameBuilder.CleanFilename(title); //Save to the Pneumatic directory (The user will need to ensure its accessible by XBMC) var filename = Path.Combine(_configService.PneumaticFolder, title + ".nzb"); logger.Trace("Downloading NZB from: {0} to: {1}", url, filename); _httpProvider.DownloadFile(url, filename); logger.Trace("NZB Download succeeded, saved to: {0}", filename); var contents = String.Format("plugin://plugin.program.pneumatic/?mode=strm&type=add_file&nzb={0}&nzbname={1}", filename, title); _diskProvider.WriteAllText(Path.Combine(_configService.DownloadedEpisodesFolder, title + ".strm"), contents); }
public Decision IsSatisfiedBy(RemoteEpisode remoteEpisode, SearchCriteriaBase searchCriteria) { if (searchCriteria == null) { return Decision.Accept(); } var singleEpisodeSpec = searchCriteria as SingleEpisodeSearchCriteria; if (singleEpisodeSpec == null) return Decision.Accept(); if (singleEpisodeSpec.SeasonNumber != remoteEpisode.ParsedEpisodeInfo.SeasonNumber) { _logger.Debug("Season number does not match searched season number, skipping."); return Decision.Reject("Wrong season"); } if (!remoteEpisode.ParsedEpisodeInfo.EpisodeNumbers.Any()) { _logger.Debug("Full season result during single episode search, skipping."); return Decision.Reject("Full season pack"); } if (!remoteEpisode.ParsedEpisodeInfo.EpisodeNumbers.Contains(singleEpisodeSpec.EpisodeNumber)) { _logger.Debug("Episode number does not match searched episode number, skipping."); return Decision.Reject("Wrong episode"); } return Decision.Accept(); }
private bool IsInQueue(RemoteEpisode newEpisode, IEnumerable<RemoteEpisode> queue) { var matchingSeries = queue.Where(q => q.Series.Id == newEpisode.Series.Id); var matchingSeriesAndQuality = matchingSeries.Where(q => new QualityModelComparer(q.Series.QualityProfile).Compare(q.ParsedEpisodeInfo.Quality, newEpisode.ParsedEpisodeInfo.Quality) >= 0); return matchingSeriesAndQuality.Any(q => q.Episodes.Select(e => e.Id).Intersect(newEpisode.Episodes.Select(e => e.Id)).Any()); }
public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) { if (subject.Release.DownloadProtocol != Indexers.DownloadProtocol.Usenet) { _logger.Debug("Not checking minimum age requirement for non-usenet report"); return Decision.Accept(); } var age = subject.Release.AgeMinutes; var minimumAge = _configService.MinimumAge; if (minimumAge == 0) { _logger.Debug("Minimum age is not set."); return Decision.Accept(); } _logger.Debug("Checking if report meets minimum age requirements. {0}", age); if (age < minimumAge) { _logger.Debug("Only {0} minutes old, minimum age is {1} minutes", age, minimumAge); return Decision.Reject("Only {0} minutes old, minimum age is {1} minutes", age, minimumAge); } _logger.Debug("Release is {0} minutes old, greater than minimum age of {1} minutes", age, minimumAge); return Decision.Accept(); }
private string EvaluateSpec(IRejectWithReason spec, RemoteEpisode remoteEpisode, SearchCriteriaBase searchCriteriaBase = null) { try { if (string.IsNullOrWhiteSpace(spec.RejectionReason)) { throw new InvalidOperationException("[Need Rejection Text]"); } var generalSpecification = spec as IDecisionEngineSpecification; if (generalSpecification != null && !generalSpecification.IsSatisfiedBy(remoteEpisode, searchCriteriaBase)) { return spec.RejectionReason; } } 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 string.Format("{0}: {1}", spec.GetType().Name, e.Message); } return null; }
public Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) { var queue = _queueService.GetQueue() .Select(q => q.RemoteEpisode).ToList(); var matchingSeries = queue.Where(q => q.Series.Id == subject.Series.Id); var matchingEpisode = matchingSeries.Where(q => q.Episodes.Select(e => e.Id).Intersect(subject.Episodes.Select(e => e.Id)).Any()); foreach (var remoteEpisode in matchingEpisode) { _logger.Debug("Checking if existing release in queue meets cutoff. Queued quality is: {0}", remoteEpisode.ParsedEpisodeInfo.Quality); if (!_qualityUpgradableSpecification.CutoffNotMet(subject.Series.Profile, remoteEpisode.ParsedEpisodeInfo.Quality, subject.ParsedEpisodeInfo.Quality)) { return Decision.Reject("Quality for release in queue already meets cutoff: {0}", remoteEpisode.ParsedEpisodeInfo.Quality); } _logger.Debug("Checking if release is higher quality than queued release. Queued quality is: {0}", remoteEpisode.ParsedEpisodeInfo.Quality); if (!_qualityUpgradableSpecification.IsUpgradable(subject.Series.Profile, remoteEpisode.ParsedEpisodeInfo.Quality, subject.ParsedEpisodeInfo.Quality)) { return Decision.Reject("Quality for release in queue is of equal or higher preference: {0}", remoteEpisode.ParsedEpisodeInfo.Quality); } } return Decision.Accept(); }
private DownloadDecision GetDecisionForReport(RemoteEpisode remoteEpisode, SearchCriteriaBase searchCriteria = null) { var reasons = _specifications.Select(c => EvaluateSpec(c, remoteEpisode, searchCriteria)) .Where(c => c != null); return new DownloadDecision(remoteEpisode, reasons.ToArray()); }
public virtual bool IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) { if (searchCriteria != null) { return true; } foreach (var file in subject.Episodes.Where(c => c.EpisodeFileId != 0).Select(c => c.EpisodeFile.Value)) { if (_qualityUpgradableSpecification.IsProperUpgrade(file.Quality, subject.ParsedEpisodeInfo.Quality)) { if (file.DateAdded < DateTime.Today.AddDays(-7)) { _logger.Trace("Proper for old file, rejecting: {0}", subject); return false; } if (!_configService.AutoDownloadPropers) { _logger.Trace("Auto downloading of propers is disabled"); return false; } } } return true; }
public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) { if (searchCriteria != null) { if (!searchCriteria.MonitoredEpisodesOnly) { _logger.Debug("Skipping monitored check during search"); return Decision.Accept(); } } if (!subject.Series.Monitored) { _logger.Debug("{0} is present in the DB but not tracked. skipping.", subject.Series); return Decision.Reject("Series is not monitored"); } var monitoredCount = subject.Episodes.Count(episode => episode.Monitored); if (monitoredCount == subject.Episodes.Count) { return Decision.Accept(); } _logger.Debug("Only {0}/{1} episodes are monitored. skipping.", monitoredCount, subject.Episodes.Count); return Decision.Reject("Episode is not monitored"); }
public virtual bool IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) { _logger.Debug("Checking if release contains any restricted terms: {0}", subject); var restrictionsString = _configService.ReleaseRestrictions; if (String.IsNullOrWhiteSpace(restrictionsString)) { _logger.Debug("No restrictions configured, allowing: {0}", subject); return true; } var restrictions = restrictionsString.Split(new []{ '\n' }, StringSplitOptions.RemoveEmptyEntries); foreach (var restriction in restrictions) { if (subject.Release.Title.ToLowerInvariant().Contains(restriction.ToLowerInvariant())) { _logger.Debug("{0} is restricted: {1}", subject, restriction); return false; } } _logger.Debug("No restrictions apply, allowing: {0}", subject); return true; }
public virtual bool IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) { _logger.Debug("Beginning size check for: {0}", subject); var quality = subject.ParsedEpisodeInfo.Quality.Quality; if (quality == Quality.RAWHD) { _logger.Debug("Raw-HD release found, skipping size check."); return true; } if (quality == Quality.Unknown) { _logger.Debug("Unknown quality. skipping size check."); return false; } var qualityDefinition = _qualityDefinitionService.Get(quality); var minSize = qualityDefinition.MinSize.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) { _logger.Debug("Item: {0}, Size: {1} is smaller than minimum allowed size ({2}), rejecting.", subject, subject.Release.Size, minSize); return false; } if (qualityDefinition.MaxSize == 0) { _logger.Debug("Max size is 0 (unlimited) - skipping check."); } else { var maxSize = qualityDefinition.MaxSize.Megabytes(); //Multiply maxSize by Series.Runtime maxSize = maxSize * subject.Series.Runtime * subject.Episodes.Count; //Check if there was only one episode parsed and it is the first if (subject.Episodes.Count == 1 && _episodeService.IsFirstOrLastEpisodeOfSeason(subject.Episodes.First().Id)) { maxSize = maxSize * 2; } //If the parsed size is greater than maxSize we don't want it if (subject.Release.Size > maxSize) { _logger.Debug("Item: {0}, Size: {1} is greater than maximum allowed size ({2}), rejecting.", subject, subject.Release.Size, maxSize); return false; } } _logger.Debug("Item: {0}, meets size constraints.", subject); return true; }
public bool IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) { if (subject.Release.Title.ToLower().Contains("sample") && subject.Release.Size < 70.Megabytes()) { return false; } return true; }
private void WithEnglishRelease() { parseResult = new RemoteEpisode { ParsedEpisodeInfo = new ParsedEpisodeInfo { Language = Language.English } }; }
public Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) { if (subject.Release.Title.ToLower().Contains("sample") && subject.Release.Size < 70.Megabytes()) { _logger.Debug("Sample release, rejecting."); return Decision.Reject("Sample"); } return Decision.Accept(); }
private void WithGermanRelease() { parseResult = new RemoteEpisode { ParsedEpisodeInfo = new ParsedEpisodeInfo { Language = Language.German } }; }
public Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) { if (_blacklistService.Blacklisted(subject.Series.Id, subject.Release)) { _logger.Debug("{0} is blacklisted, rejecting.", subject.Release.Title); return Decision.Reject("Release is blacklisted"); } return Decision.Accept(); }
public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) { if (_sameEpisodesSpecification.IsSatisfiedBy(subject.Episodes)) { return Decision.Accept(); } _logger.Debug("Episode file on disk contains more episodes than this release contains"); return Decision.Reject("Episode file on disk contains more episodes than this release contains"); }
public virtual bool IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) { _logger.Debug("Checking if report meets language requirements. {0}", subject.ParsedEpisodeInfo.Language); if (subject.ParsedEpisodeInfo.Language != Language.English) { _logger.Debug("Report Language: {0} rejected because it is not English", subject.ParsedEpisodeInfo.Language); return false; } return true; }
public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) { _logger.Debug("Checking if report meets quality requirements. {0}", subject.ParsedEpisodeInfo.Quality); if (!subject.Series.Profile.Value.Items.Exists(v => v.Allowed && v.Quality == subject.ParsedEpisodeInfo.Quality.Quality)) { _logger.Debug("Quality {0} rejected by Series' quality profile", subject.ParsedEpisodeInfo.Quality); return Decision.Reject("{0} is not wanted in profile", subject.ParsedEpisodeInfo.Quality.Quality); } return Decision.Accept(); }
public virtual bool IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) { _logger.Trace("Checking if report meets quality requirements. {0}", subject.ParsedEpisodeInfo.Quality); if (!subject.Series.QualityProfile.Value.Allowed.Contains(subject.ParsedEpisodeInfo.Quality.Quality)) { _logger.Trace("Quality {0} rejected by Series' quality profile", subject.ParsedEpisodeInfo.Quality); return false; } return true; }
public bool IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) { var queue = _queueService.GetQueue().Select(q => q.RemoteEpisode); if (IsInQueue(subject, queue)) { _logger.Debug("Already in queue, rejecting."); return false; } return true; }
public void DownloadReport(RemoteEpisode remoteEpisode) { Ensure.That(remoteEpisode.Series, () => remoteEpisode.Series).IsNotNull(); Ensure.That(remoteEpisode.Episodes, () => remoteEpisode.Episodes).HasItems(); var downloadTitle = remoteEpisode.Release.Title; var downloadClient = _downloadClientProvider.GetDownloadClient(remoteEpisode.Release.DownloadProtocol); if (downloadClient == null) { _logger.Warn("{0} Download client isn't configured yet.", remoteEpisode.Release.DownloadProtocol); return; } // Limit grabs to 2 per second. if (remoteEpisode.Release.DownloadUrl.IsNotNullOrWhiteSpace() && !remoteEpisode.Release.DownloadUrl.StartsWith("magnet:")) { var url = new HttpUri(remoteEpisode.Release.DownloadUrl); _rateLimitService.WaitAndPulse(url.Host, TimeSpan.FromSeconds(2)); } string downloadClientId; try { downloadClientId = downloadClient.Download(remoteEpisode); _indexerStatusService.RecordSuccess(remoteEpisode.Release.IndexerId); } catch (ReleaseDownloadException ex) { var http429 = ex.InnerException as TooManyRequestsException; if (http429 != null) { _indexerStatusService.RecordFailure(remoteEpisode.Release.IndexerId, http429.RetryAfter); } else { _indexerStatusService.RecordFailure(remoteEpisode.Release.IndexerId); } throw; } var episodeGrabbedEvent = new EpisodeGrabbedEvent(remoteEpisode); episodeGrabbedEvent.DownloadClient = downloadClient.GetType().Name; if (!string.IsNullOrWhiteSpace(downloadClientId)) { episodeGrabbedEvent.DownloadId = downloadClientId; } _logger.ProgressInfo("Report sent to {0}. {1}", downloadClient.Definition.Name, downloadTitle); _eventAggregator.PublishEvent(episodeGrabbedEvent); }
public virtual bool IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) { var age = subject.Release.Age; _logger.Trace("Checking if report meets retention requirements. {0}", age); if (_configService.Retention > 0 && age > _configService.Retention) { _logger.Trace("Report age: {0} rejected by user's retention limit", age); return false; } return true; }
public virtual Decision IsSatisfiedBy(RemoteEpisode 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 episode in subject.Episodes) { _logger.Debug("Checking current status of episode [{0}] in history", episode.Id); var mostRecent = _historyService.MostRecentForEpisode(episode.Id); if (mostRecent != null && mostRecent.EventType == HistoryEventType.Grabbed) { var recent = mostRecent.Date.After(DateTime.UtcNow.AddHours(-12)); var cutoffUnmet = _qualityUpgradableSpecification.CutoffNotMet(subject.Series.Profile, mostRecent.Quality, subject.ParsedEpisodeInfo.Quality); var upgradeable = _qualityUpgradableSpecification.IsUpgradable(subject.Series.Profile, mostRecent.Quality, subject.ParsedEpisodeInfo.Quality); 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(); }
public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) { var wantedLanguage = subject.Series.Profile.Value.Language; _logger.Debug("Checking if report meets language requirements. {0}", subject.ParsedEpisodeInfo.Language); if (subject.ParsedEpisodeInfo.Language != wantedLanguage) { _logger.Debug("Report Language: {0} rejected because it is not wanted, wanted {1}", subject.ParsedEpisodeInfo.Language, wantedLanguage); return Decision.Reject("{0} is wanted, but found {1}", wantedLanguage, subject.ParsedEpisodeInfo.Language); } return Decision.Accept(); }
public virtual bool IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) { foreach (var file in subject.Episodes.Where(c => c.EpisodeFileId != 0).Select(c => c.EpisodeFile.Value)) { _logger.Trace("Comparing file quality with report. Existing file is {0}", file.Quality); if (!_qualityUpgradableSpecification.IsUpgradable(file.Quality, subject.ParsedEpisodeInfo.Quality)) { return false; } } return true; }