using System; using System.Threading.Tasks; using NLog; using NzbDrone.Common.EnsureThat; using NzbDrone.Common.Extensions; using NzbDrone.Common.Http; using NzbDrone.Common.Instrumentation.Extensions; using NzbDrone.Common.TPL; using NzbDrone.Core.Download.Clients; using NzbDrone.Core.Download.Pending; using NzbDrone.Core.Exceptions; using NzbDrone.Core.Indexers; using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Parser.Model; namespace NzbDrone.Core.Download { public interface IDownloadService { Task DownloadReport(RemoteEpisode remoteEpisode, int? downloadClientId); } public class DownloadService : IDownloadService { private readonly IProvideDownloadClient _downloadClientProvider; private readonly IDownloadClientStatusService _downloadClientStatusService; private readonly IIndexerFactory _indexerFactory; private readonly IIndexerStatusService _indexerStatusService; private readonly IRateLimitService _rateLimitService; private readonly IEventAggregator _eventAggregator; private readonly ISeedConfigProvider _seedConfigProvider; private readonly Logger _logger; public DownloadService(IProvideDownloadClient downloadClientProvider, IDownloadClientStatusService downloadClientStatusService, IIndexerFactory indexerFactory, IIndexerStatusService indexerStatusService, IRateLimitService rateLimitService, IEventAggregator eventAggregator, ISeedConfigProvider seedConfigProvider, Logger logger) { _downloadClientProvider = downloadClientProvider; _downloadClientStatusService = downloadClientStatusService; _indexerFactory = indexerFactory; _indexerStatusService = indexerStatusService; _rateLimitService = rateLimitService; _eventAggregator = eventAggregator; _seedConfigProvider = seedConfigProvider; _logger = logger; } public async Task DownloadReport(RemoteEpisode remoteEpisode, int? downloadClientId) { var filterBlockedClients = remoteEpisode.Release.PendingReleaseReason == PendingReleaseReason.DownloadClientUnavailable; var tags = remoteEpisode.Series?.Tags; var downloadClient = downloadClientId.HasValue ? _downloadClientProvider.Get(downloadClientId.Value) : _downloadClientProvider.GetDownloadClient(remoteEpisode.Release.DownloadProtocol, remoteEpisode.Release.IndexerId, filterBlockedClients, tags); await DownloadReport(remoteEpisode, downloadClient); } private async Task DownloadReport(RemoteEpisode remoteEpisode, IDownloadClient downloadClient) { Ensure.That(remoteEpisode.Series, () => remoteEpisode.Series).IsNotNull(); Ensure.That(remoteEpisode.Episodes, () => remoteEpisode.Episodes).HasItems(); var downloadTitle = remoteEpisode.Release.Title; if (downloadClient == null) { throw new DownloadClientUnavailableException($"{remoteEpisode.Release.DownloadProtocol} Download client isn't configured yet"); } // Get the seed configuration for this release. remoteEpisode.SeedConfiguration = _seedConfigProvider.GetSeedConfiguration(remoteEpisode); // Limit grabs to 2 per second. if (remoteEpisode.Release.DownloadUrl.IsNotNullOrWhiteSpace() && !remoteEpisode.Release.DownloadUrl.StartsWith("magnet:")) { var url = new HttpUri(remoteEpisode.Release.DownloadUrl); await _rateLimitService.WaitAndPulseAsync(url.Host, TimeSpan.FromSeconds(2)); } IIndexer indexer = null; if (remoteEpisode.Release.IndexerId > 0) { indexer = _indexerFactory.GetInstance(_indexerFactory.Get(remoteEpisode.Release.IndexerId)); } string downloadClientId; try { downloadClientId = await downloadClient.Download(remoteEpisode, indexer); _downloadClientStatusService.RecordSuccess(downloadClient.Definition.Id); _indexerStatusService.RecordSuccess(remoteEpisode.Release.IndexerId); } catch (ReleaseUnavailableException) { _logger.Trace("Release {0} no longer available on indexer.", remoteEpisode); throw; } catch (ReleaseBlockedException) { _logger.Trace("Release {0} previously added to blocklist, not sending to download client again.", remoteEpisode); throw; } catch (DownloadClientRejectedReleaseException) { _logger.Trace("Release {0} rejected by download client, possible duplicate.", remoteEpisode); throw; } catch (ReleaseDownloadException ex) { if (ex.InnerException is TooManyRequestsException http429) { _indexerStatusService.RecordFailure(remoteEpisode.Release.IndexerId, http429.RetryAfter); } else { _indexerStatusService.RecordFailure(remoteEpisode.Release.IndexerId); } throw; } var episodeGrabbedEvent = new EpisodeGrabbedEvent(remoteEpisode); episodeGrabbedEvent.DownloadClient = downloadClient.Name; episodeGrabbedEvent.DownloadClientId = downloadClient.Definition.Id; episodeGrabbedEvent.DownloadClientName = downloadClient.Definition.Name; if (downloadClientId.IsNotNullOrWhiteSpace()) { episodeGrabbedEvent.DownloadId = downloadClientId; } _logger.ProgressInfo("Report sent to {0}. Indexer {1}. {2}", downloadClient.Definition.Name, remoteEpisode.Release.Indexer, downloadTitle); _eventAggregator.PublishEvent(episodeGrabbedEvent); } } }