diff --git a/src/NzbDrone.Api/Config/DownloadClientConfigModule.cs b/src/NzbDrone.Api/Config/DownloadClientConfigModule.cs index 9517acda2..c60e99234 100644 --- a/src/NzbDrone.Api/Config/DownloadClientConfigModule.cs +++ b/src/NzbDrone.Api/Config/DownloadClientConfigModule.cs @@ -17,14 +17,6 @@ namespace NzbDrone.Api.Config .SetValidator(pathExistsValidator) .When(c => !String.IsNullOrWhiteSpace(c.DownloadedEpisodesFolder)); - SharedValidator.RuleFor(c => c.BlacklistGracePeriod) - .InclusiveBetween(1, 24); - - SharedValidator.RuleFor(c => c.BlacklistRetryInterval) - .InclusiveBetween(5, 120); - - SharedValidator.RuleFor(c => c.BlacklistRetryLimit) - .InclusiveBetween(0, 10); } } } \ No newline at end of file diff --git a/src/NzbDrone.Api/Config/DownloadClientConfigResource.cs b/src/NzbDrone.Api/Config/DownloadClientConfigResource.cs index 5440099d7..94d039866 100644 --- a/src/NzbDrone.Api/Config/DownloadClientConfigResource.cs +++ b/src/NzbDrone.Api/Config/DownloadClientConfigResource.cs @@ -12,11 +12,7 @@ namespace NzbDrone.Api.Config public Boolean EnableCompletedDownloadHandling { get; set; } public Boolean RemoveCompletedDownloads { get; set; } - public Boolean EnableFailedDownloadHandling { get; set; } public Boolean AutoRedownloadFailed { get; set; } public Boolean RemoveFailedDownloads { get; set; } - public Int32 BlacklistGracePeriod { get; set; } - public Int32 BlacklistRetryInterval { get; set; } - public Int32 BlacklistRetryLimit { get; set; } } } diff --git a/src/NzbDrone.Api/History/HistoryModule.cs b/src/NzbDrone.Api/History/HistoryModule.cs index 9a9984566..9094a5337 100644 --- a/src/NzbDrone.Api/History/HistoryModule.cs +++ b/src/NzbDrone.Api/History/HistoryModule.cs @@ -12,15 +12,15 @@ namespace NzbDrone.Api.History { private readonly IHistoryService _historyService; private readonly IQualityUpgradableSpecification _qualityUpgradableSpecification; - private readonly IDownloadTrackingService _downloadTrackingService; + private readonly IFailedDownloadService _failedDownloadService; public HistoryModule(IHistoryService historyService, IQualityUpgradableSpecification qualityUpgradableSpecification, - IDownloadTrackingService downloadTrackingService) + IFailedDownloadService failedDownloadService) { _historyService = historyService; _qualityUpgradableSpecification = qualityUpgradableSpecification; - _downloadTrackingService = downloadTrackingService; + _failedDownloadService = failedDownloadService; GetResourcePaged = GetHistory; Post["/failed"] = x => MarkAsFailed(); @@ -28,9 +28,9 @@ namespace NzbDrone.Api.History protected override HistoryResource ToResource(TModel model) { - var resource = base.ToResource(model); + var resource = base.ToResource(model); - var history = model as NzbDrone.Core.History.History; + var history = model as Core.History.History; if (history != null && history.Series != null) { @@ -70,7 +70,7 @@ namespace NzbDrone.Api.History private Response MarkAsFailed() { var id = (int)Request.Form.Id; - _downloadTrackingService.MarkAsFailed(id); + _failedDownloadService.MarkAsFailed(id); return new Object().AsResponse(); } } diff --git a/src/NzbDrone.Api/History/HistoryResource.cs b/src/NzbDrone.Api/History/HistoryResource.cs index 48093f685..6d422f805 100644 --- a/src/NzbDrone.Api/History/HistoryResource.cs +++ b/src/NzbDrone.Api/History/HistoryResource.cs @@ -20,6 +20,7 @@ namespace NzbDrone.Api.History public string Indexer { get; set; } public string NzbInfoUrl { get; set; } public string ReleaseGroup { get; set; } + public string DownloadId { get; set; } public HistoryEventType EventType { get; set; } diff --git a/src/NzbDrone.Api/NzbDrone.Api.csproj b/src/NzbDrone.Api/NzbDrone.Api.csproj index 3a91799b6..585b40ec7 100644 --- a/src/NzbDrone.Api/NzbDrone.Api.csproj +++ b/src/NzbDrone.Api/NzbDrone.Api.csproj @@ -63,10 +63,6 @@ ..\packages\DDay.iCal.1.0.2.575\lib\DDay.iCal.dll - - False - ..\packages\Microsoft.AspNet.SignalR.Core.1.1.3\lib\net40\Microsoft.AspNet.SignalR.Core.dll - ..\packages\NLog.2.1.0\lib\net40\NLog.dll diff --git a/src/NzbDrone.Api/Queue/QueueActionModule.cs b/src/NzbDrone.Api/Queue/QueueActionModule.cs index cba735d74..485d5fde8 100644 --- a/src/NzbDrone.Api/Queue/QueueActionModule.cs +++ b/src/NzbDrone.Api/Queue/QueueActionModule.cs @@ -1,10 +1,10 @@ -using System.Linq; -using Nancy; +using Nancy; using Nancy.Responses; using NzbDrone.Api.Extensions; using NzbDrone.Api.REST; using NzbDrone.Core.Download; using NzbDrone.Core.Download.Pending; +using NzbDrone.Core.Download.TrackedDownloads; using NzbDrone.Core.Queue; namespace NzbDrone.Api.Queue @@ -12,21 +12,21 @@ namespace NzbDrone.Api.Queue public class QueueActionModule : NzbDroneRestModule { private readonly IQueueService _queueService; - private readonly IDownloadTrackingService _downloadTrackingService; + private readonly ITrackedDownloadService _trackedDownloadService; private readonly ICompletedDownloadService _completedDownloadService; private readonly IProvideDownloadClient _downloadClientProvider; private readonly IPendingReleaseService _pendingReleaseService; private readonly IDownloadService _downloadService; public QueueActionModule(IQueueService queueService, - IDownloadTrackingService downloadTrackingService, + ITrackedDownloadService trackedDownloadService, ICompletedDownloadService completedDownloadService, IProvideDownloadClient downloadClientProvider, IPendingReleaseService pendingReleaseService, IDownloadService downloadService) { _queueService = queueService; - _downloadTrackingService = downloadTrackingService; + _trackedDownloadService = trackedDownloadService; _completedDownloadService = completedDownloadService; _downloadClientProvider = downloadClientProvider; _pendingReleaseService = pendingReleaseService; @@ -60,7 +60,7 @@ namespace NzbDrone.Api.Queue throw new BadRequestException(); } - downloadClient.RemoveItem(trackedDownload.DownloadItem.DownloadClientId); + downloadClient.RemoveItem(trackedDownload.DownloadItem.DownloadId); return new object().AsResponse(); } @@ -70,7 +70,7 @@ namespace NzbDrone.Api.Queue var resource = Request.Body.FromJson(); var trackedDownload = GetTrackedDownload(resource.Id); - _completedDownloadService.Import(trackedDownload); + _completedDownloadService.Process(trackedDownload); return resource.AsResponse(); } @@ -100,7 +100,7 @@ namespace NzbDrone.Api.Queue throw new NotFoundException(); } - var trackedDownload = _downloadTrackingService.Find(queueItem.TrackingId); + var trackedDownload = _trackedDownloadService.Find(queueItem.TrackingId); if (trackedDownload == null) { diff --git a/src/NzbDrone.Api/Queue/QueueModule.cs b/src/NzbDrone.Api/Queue/QueueModule.cs index 69dc0a9ad..e3de5940e 100644 --- a/src/NzbDrone.Api/Queue/QueueModule.cs +++ b/src/NzbDrone.Api/Queue/QueueModule.cs @@ -9,7 +9,7 @@ using NzbDrone.SignalR; namespace NzbDrone.Api.Queue { public class QueueModule : NzbDroneRestModuleWithSignalR, - IHandle, IHandle + IHandle, IHandle { private readonly IQueueService _queueService; private readonly IPendingReleaseService _pendingReleaseService; @@ -35,7 +35,7 @@ namespace NzbDrone.Api.Queue return queue.Concat(pending); } - public void Handle(UpdateQueueEvent message) + public void Handle(QueueUpdatedEvent message) { BroadcastResourceChange(ModelAction.Sync); } diff --git a/src/NzbDrone.Api/Queue/QueueResource.cs b/src/NzbDrone.Api/Queue/QueueResource.cs index 44f54fbd4..78200089d 100644 --- a/src/NzbDrone.Api/Queue/QueueResource.cs +++ b/src/NzbDrone.Api/Queue/QueueResource.cs @@ -1,10 +1,10 @@ using System; using System.Collections.Generic; using NzbDrone.Api.REST; -using NzbDrone.Core.Download; using NzbDrone.Core.Qualities; using NzbDrone.Api.Series; using NzbDrone.Api.Episodes; +using NzbDrone.Core.Download.TrackedDownloads; namespace NzbDrone.Api.Queue { diff --git a/src/NzbDrone.Common.Test/NzbDrone.Common.Test.csproj b/src/NzbDrone.Common.Test/NzbDrone.Common.Test.csproj index f0c402849..08adf6e38 100644 --- a/src/NzbDrone.Common.Test/NzbDrone.Common.Test.csproj +++ b/src/NzbDrone.Common.Test/NzbDrone.Common.Test.csproj @@ -88,6 +88,7 @@ + diff --git a/src/NzbDrone.Common.Test/TPLTests/DebouncerFixture.cs b/src/NzbDrone.Common.Test/TPLTests/DebouncerFixture.cs new file mode 100644 index 000000000..918409fa4 --- /dev/null +++ b/src/NzbDrone.Common.Test/TPLTests/DebouncerFixture.cs @@ -0,0 +1,69 @@ +using System; +using System.Threading; +using FluentAssertions; +using NUnit.Framework; +using NzbDrone.Common.TPL; + +namespace NzbDrone.Common.Test.TPLTests +{ + [TestFixture] + public class DebouncerFixture + { + public class Counter + { + public int Count { get; private set; } + + public void Hit() + { + Count++; + } + } + + + [Test] + public void should_hold_the_call_for_debounce_duration() + { + var counter = new Counter(); + var debounceFunction = new Debouncer(counter.Hit, TimeSpan.FromMilliseconds(50)); + + debounceFunction.Execute(); + debounceFunction.Execute(); + debounceFunction.Execute(); + + counter.Count.Should().Be(0); + + + Thread.Sleep(100); + + counter.Count.Should().Be(1); + + } + + [Test] + public void should_throttle_cals() + { + var counter = new Counter(); + var debounceFunction = new Debouncer(counter.Hit, TimeSpan.FromMilliseconds(50)); + + debounceFunction.Execute(); + debounceFunction.Execute(); + debounceFunction.Execute(); + + counter.Count.Should().Be(0); + + + Thread.Sleep(200); + + debounceFunction.Execute(); + debounceFunction.Execute(); + debounceFunction.Execute(); + + Thread.Sleep(200); + + counter.Count.Should().Be(2); + + } + + + } +} \ No newline at end of file diff --git a/src/NzbDrone.Common/NzbDrone.Common.csproj b/src/NzbDrone.Common/NzbDrone.Common.csproj index 4aa1ce5e5..da7170735 100644 --- a/src/NzbDrone.Common/NzbDrone.Common.csproj +++ b/src/NzbDrone.Common/NzbDrone.Common.csproj @@ -182,6 +182,7 @@ + diff --git a/src/NzbDrone.Common/TPL/Debouncer.cs b/src/NzbDrone.Common/TPL/Debouncer.cs new file mode 100644 index 000000000..328d79aa8 --- /dev/null +++ b/src/NzbDrone.Common/TPL/Debouncer.cs @@ -0,0 +1,28 @@ +using System; + +namespace NzbDrone.Common.TPL +{ + public class Debouncer + { + private readonly Action _action; + private readonly System.Timers.Timer _timer; + + public Debouncer(Action action, TimeSpan debounceDuration) + { + _action = action; + _timer = new System.Timers.Timer(debounceDuration.TotalMilliseconds); + _timer.Elapsed += timer_Elapsed; + } + + void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) + { + _timer.Stop(); + _action(); + } + + public void Execute() + { + _timer.Start(); + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Core.Test/Blacklisting/BlacklistServiceFixture.cs b/src/NzbDrone.Core.Test/Blacklisting/BlacklistServiceFixture.cs index e3cfd0654..9f88fd939 100644 --- a/src/NzbDrone.Core.Test/Blacklisting/BlacklistServiceFixture.cs +++ b/src/NzbDrone.Core.Test/Blacklisting/BlacklistServiceFixture.cs @@ -24,20 +24,12 @@ namespace NzbDrone.Core.Test.Blacklisting Quality = new QualityModel(Quality.Bluray720p), SourceTitle = "series.title.s01e01", DownloadClient = "SabnzbdClient", - DownloadClientId = "Sabnzbd_nzo_2dfh73k" + DownloadId = "Sabnzbd_nzo_2dfh73k" }; _event.Data.Add("publishedDate", DateTime.UtcNow.ToString("s") + "Z"); } - [Test] - public void should_trigger_redownload() - { - Subject.Handle(_event); - - Mocker.GetMock() - .Verify(v => v.Redownload(_event.SeriesId, _event.EpisodeIds), Times.Once()); - } [Test] public void should_add_to_repository() diff --git a/src/NzbDrone.Core.Test/Datastore/Migration/072_history_grabIdFixture.cs b/src/NzbDrone.Core.Test/Datastore/Migration/072_history_grabIdFixture.cs new file mode 100644 index 000000000..9a85f3e26 --- /dev/null +++ b/src/NzbDrone.Core.Test/Datastore/Migration/072_history_grabIdFixture.cs @@ -0,0 +1,108 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using FluentAssertions; +using FluentMigrator; +using NUnit.Framework; +using NzbDrone.Common.Serializer; +using NzbDrone.Core.Datastore.Migration; +using NzbDrone.Core.History; +using NzbDrone.Core.Test.Framework; + +namespace NzbDrone.Core.Test.Datastore.Migration +{ + [TestFixture] + public class history_downloadIdFixture : MigrationTest + { + [Test] + public void should_move_grab_id_from_date_to_columns() + { + WithTestDb(c => + { + InsertHistory(c, new Dictionary + { + {"indexer","test"}, + {"downloadClientId","123"} + }); + + InsertHistory(c, new Dictionary + { + {"indexer","test"}, + {"downloadClientId","abc"} + }); + + }); + + var allProfiles = Mocker.Resolve().All().ToList(); + + allProfiles.Should().HaveCount(2); + allProfiles.Should().NotContain(c => c.Data.ContainsKey("downloadClientId")); + allProfiles.Should().Contain(c => c.DownloadId == "123"); + allProfiles.Should().Contain(c => c.DownloadId == "abc"); + } + + + [Test] + public void should_leave_items_with_no_grabid() + { + WithTestDb(c => + { + InsertHistory(c, new Dictionary + { + {"indexer","test"}, + {"downloadClientId","123"} + }); + + InsertHistory(c, new Dictionary + { + {"indexer","test"} + }); + + }); + + var allProfiles = Mocker.Resolve().All().ToList(); + + allProfiles.Should().HaveCount(2); + allProfiles.Should().NotContain(c => c.Data.ContainsKey("downloadClientId")); + allProfiles.Should().Contain(c => c.DownloadId == "123"); + allProfiles.Should().Contain(c => c.DownloadId == null); + } + + [Test] + public void should_leave_other_data() + { + WithTestDb(c => + { + InsertHistory(c, new Dictionary + { + {"indexer","test"}, + {"group","test2"}, + {"downloadClientId","123"} + }); + }); + + var allProfiles = Mocker.Resolve().All().Single(); + + allProfiles.Data.Should().NotContainKey("downloadClientId"); + allProfiles.Data.Should().Contain(new KeyValuePair("indexer", "test")); + allProfiles.Data.Should().Contain(new KeyValuePair("group", "test2")); + + allProfiles.DownloadId.Should().Be("123"); + } + + + private void InsertHistory(MigrationBase migrationBase, Dictionary data) + { + migrationBase.Insert.IntoTable("History").Row(new + { + EpisodeId = 1, + SeriesId = 1, + SourceTitle = "Test", + Date = DateTime.Now, + Quality = "{}", + Data = data.ToJson(), + EventType = 1 + }); + } + } +} diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/NotInQueueSpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/NotInQueueSpecificationFixture.cs index 8689d1817..9991b9c53 100644 --- a/src/NzbDrone.Core.Test/DecisionEngineTests/NotInQueueSpecificationFixture.cs +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/NotInQueueSpecificationFixture.cs @@ -1,12 +1,14 @@ using System.Collections.Generic; +using System.Linq; using FizzWare.NBuilder; using FluentAssertions; using NUnit.Framework; using NzbDrone.Core.DecisionEngine.Specifications; -using NzbDrone.Core.Download; +using NzbDrone.Core.Download.TrackedDownloads; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Profiles; using NzbDrone.Core.Qualities; +using NzbDrone.Core.Queue; using NzbDrone.Core.Tv; using NzbDrone.Core.Test.Framework; @@ -47,33 +49,27 @@ namespace NzbDrone.Core.Test.DecisionEngineTests _remoteEpisode = Builder.CreateNew() .With(r => r.Series = _series) .With(r => r.Episodes = new List { _episode }) - .With(r => r.ParsedEpisodeInfo = new ParsedEpisodeInfo { Quality = new QualityModel(Quality.DVD)}) + .With(r => r.ParsedEpisodeInfo = new ParsedEpisodeInfo { Quality = new QualityModel(Quality.DVD) }) .Build(); } private void GivenEmptyQueue() { - Mocker.GetMock() - .Setup(s => s.GetQueuedDownloads()) - .Returns(new TrackedDownload[0]); + Mocker.GetMock() + .Setup(s => s.GetQueue()) + .Returns(new List()); } - private void GivenQueue(IEnumerable remoteEpisodes, TrackedDownloadState state = TrackedDownloadState.Downloading) + private void GivenQueue(IEnumerable remoteEpisodes) { - var queue = new List(); - - foreach (var remoteEpisode in remoteEpisodes) + var queue = remoteEpisodes.Select(remoteEpisode => new Queue.Queue { - queue.Add(new TrackedDownload - { - State = state, - RemoteEpisode = remoteEpisode - }); - } - - Mocker.GetMock() - .Setup(s => s.GetQueuedDownloads()) - .Returns(queue.ToArray()); + RemoteEpisode = remoteEpisode + }); + + Mocker.GetMock() + .Setup(s => s.GetQueue()) + .Returns(queue.ToList()); } [Test] @@ -95,22 +91,6 @@ namespace NzbDrone.Core.Test.DecisionEngineTests Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeTrue(); } - [Test] - public void should_return_true_when_download_is_failed() - { - var remoteEpisode = Builder.CreateNew() - .With(r => r.Series = _series) - .With(r => r.Episodes = new List { _episode }) - .With(r => r.ParsedEpisodeInfo = new ParsedEpisodeInfo - { - Quality = new QualityModel(Quality.DVD) - }) - .Build(); - - GivenQueue(new List { remoteEpisode }, TrackedDownloadState.DownloadFailed); - - Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeTrue(); - } [Test] public void should_return_true_when_quality_in_queue_is_lower() @@ -241,9 +221,9 @@ namespace NzbDrone.Core.Test.DecisionEngineTests Quality.HDTV720p) }) .TheFirst(1) - .With(r => r.Episodes = new List {_episode}) + .With(r => r.Episodes = new List { _episode }) .TheNext(1) - .With(r => r.Episodes = new List {_otherEpisode}) + .With(r => r.Episodes = new List { _otherEpisode }) .Build(); _remoteEpisode.Episodes.Add(_otherEpisode); diff --git a/src/NzbDrone.Core.Test/Download/CompletedDownloadServiceFixture.cs b/src/NzbDrone.Core.Test/Download/CompletedDownloadServiceFixture.cs index 8c35576b8..b90b36948 100644 --- a/src/NzbDrone.Core.Test/Download/CompletedDownloadServiceFixture.cs +++ b/src/NzbDrone.Core.Test/Download/CompletedDownloadServiceFixture.cs @@ -1,560 +1,225 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.IO; +using System.Collections.Generic; using FizzWare.NBuilder; +using FluentAssertions; using Moq; using NUnit.Framework; using NzbDrone.Common.Disk; using NzbDrone.Core.Configuration; using NzbDrone.Core.Download; +using NzbDrone.Core.Download.TrackedDownloads; using NzbDrone.Core.History; -using NzbDrone.Core.IndexerSearch.Definitions; -using NzbDrone.Core.Parser; -using NzbDrone.Core.Test.Framework; using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MediaFiles.EpisodeImport; +using NzbDrone.Core.Messaging.Events; +using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Tv; using NzbDrone.Test.Common; -using NzbDrone.Core.Parser.Model; namespace NzbDrone.Core.Test.Download { [TestFixture] - public class CompletedDownloadServiceFixture : CoreTest + public class CompletedDownloadServiceFixture : CoreTest { - private List _completed; + private TrackedDownload _trackedDownload; [SetUp] public void Setup() { - _completed = Builder.CreateListOfSize(1) - .All() + var completed = Builder.CreateNew() .With(h => h.Status = DownloadItemStatus.Completed) .With(h => h.OutputPath = new OsPath(@"C:\DropFolder\MyDownload".AsOsAgnostic())) .With(h => h.Title = "Drone.S01E01.HDTV") - .Build() - .ToList(); + .Build(); var remoteEpisode = new RemoteEpisode { Series = new Series(), - Episodes = new List {new Episode {Id = 1}} + Episodes = new List { new Episode { Id = 1 } } }; - - Mocker.GetMock() - .Setup(c => c.GetDownloadClients()) - .Returns( new[] { Mocker.GetMock().Object }); - - Mocker.GetMock() - .SetupGet(c => c.Definition) - .Returns(new DownloadClientDefinition { Id = 1, Name = "testClient" }); - - Mocker.GetMock() - .SetupGet(s => s.EnableCompletedDownloadHandling) - .Returns(true); - Mocker.GetMock() - .SetupGet(s => s.RemoveCompletedDownloads) - .Returns(true); - - Mocker.GetMock() - .Setup(s => s.Failed()) - .Returns(new List()); + _trackedDownload = Builder.CreateNew() + .With(c => c.State = TrackedDownloadStage.Downloading) + .With(c => c.DownloadItem = completed) + .With(c => c.RemoteEpisode = remoteEpisode) + .Build(); - Mocker.GetMock() - .Setup(s => s.Map(It.IsAny(), It.IsAny(), It.IsAny>())) - .Returns(remoteEpisode); - Mocker.GetMock() - .Setup(s => s.Map(It.IsAny(), It.IsAny(), (SearchCriteriaBase)null)) - .Returns(remoteEpisode); - - Mocker.SetConstant(Mocker.Resolve()); - } + Mocker.GetMock() + .SetupGet(c => c.Definition) + .Returns(new DownloadClientDefinition { Id = 1, Name = "testClient" }); - private void GivenNoGrabbedHistory() - { - Mocker.GetMock() - .Setup(s => s.Grabbed()) - .Returns(new List()); - } + Mocker.GetMock() + .Setup(c => c.Get(It.IsAny())) + .Returns(Mocker.GetMock().Object); - private void GivenGrabbedHistory(List history) - { Mocker.GetMock() - .Setup(s => s.Grabbed()) - .Returns(history); - } + .Setup(s => s.MostRecentForDownloadId(_trackedDownload.DownloadItem.DownloadId)) + .Returns(new History.History()); - private void GivenNoImportedHistory() - { - Mocker.GetMock() - .Setup(s => s.Imported()) - .Returns(new List()); } - private void GivenImportedHistory(List importedHistory) + private void GivenNoGrabbedHistory() { Mocker.GetMock() - .Setup(s => s.Imported()) - .Returns(importedHistory); + .Setup(s => s.MostRecentForDownloadId(_trackedDownload.DownloadItem.DownloadId)) + .Returns((History.History)null); } - private void GivenCompletedDownloadClientHistory(bool hasStorage = true) - { - Mocker.GetMock() - .Setup(s => s.GetItems()) - .Returns(_completed); - - Mocker.GetMock() - .Setup(c => c.FolderExists(It.IsAny())) - .Returns(hasStorage); - } - private void GivenCompletedImport() + private void GivenSuccessfulImport() { Mocker.GetMock() - .Setup(v => v.ProcessFolder(It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(v => v.ProcessPath(It.IsAny(), It.IsAny())) .Returns(new List { new ImportResult(new ImportDecision(new LocalEpisode() { Path = @"C:\TestPath\Droned.S01E01.mkv" })) }); } - private void GivenFailedImport() - { - Mocker.GetMock() - .Setup(v => v.ProcessFolder(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(new List() - { - new ImportResult(new ImportDecision(new LocalEpisode() { Path = @"C:\TestPath\Droned.S01E01.mkv" }, "Test Failure")) - }); - } - - private void VerifyNoImports() - { - Mocker.GetMock() - .Verify(v => v.ProcessFolder(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never()); - } - - private void VerifyImports() - { - Mocker.GetMock() - .Verify(v => v.ProcessFolder(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once()); - } - [Test] - public void should_process_if_matching_history_is_not_found_but_category_specified() + [TestCase(DownloadItemStatus.Downloading)] + [TestCase(DownloadItemStatus.Failed)] + [TestCase(DownloadItemStatus.Queued)] + [TestCase(DownloadItemStatus.Paused)] + [TestCase(DownloadItemStatus.Warning)] + public void should_not_process_if_download_status_isnt_completed(DownloadItemStatus status) { - _completed.First().Category = "tv"; + _trackedDownload.DownloadItem.Status = status; - GivenCompletedDownloadClientHistory(); - GivenNoGrabbedHistory(); - GivenNoImportedHistory(); - GivenCompletedImport(); - - Subject.Execute(new CheckForFinishedDownloadCommand()); + Subject.Process(_trackedDownload); - VerifyImports(); + AssertNoAttemptedImport(); } + [Test] public void should_not_process_if_matching_history_is_not_found_and_no_category_specified() { - _completed.First().Category = null; - - GivenCompletedDownloadClientHistory(); + _trackedDownload.DownloadItem.Category = null; GivenNoGrabbedHistory(); - GivenNoImportedHistory(); - - Subject.Execute(new CheckForFinishedDownloadCommand()); - - VerifyNoImports(); - ExceptionVerification.ExpectedWarns(1); - } - - [Test] - public void should_not_process_if_grabbed_history_contains_null_downloadclient_id() - { - _completed.First().Category = null; - - GivenCompletedDownloadClientHistory(); - - var historyGrabbed = Builder.CreateListOfSize(1) - .Build() - .ToList(); - - historyGrabbed.First().Data.Add("downloadClient", "SabnzbdClient"); - historyGrabbed.First().Data.Add("downloadClientId", null); - - GivenGrabbedHistory(historyGrabbed); - GivenNoImportedHistory(); - GivenFailedImport(); - - Subject.Execute(new CheckForFinishedDownloadCommand()); - - VerifyNoImports(); - ExceptionVerification.ExpectedWarns(1); - } - - [Test] - public void should_process_if_failed_history_contains_null_downloadclient_id() - { - GivenCompletedDownloadClientHistory(); - - var historyGrabbed = Builder.CreateListOfSize(1) - .Build() - .ToList(); - - historyGrabbed.First().Data.Add("downloadClient", "SabnzbdClient"); - historyGrabbed.First().Data.Add("downloadClientId", _completed.First().DownloadClientId); - - GivenGrabbedHistory(historyGrabbed); - - var historyImported = Builder.CreateListOfSize(1) - .Build() - .ToList(); - - historyImported.First().Data.Add("downloadClient", "SabnzbdClient"); - historyImported.First().Data.Add("downloadClientId", null); - - GivenImportedHistory(historyImported); - GivenCompletedImport(); - - Subject.Execute(new CheckForFinishedDownloadCommand()); - - VerifyImports(); - } - - [Test] - public void should_not_process_if_already_added_to_history_as_imported() - { - GivenCompletedDownloadClientHistory(); - - var history = Builder.CreateListOfSize(1) - .Build() - .ToList(); - - GivenGrabbedHistory(history); - GivenImportedHistory(history); - - history.First().Data.Add("downloadClient", "SabnzbdClient"); - history.First().Data.Add("downloadClientId", _completed.First().DownloadClientId); - Subject.Execute(new CheckForFinishedDownloadCommand()); + Subject.Process(_trackedDownload); - VerifyNoImports(); + AssertNoAttemptedImport(); } [Test] - public void should_process_if_not_already_in_imported_history() + public void should_process_if_matching_history_is_not_found_but_category_specified() { - GivenCompletedDownloadClientHistory(); - - var history = Builder.CreateListOfSize(1) - .Build() - .ToList(); - - GivenGrabbedHistory(history); - GivenNoImportedHistory(); - GivenCompletedImport(); - - history.First().Data.Add("downloadClient", "SabnzbdClient"); - history.First().Data.Add("downloadClientId", _completed.First().DownloadClientId); + _trackedDownload.DownloadItem.Category = "tv"; + GivenNoGrabbedHistory(); + GivenSuccessfulImport(); - Subject.Execute(new CheckForFinishedDownloadCommand()); + Subject.Process(_trackedDownload); - VerifyImports(); + AssertCompletedDownload(); } - [Test] - public void should_not_process_if_storage_directory_does_not_exist() - { - GivenCompletedDownloadClientHistory(false); - - var history = Builder.CreateListOfSize(1) - .Build() - .ToList(); - - GivenGrabbedHistory(history); - GivenNoImportedHistory(); - - history.First().Data.Add("downloadClient", "SabnzbdClient"); - history.First().Data.Add("downloadClientId", _completed.First().DownloadClientId); - Subject.Execute(new CheckForFinishedDownloadCommand()); - - VerifyNoImports(); - - ExceptionVerification.IgnoreErrors(); - } [Test] public void should_not_process_if_storage_directory_in_drone_factory() { - GivenCompletedDownloadClientHistory(true); - - var history = Builder.CreateListOfSize(1) - .Build() - .ToList(); - - GivenGrabbedHistory(history); - GivenNoImportedHistory(); - Mocker.GetMock() .SetupGet(v => v.DownloadedEpisodesFolder) .Returns(@"C:\DropFolder".AsOsAgnostic()); - history.First().Data.Add("downloadClient", "SabnzbdClient"); - history.First().Data.Add("downloadClientId", _completed.First().DownloadClientId); - - Subject.Execute(new CheckForFinishedDownloadCommand()); - - VerifyNoImports(); - - ExceptionVerification.IgnoreWarns(); - } - - [Test] - public void should_process_as_already_imported_if_drone_factory_import_history_exists() - { - GivenCompletedDownloadClientHistory(false); - - _completed.Clear(); - _completed.AddRange(Builder.CreateListOfSize(2) - .All() - .With(h => h.Status = DownloadItemStatus.Completed) - .With(h => h.OutputPath = new OsPath(@"C:\DropFolder\MyDownload".AsOsAgnostic())) - .With(h => h.Title = "Drone.S01E01.HDTV") - .Build()); - - var grabbedHistory = Builder.CreateListOfSize(2) - .All() - .With(d => d.Data["downloadClient"] = "SabnzbdClient") - .TheFirst(1) - .With(d => d.Data["downloadClientId"] = _completed.First().DownloadClientId) - .With(d => d.SourceTitle = "Droned.S01E01.720p-LAZY") - .TheLast(1) - .With(d => d.Data["downloadClientId"] = _completed.Last().DownloadClientId) - .With(d => d.SourceTitle = "Droned.S01E01.Proper.720p-LAZY") - .Build() - .ToList(); - - var importedHistory = Builder.CreateListOfSize(2) - .All() - .With(d => d.EpisodeId = 1) - .TheFirst(1) - .With(d => d.Data["droppedPath"] = @"C:\mydownload\Droned.S01E01.720p-LAZY\lzy-dr101.mkv".AsOsAgnostic()) - .TheLast(1) - .With(d => d.Data["droppedPath"] = @"C:\mydownload\Droned.S01E01.Proper.720p-LAZY\lzy-dr101.mkv".AsOsAgnostic()) - .Build() - .ToList(); - - GivenGrabbedHistory(grabbedHistory); - GivenImportedHistory(importedHistory); - - Subject.Execute(new CheckForFinishedDownloadCommand()); - - VerifyNoImports(); - - Mocker.GetMock() - .Verify(v => v.UpdateHistoryData(It.IsAny(), It.IsAny>()), Times.Exactly(2)); - } - - [Test] - public void should_not_remove_if_config_disabled() - { - GivenCompletedDownloadClientHistory(); - - var history = Builder.CreateListOfSize(1) - .Build() - .ToList(); + _trackedDownload.DownloadItem.OutputPath = new OsPath(@"C:\DropFolder\SomeOtherFolder".AsOsAgnostic()); - GivenGrabbedHistory(history); - GivenNoImportedHistory(); - GivenCompletedImport(); + Subject.Process(_trackedDownload); - history.First().Data.Add("downloadClient", "SabnzbdClient"); - history.First().Data.Add("downloadClientId", _completed.First().DownloadClientId); - - Mocker.GetMock() - .SetupGet(s => s.RemoveCompletedDownloads) - .Returns(false); - - Subject.Execute(new CheckForFinishedDownloadCommand()); - - Mocker.GetMock() - .Verify(c => c.DeleteFolder(It.IsAny(), true), Times.Never()); + AssertNoAttemptedImport(); } - [Test] - public void should_not_remove_while_readonly() - { - GivenCompletedDownloadClientHistory(); - - var history = Builder.CreateListOfSize(1) - .Build() - .ToList(); - - GivenGrabbedHistory(history); - GivenNoImportedHistory(); - GivenCompletedImport(); - - _completed.First().IsReadOnly = true; - - history.First().Data.Add("downloadClient", "SabnzbdClient"); - history.First().Data.Add("downloadClientId", _completed.First().DownloadClientId); - - Subject.Execute(new CheckForFinishedDownloadCommand()); - - Mocker.GetMock() - .Verify(c => c.DeleteFolder(It.IsAny(), true), Times.Never()); - } [Test] - public void should_not_remove_if_imported_failed() + public void should_not_process_if_output_path_is_empty() { - GivenCompletedDownloadClientHistory(); - - var history = Builder.CreateListOfSize(1) - .Build() - .ToList(); - - GivenGrabbedHistory(history); - GivenNoImportedHistory(); - GivenFailedImport(); + _trackedDownload.DownloadItem.OutputPath = new OsPath(); - _completed.First().IsReadOnly = true; + Subject.Process(_trackedDownload); - history.First().Data.Add("downloadClient", "SabnzbdClient"); - history.First().Data.Add("downloadClientId", _completed.First().DownloadClientId); - - Subject.Execute(new CheckForFinishedDownloadCommand()); - - Mocker.GetMock() - .Verify(c => c.DeleteFolder(It.IsAny(), true), Times.Never()); - - ExceptionVerification.IgnoreErrors(); + AssertNoAttemptedImport(); } - [Test] - public void should_remove_if_imported() - { - GivenCompletedDownloadClientHistory(); - - var history = Builder.CreateListOfSize(1) - .Build() - .ToList(); - - GivenGrabbedHistory(history); - GivenNoImportedHistory(); - GivenCompletedImport(); - - history.First().Data.Add("downloadClient", "SabnzbdClient"); - history.First().Data.Add("downloadClientId", _completed.First().DownloadClientId); - - Subject.Execute(new CheckForFinishedDownloadCommand()); - - Mocker.GetMock() - .Verify(c => c.DeleteFolder(It.IsAny(), true), Times.Once()); - } [Test] public void should_not_mark_as_imported_if_all_files_were_rejected() { - GivenCompletedDownloadClientHistory(); - - var history = Builder.CreateListOfSize(1) - .Build() - .ToList(); - - GivenGrabbedHistory(history); - GivenNoImportedHistory(); - Mocker.GetMock() - .Setup(v => v.ProcessFolder(It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(v => v.ProcessPath(It.IsAny(), It.IsAny())) .Returns(new List { - new ImportResult( - new ImportDecision(new LocalEpisode() {Path = @"C:\TestPath\Droned.S01E01.mkv"}, "Rejected!"), - "Test Failure") + new ImportResult(new ImportDecision(new LocalEpisode {Path = @"C:\TestPath\Droned.S01E01.mkv"}, "Rejected!"),"Test Failure"), + new ImportResult(new ImportDecision(new LocalEpisode {Path = @"C:\TestPath\Droned.S01E02.mkv"}, "Rejected!"),"Test Failure") }); - history.First().Data.Add("downloadClient", "SabnzbdClient"); - history.First().Data.Add("downloadClientId", _completed.First().DownloadClientId); + Subject.Process(_trackedDownload); - Subject.Execute(new CheckForFinishedDownloadCommand()); - Mocker.GetMock() - .Verify(c => c.DeleteFolder(It.IsAny(), true), Times.Never()); + _trackedDownload.State.Should().NotBe(TrackedDownloadStage.Imported); - ExceptionVerification.ExpectedErrors(1); + AssertNoCompletedDownload(); } [Test] public void should_not_mark_as_imported_if_all_files_were_skipped() { - GivenCompletedDownloadClientHistory(); - - var history = Builder.CreateListOfSize(1) - .Build() - .ToList(); - - GivenGrabbedHistory(history); - GivenNoImportedHistory(); - Mocker.GetMock() - .Setup(v => v.ProcessFolder(It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(v => v.ProcessPath(It.IsAny(), It.IsAny())) .Returns(new List { - new ImportResult( - new ImportDecision(new LocalEpisode() {Path = @"C:\TestPath\Droned.S01E01.mkv"}), - "Test Failure") + new ImportResult(new ImportDecision(new LocalEpisode {Path = @"C:\TestPath\Droned.S01E01.mkv"}),"Test Failure"), + new ImportResult(new ImportDecision(new LocalEpisode {Path = @"C:\TestPath\Droned.S01E01.mkv"}),"Test Failure") }); - history.First().Data.Add("downloadClient", "SabnzbdClient"); - history.First().Data.Add("downloadClientId", _completed.First().DownloadClientId); - - Subject.Execute(new CheckForFinishedDownloadCommand()); - Mocker.GetMock() - .Verify(c => c.DeleteFolder(It.IsAny(), true), Times.Never()); + Subject.Process(_trackedDownload); - ExceptionVerification.ExpectedErrors(1); + AssertNoCompletedDownload(); } [Test] public void should_not_mark_as_imported_if_some_files_were_skipped() { - GivenCompletedDownloadClientHistory(); - - var history = Builder.CreateListOfSize(1) - .Build() - .ToList(); - - GivenGrabbedHistory(history); - GivenNoImportedHistory(); - Mocker.GetMock() - .Setup(v => v.ProcessFolder(It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(v => v.ProcessPath(It.IsAny(), It.IsAny())) .Returns(new List { - new ImportResult(new ImportDecision(new LocalEpisode() {Path = @"C:\TestPath\Droned.S01E01.mkv"})), - new ImportResult( - new ImportDecision(new LocalEpisode() {Path = @"C:\TestPath\Droned.S01E01.mkv"}), - "Test Failure") + new ImportResult(new ImportDecision(new LocalEpisode {Path = @"C:\TestPath\Droned.S01E01.mkv"})), + new ImportResult(new ImportDecision(new LocalEpisode{Path = @"C:\TestPath\Droned.S01E01.mkv"}),"Test Failure") }); - history.First().Data.Add("downloadClient", "SabnzbdClient"); - history.First().Data.Add("downloadClientId", _completed.First().DownloadClientId); - Subject.Execute(new CheckForFinishedDownloadCommand()); + Subject.Process(_trackedDownload); + + AssertNoCompletedDownload(); + } + + + private void AssertNoAttemptedImport() + { + Mocker.GetMock() + .Verify(v => v.ProcessPath(It.IsAny(), It.IsAny()), Times.Never()); + + AssertNoCompletedDownload(); + } + + private void AssertNoCompletedDownload() + { + Mocker.GetMock() + .Verify(v => v.PublishEvent(It.IsAny()), Times.Never()); - Mocker.GetMock() - .Verify(c => c.DeleteFolder(It.IsAny(), true), Times.Never()); + _trackedDownload.State.Should().NotBe(TrackedDownloadStage.Imported); + } - ExceptionVerification.ExpectedErrors(1); + private void AssertCompletedDownload() + { + Mocker.GetMock() + .Verify(v => v.ProcessPath(_trackedDownload.DownloadItem.OutputPath.FullPath, _trackedDownload.DownloadItem), Times.Once()); + + _trackedDownload.State.Should().Be(TrackedDownloadStage.Imported); } } } diff --git a/src/NzbDrone.Core.Test/Download/DownloadClientTests/DownloadClientFixtureBase.cs b/src/NzbDrone.Core.Test/Download/DownloadClientTests/DownloadClientFixtureBase.cs index 095137dcf..58ea65f13 100644 --- a/src/NzbDrone.Core.Test/Download/DownloadClientTests/DownloadClientFixtureBase.cs +++ b/src/NzbDrone.Core.Test/Download/DownloadClientTests/DownloadClientFixtureBase.cs @@ -63,7 +63,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests protected void VerifyIdentifiable(DownloadClientItem downloadClientItem) { downloadClientItem.DownloadClient.Should().Be(Subject.Definition.Name); - downloadClientItem.DownloadClientId.Should().NotBeNullOrEmpty(); + downloadClientItem.DownloadId.Should().NotBeNullOrEmpty(); downloadClientItem.Title.Should().NotBeNullOrEmpty(); } diff --git a/src/NzbDrone.Core.Test/Download/FailedDownloadServiceFixture.cs b/src/NzbDrone.Core.Test/Download/FailedDownloadServiceFixture.cs index e2b99256d..6d2149386 100644 --- a/src/NzbDrone.Core.Test/Download/FailedDownloadServiceFixture.cs +++ b/src/NzbDrone.Core.Test/Download/FailedDownloadServiceFixture.cs @@ -1,15 +1,13 @@ -using System; -using System.Collections.Generic; -using System.Linq; +using System.Collections.Generic; using FizzWare.NBuilder; +using FluentAssertions; using Moq; using NUnit.Framework; -using NzbDrone.Core.Configuration; +using NzbDrone.Common.Disk; using NzbDrone.Core.Download; +using NzbDrone.Core.Download.TrackedDownloads; using NzbDrone.Core.History; -using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.Messaging.Events; -using NzbDrone.Core.Parser; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Tv; @@ -18,28 +16,21 @@ using NzbDrone.Test.Common; namespace NzbDrone.Core.Test.Download { [TestFixture] - public class FailedDownloadServiceFixture : CoreTest + public class FailedDownloadServiceFixture : CoreTest { - private List _completed; - private List _failed; + private TrackedDownload _trackedDownload; + private List _grabHistory; [SetUp] public void Setup() { - _completed = Builder.CreateListOfSize(5) - .All() + var completed = Builder.CreateNew() .With(h => h.Status = DownloadItemStatus.Completed) - .With(h => h.IsEncrypted = false) + .With(h => h.OutputPath = new OsPath(@"C:\DropFolder\MyDownload".AsOsAgnostic())) .With(h => h.Title = "Drone.S01E01.HDTV") - .Build() - .ToList(); + .Build(); - _failed = Builder.CreateListOfSize(1) - .All() - .With(h => h.Status = DownloadItemStatus.Failed) - .With(h => h.Title = "Drone.S01E01.HDTV") - .Build() - .ToList(); + _grabHistory = Builder.CreateListOfSize(2).BuildList(); var remoteEpisode = new RemoteEpisode { @@ -47,410 +38,74 @@ namespace NzbDrone.Core.Test.Download Episodes = new List { new Episode { Id = 1 } } }; - Mocker.GetMock() - .Setup(c => c.GetDownloadClients()) - .Returns( new IDownloadClient[] { Mocker.GetMock().Object }); + _trackedDownload = Builder.CreateNew() + .With(c => c.State = TrackedDownloadStage.Downloading) + .With(c => c.DownloadItem = completed) + .With(c => c.RemoteEpisode = remoteEpisode) + .Build(); - Mocker.GetMock() - .SetupGet(c => c.Definition) - .Returns(new DownloadClientDefinition { Id = 1, Name = "testClient" }); - - Mocker.GetMock() - .SetupGet(s => s.EnableFailedDownloadHandling) - .Returns(true); Mocker.GetMock() - .Setup(s => s.Imported()) - .Returns(new List()); - - Mocker.GetMock() - .Setup(s => s.Map(It.IsAny(), It.IsAny(), It.IsAny>())) - .Returns(remoteEpisode); - - Mocker.GetMock() - .Setup(s => s.Map(It.IsAny(), It.IsAny(), (SearchCriteriaBase)null)) - .Returns(remoteEpisode); + .Setup(s => s.Find(_trackedDownload.DownloadItem.DownloadId, HistoryEventType.Grabbed)) + .Returns(_grabHistory); - Mocker.SetConstant(Mocker.Resolve()); } private void GivenNoGrabbedHistory() { Mocker.GetMock() - .Setup(s => s.Grabbed()) - .Returns(new List()); - } - - private void GivenGrabbedHistory(List history) - { - Mocker.GetMock() - .Setup(s => s.Grabbed()) - .Returns(history); - } - - private void GivenNoFailedHistory() - { - Mocker.GetMock() - .Setup(s => s.Failed()) - .Returns(new List()); - } - - private void GivenFailedHistory(List failedHistory) - { - Mocker.GetMock() - .Setup(s => s.Failed()) - .Returns(failedHistory); - } - - private void GivenFailedDownloadClientHistory() - { - Mocker.GetMock() - .Setup(s => s.GetItems()) - .Returns(_failed); - } - - private void GivenGracePeriod(int hours) - { - Mocker.GetMock().SetupGet(s => s.BlacklistGracePeriod).Returns(hours); - } - - private void GivenRetryLimit(int count, int interval = 5) - { - Mocker.GetMock().SetupGet(s => s.BlacklistRetryLimit).Returns(count); - Mocker.GetMock().SetupGet(s => s.BlacklistRetryInterval).Returns(interval); - } - - private void VerifyNoFailedDownloads() - { - Mocker.GetMock() - .Verify(v => v.PublishEvent(It.IsAny()), Times.Never()); - } - - private void VerifyFailedDownloads(int count = 1) - { - Mocker.GetMock() - .Verify(v => v.PublishEvent(It.Is(d => d.EpisodeIds.Count == count)), Times.Once()); - } - - private void VerifyRetryDownload() - { - Mocker.GetMock() - .Verify(v => v.RetryDownload(It.IsAny()), Times.Once()); - } - - private void VerifyNoRetryDownload() - { - Mocker.GetMock() - .Verify(v => v.RetryDownload(It.IsAny()), Times.Never()); - } - - [Test] - public void should_not_process_if_no_download_client_history() - { - Mocker.GetMock() - .Setup(s => s.GetItems()) - .Returns(new List()); - - Subject.Execute(new CheckForFinishedDownloadCommand()); - - Mocker.GetMock() - .Verify(s => s.BetweenDates(It.IsAny(), It.IsAny(), HistoryEventType.Grabbed), - Times.Never()); - - VerifyNoFailedDownloads(); + .Setup(s => s.Find(_trackedDownload.DownloadItem.DownloadId, HistoryEventType.Grabbed)) + .Returns(new List()); } [Test] - public void should_not_process_if_no_failed_items_in_download_client_history() + public void should_not_fail_if_matching_history_is_not_found() { GivenNoGrabbedHistory(); - GivenNoFailedHistory(); - - Mocker.GetMock() - .Setup(s => s.GetItems()) - .Returns(_completed); - Subject.Execute(new CheckForFinishedDownloadCommand()); + Subject.Process(_trackedDownload); - Mocker.GetMock() - .Verify(s => s.BetweenDates(It.IsAny(), It.IsAny(), HistoryEventType.Grabbed), - Times.Never()); - - VerifyNoFailedDownloads(); + AssertDownloadNotFailed(); } - [Test] - public void should_not_process_if_matching_history_is_not_found() - { - GivenNoGrabbedHistory(); - GivenFailedDownloadClientHistory(); - - Subject.Execute(new CheckForFinishedDownloadCommand()); - - VerifyNoFailedDownloads(); - ExceptionVerification.ExpectedWarns(1); - } [Test] - public void should_not_process_if_grabbed_history_contains_null_downloadclient_id() + public void should_mark_failed_if_encrypted() { - GivenFailedDownloadClientHistory(); - - var historyGrabbed = Builder.CreateListOfSize(1) - .Build() - .ToList(); - - historyGrabbed.First().Data.Add("downloadClient", "SabnzbdClient"); - historyGrabbed.First().Data.Add("downloadClientId", null); + _trackedDownload.DownloadItem.IsEncrypted = true; - GivenGrabbedHistory(historyGrabbed); - GivenNoFailedHistory(); + Subject.Process(_trackedDownload); - Subject.Execute(new CheckForFinishedDownloadCommand()); - - VerifyNoFailedDownloads(); - ExceptionVerification.ExpectedWarns(1); + AssertDownloadFailed(); } - [Test] - public void should_process_if_failed_history_contains_null_downloadclient_id() - { - GivenFailedDownloadClientHistory(); - - var historyGrabbed = Builder.CreateListOfSize(1) - .Build() - .ToList(); - - historyGrabbed.First().Data.Add("downloadClient", "SabnzbdClient"); - historyGrabbed.First().Data.Add("downloadClientId", _failed.First().DownloadClientId); - - GivenGrabbedHistory(historyGrabbed); - - var historyFailed = Builder.CreateListOfSize(1) - .Build() - .ToList(); - - historyFailed.First().Data.Add("downloadClient", "SabnzbdClient"); - historyFailed.First().Data.Add("downloadClientId", null); - - GivenFailedHistory(historyFailed); - - Subject.Execute(new CheckForFinishedDownloadCommand()); - - VerifyFailedDownloads(); - } [Test] - public void should_not_process_if_already_added_to_history_as_failed() + public void should_mark_failed_if_download_item_is_failed() { - GivenFailedDownloadClientHistory(); - - var history = Builder.CreateListOfSize(1) - .Build() - .ToList(); - - GivenGrabbedHistory(history); - GivenFailedHistory(history); - - history.First().Data.Add("downloadClient", "SabnzbdClient"); - history.First().Data.Add("downloadClientId", _failed.First().DownloadClientId); + _trackedDownload.DownloadItem.Status = DownloadItemStatus.Failed; - Subject.Execute(new CheckForFinishedDownloadCommand()); + Subject.Process(_trackedDownload); - VerifyNoFailedDownloads(); + AssertDownloadFailed(); } - [Test] - public void should_process_if_not_already_in_failed_history() - { - GivenFailedDownloadClientHistory(); - var history = Builder.CreateListOfSize(1) - .Build() - .ToList(); - - GivenGrabbedHistory(history); - GivenNoFailedHistory(); - - history.First().Data.Add("downloadClient", "SabnzbdClient"); - history.First().Data.Add("downloadClientId", _failed.First().DownloadClientId); - - Subject.Execute(new CheckForFinishedDownloadCommand()); - - VerifyFailedDownloads(); - } - - [Test] - public void should_have_multiple_episode_ids_when_multi_episode_release_fails() - { - GivenFailedDownloadClientHistory(); - - var history = Builder.CreateListOfSize(2) - .Build() - .ToList(); - - GivenGrabbedHistory(history); - GivenNoFailedHistory(); - - history.ForEach(h => - { - h.Data.Add("downloadClient", "SabnzbdClient"); - h.Data.Add("downloadClientId", _failed.First().DownloadClientId); - }); - - Subject.Execute(new CheckForFinishedDownloadCommand()); - - VerifyFailedDownloads(2); - } - - [Test] - public void should_skip_if_enable_failed_download_handling_is_off() - { - Mocker.GetMock() - .SetupGet(s => s.EnableFailedDownloadHandling) - .Returns(false); - - Subject.Execute(new CheckForFinishedDownloadCommand()); - - VerifyNoFailedDownloads(); - } - - [Test] - public void should_process_if_ageHours_is_not_set() - { - GivenFailedDownloadClientHistory(); - - var historyGrabbed = Builder.CreateListOfSize(1) - .Build() - .ToList(); - - historyGrabbed.First().Data.Add("downloadClient", "SabnzbdClient"); - historyGrabbed.First().Data.Add("downloadClientId", _failed.First().DownloadClientId); - - GivenGrabbedHistory(historyGrabbed); - GivenNoFailedHistory(); - - Subject.Execute(new CheckForFinishedDownloadCommand()); - - VerifyFailedDownloads(); - VerifyNoRetryDownload(); - } - - [Test] - public void should_process_if_age_is_greater_than_grace_period() - { - GivenFailedDownloadClientHistory(); - - var historyGrabbed = Builder.CreateListOfSize(1) - .Build() - .ToList(); - - historyGrabbed.First().Data.Add("downloadClient", "SabnzbdClient"); - historyGrabbed.First().Data.Add("downloadClientId", _failed.First().DownloadClientId); - historyGrabbed.First().Data.Add("ageHours", "48"); - - GivenGrabbedHistory(historyGrabbed); - GivenNoFailedHistory(); - - Subject.Execute(new CheckForFinishedDownloadCommand()); - - VerifyFailedDownloads(); - VerifyNoRetryDownload(); - } - - [Test] - public void should_not_retry_if_already_failed() - { - GivenFailedDownloadClientHistory(); - - var historyGrabbed = Builder.CreateListOfSize(1) - .Build() - .ToList(); - - historyGrabbed.First().Data.Add("downloadClient", "SabnzbdClient"); - historyGrabbed.First().Data.Add("downloadClientId", _failed.First().DownloadClientId); - historyGrabbed.First().Data.Add("ageHours", "1"); - - GivenGrabbedHistory(historyGrabbed); - GivenFailedHistory(historyGrabbed); - GivenGracePeriod(6); - GivenRetryLimit(1); - - Subject.Execute(new CheckForFinishedDownloadCommand()); - - VerifyNoFailedDownloads(); - VerifyNoRetryDownload(); - } - - [Test] - public void should_process_if_retry_count_is_greater_than_grace_period() + private void AssertDownloadNotFailed() { - GivenFailedDownloadClientHistory(); - - var historyGrabbed = Builder.CreateListOfSize(1) - .Build() - .ToList(); - - historyGrabbed.First().Data.Add("downloadClient", "SabnzbdClient"); - historyGrabbed.First().Data.Add("downloadClientId", _failed.First().DownloadClientId); - historyGrabbed.First().Data.Add("ageHours", "48"); - - GivenGrabbedHistory(historyGrabbed); - GivenNoFailedHistory(); - GivenGracePeriod(6); - - Subject.Execute(new CheckForFinishedDownloadCommand()); + Mocker.GetMock() + .Verify(v => v.PublishEvent(It.IsAny()), Times.Never()); - VerifyFailedDownloads(); - VerifyNoRetryDownload(); + _trackedDownload.State.Should().NotBe(TrackedDownloadStage.DownloadFailed); } - [Test] - public void should_not_process_if_age_is_less_than_grace_period() - { - GivenFailedDownloadClientHistory(); - - var historyGrabbed = Builder.CreateListOfSize(1) - .Build() - .ToList(); - - historyGrabbed.First().Data.Add("downloadClient", "SabnzbdClient"); - historyGrabbed.First().Data.Add("downloadClientId", _failed.First().DownloadClientId); - historyGrabbed.First().Data.Add("ageHours", "1"); - - GivenGrabbedHistory(historyGrabbed); - GivenNoFailedHistory(); - GivenGracePeriod(6); - GivenRetryLimit(1); - - Subject.Execute(new CheckForFinishedDownloadCommand()); - - VerifyNoFailedDownloads(); - VerifyNoRetryDownload(); - ExceptionVerification.IgnoreWarns(); - } - - [Test] - public void should_manual_mark_all_episodes_of_release_as_failed() + private void AssertDownloadFailed() { - var historyFailed = Builder.CreateListOfSize(2) - .All() - .With(v => v.EventType == HistoryEventType.Grabbed) - .Do(v => v.Data.Add("downloadClient", "SabnzbdClient")) - .Do(v => v.Data.Add("downloadClientId", "test")) - .Build() - .ToList(); - - GivenGrabbedHistory(historyFailed); - - Mocker.GetMock() - .Setup(s => s.Get(It.IsAny())) - .Returns(i => historyFailed.FirstOrDefault(v => v.Id == i)); - - Subject.MarkAsFailed(1); + Mocker.GetMock() + .Verify(v => v.PublishEvent(It.IsAny()), Times.Once()); - VerifyFailedDownloads(2); + _trackedDownload.State.Should().Be(TrackedDownloadStage.DownloadFailed); } } } diff --git a/src/NzbDrone.Core.Test/HealthCheck/Checks/ImportMechanismCheckFixture.cs b/src/NzbDrone.Core.Test/HealthCheck/Checks/ImportMechanismCheckFixture.cs index f493f8027..5f0f3d9a0 100644 --- a/src/NzbDrone.Core.Test/HealthCheck/Checks/ImportMechanismCheckFixture.cs +++ b/src/NzbDrone.Core.Test/HealthCheck/Checks/ImportMechanismCheckFixture.cs @@ -1,13 +1,9 @@ -using System.Linq; -using System.Collections.Generic; -using FizzWare.NBuilder; -using NUnit.Framework; -using NzbDrone.Test.Common; +using NUnit.Framework; using NzbDrone.Common.Disk; using NzbDrone.Core.Configuration; using NzbDrone.Core.HealthCheck.Checks; using NzbDrone.Core.Test.Framework; -using NzbDrone.Core.Download; +using NzbDrone.Test.Common; namespace NzbDrone.Core.Test.HealthCheck.Checks { @@ -16,7 +12,6 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks { private const string DRONE_FACTORY_FOLDER = @"C:\Test\Unsorted"; - private IList _completed; private void GivenCompletedDownloadHandling(bool? enabled = null) { @@ -30,18 +25,6 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks .SetupGet(s => s.EnableCompletedDownloadHandling) .Returns(enabled.Value); } - - _completed = Builder.CreateListOfSize(1) - .All() - .With(v => v.State == TrackedDownloadState.Downloading) - .With(v => v.DownloadItem = new DownloadClientItem()) - .With(v => v.DownloadItem.Status = DownloadItemStatus.Completed) - .With(v => v.DownloadItem.OutputPath = new OsPath(@"C:\Test\DropFolder\myfile.mkv".AsOsAgnostic())) - .Build(); - - Mocker.GetMock() - .Setup(v => v.GetCompletedDownloads()) - .Returns(_completed.ToArray()); } private void GivenDroneFactoryFolder(bool exists = false) @@ -68,17 +51,6 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks Subject.Check().ShouldBeWarning(); } - - [Test] - public void should_return_warning_when_downloadclient_drops_in_dronefactory_folder() - { - GivenCompletedDownloadHandling(true); - GivenDroneFactoryFolder(true); - - _completed.First().DownloadItem.OutputPath = new OsPath((DRONE_FACTORY_FOLDER + @"\myfile.mkv").AsOsAgnostic()); - - Subject.Check().ShouldBeWarning(); - } [Test] public void should_return_ok_when_no_issues_found() diff --git a/src/NzbDrone.Core.Test/HistoryTests/HistoryRepositoryFixture.cs b/src/NzbDrone.Core.Test/HistoryTests/HistoryRepositoryFixture.cs index 34b6936bb..649c3d499 100644 --- a/src/NzbDrone.Core.Test/HistoryTests/HistoryRepositoryFixture.cs +++ b/src/NzbDrone.Core.Test/HistoryTests/HistoryRepositoryFixture.cs @@ -1,5 +1,4 @@ -using System; -using FizzWare.NBuilder; +using FizzWare.NBuilder; using FluentAssertions; using NUnit.Framework; using NzbDrone.Core.History; @@ -11,25 +10,6 @@ namespace NzbDrone.Core.Test.HistoryTests [TestFixture] public class HistoryRepositoryFixture : DbTest { - [Test] - public void Trim_Items() - { - var historyItem = Builder.CreateListOfSize(30) - .All() - .With(c => c.Id = 0) - .With(c => c.Quality = new QualityModel()) - .TheFirst(10).With(c => c.Date = DateTime.Now) - .TheNext(20).With(c => c.Date = DateTime.Now.AddDays(-31)) - .Build(); - - Db.InsertMany(historyItem); - - AllStoredModels.Should().HaveCount(30); - Subject.Trim(); - - AllStoredModels.Should().HaveCount(10); - AllStoredModels.Should().OnlyContain(s => s.Date > DateTime.Now.AddDays(-30)); - } [Test] public void should_read_write_dictionary() @@ -38,29 +18,37 @@ namespace NzbDrone.Core.Test.HistoryTests .With(c => c.Quality = new QualityModel()) .BuildNew(); - history.Data.Add("key1","value1"); - history.Data.Add("key2","value2"); + history.Data.Add("key1", "value1"); + history.Data.Add("key2", "value2"); Subject.Insert(history); StoredModel.Data.Should().HaveCount(2); } + [Test] - public void grabbed_should_return_grabbed_items() + public void should_get_download_history() { - var history = Builder - .CreateListOfSize(5) - .All() - .With(c => c.Quality = new QualityModel()) - .With(c => c.EventType = HistoryEventType.Unknown) - .Random(3) + var historyBluray = Builder.CreateNew() + .With(c => c.Quality = new QualityModel(Quality.Bluray1080p)) + .With(c => c.SeriesId = 12) .With(c => c.EventType = HistoryEventType.Grabbed) - .BuildListOfNew(); + .BuildNew(); - Subject.InsertMany(history); + var historyDvd = Builder.CreateNew() + .With(c => c.Quality = new QualityModel(Quality.DVD)) + .With(c => c.SeriesId = 12) + .With(c => c.EventType = HistoryEventType.Grabbed) + .BuildNew(); - Subject.Grabbed().Should().HaveCount(3); + Subject.Insert(historyBluray); + Subject.Insert(historyDvd); + + var downloadHistory = Subject.FindDownloadHistory(12, new QualityModel(Quality.Bluray1080p)); + + downloadHistory.Should().HaveCount(1); } + } } \ No newline at end of file diff --git a/src/NzbDrone.Core.Test/HistoryTests/HistoryServiceFixture.cs b/src/NzbDrone.Core.Test/HistoryTests/HistoryServiceFixture.cs index 5545242b2..004863cdf 100644 --- a/src/NzbDrone.Core.Test/HistoryTests/HistoryServiceFixture.cs +++ b/src/NzbDrone.Core.Test/HistoryTests/HistoryServiceFixture.cs @@ -81,7 +81,7 @@ namespace NzbDrone.Core.Test.HistoryTests Path = @"C:\Test\Unsorted\Series.s01e01.mkv" }; - Subject.Handle(new EpisodeImportedEvent(localEpisode, episodeFile, true)); + Subject.Handle(new EpisodeImportedEvent(localEpisode, episodeFile, true, "sab","abcd")); Mocker.GetMock() .Verify(v => v.Insert(It.Is(h => h.SourceTitle == Path.GetFileNameWithoutExtension(localEpisode.Path)))); diff --git a/src/NzbDrone.Core.Test/MediaFiles/DownloadedEpisodesCommandServiceFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/DownloadedEpisodesCommandServiceFixture.cs index 328a5b549..e56a430f4 100644 --- a/src/NzbDrone.Core.Test/MediaFiles/DownloadedEpisodesCommandServiceFixture.cs +++ b/src/NzbDrone.Core.Test/MediaFiles/DownloadedEpisodesCommandServiceFixture.cs @@ -9,7 +9,6 @@ using NzbDrone.Core.Download; using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MediaFiles.Commands; using NzbDrone.Core.MediaFiles.EpisodeImport; -using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Test.Framework; using NzbDrone.Test.Common; @@ -19,9 +18,7 @@ namespace NzbDrone.Core.Test.MediaFiles public class DownloadedEpisodesCommandServiceFixture : CoreTest { private string _droneFactory = "c:\\drop\\".AsOsAgnostic(); - private string _downloadFolder = "c:\\drop_other\\Show.S01E01\\".AsOsAgnostic(); - private TrackedDownload _trackedDownload; [SetUp] public void Setup() @@ -39,56 +36,7 @@ namespace NzbDrone.Core.Test.MediaFiles Mocker.GetMock() .Setup(v => v.ProcessFolder(It.IsAny(), It.IsAny())) .Returns(new List()); - - var downloadItem = Builder.CreateNew() - .With(v => v.DownloadClientId = "sab1") - .With(v => v.Status = DownloadItemStatus.Downloading) - .Build(); - - _trackedDownload = new TrackedDownload - { - DownloadItem = downloadItem, - State = TrackedDownloadState.Downloading - }; - } - - private void GivenValidQueueItem() - { - var downloadItem = Builder.CreateNew() - .With(v => v.DownloadClientId = "sab1") - .With(v => v.Status = DownloadItemStatus.Downloading) - .Build(); - - Mocker.GetMock() - .Setup(s => s.GetQueuedDownloads()) - .Returns(new [] { _trackedDownload }); - } - - private void GivenSuccessfulImport() - { - Mocker.GetMock() - .Setup(v => v.ProcessFolder(It.IsAny(), It.IsAny())) - .Returns(new List() { - new ImportResult(new ImportDecision(new LocalEpisode() { Path = @"C:\TestPath\Droned.S01E01.mkv" })) - }); - } - - private void GivenRejectedImport() - { - Mocker.GetMock() - .Setup(v => v.ProcessFolder(It.IsAny(), It.IsAny())) - .Returns(new List() { - new ImportResult(new ImportDecision(new LocalEpisode() { Path = @"C:\TestPath\Droned.S01E01.mkv" }, "Some Rejection"), "I was rejected") - }); - } - - private void GivenSkippedImport() - { - Mocker.GetMock() - .Setup(v => v.ProcessFolder(It.IsAny(), It.IsAny())) - .Returns(new List() { - new ImportResult(new ImportDecision(new LocalEpisode() { Path = @"C:\TestPath\Droned.S01E01.mkv" }), "I was skipped") - }); + } [Test] @@ -110,41 +58,6 @@ namespace NzbDrone.Core.Test.MediaFiles ExceptionVerification.ExpectedWarns(1); } - - [Test] - public void should_ignore_downloadclientid_if_path_is_not_specified() - { - Subject.Execute(new DownloadedEpisodesScanCommand() { DownloadClientId = "sab1" }); - - Mocker.GetMock().Verify(c => c.ProcessRootFolder(It.IsAny()), Times.Once()); - } - - [Test] - public void should_process_folder_if_downloadclientid_is_not_specified() - { - Subject.Execute(new DownloadedEpisodesScanCommand() { Path = _downloadFolder }); - - Mocker.GetMock().Verify(c => c.ProcessFolder(It.IsAny(), null), Times.Once()); - } - - [Test] - public void should_process_folder_with_downloadclientitem_if_available() - { - GivenValidQueueItem(); - - Subject.Execute(new DownloadedEpisodesScanCommand() { Path = _downloadFolder, DownloadClientId = "sab1" }); - - Mocker.GetMock().Verify(c => c.Import(It.Is(v => v != null), _downloadFolder), Times.Once()); - } - - [Test] - public void should_process_folder_without_downloadclientitem_if_not_available() - { - Subject.Execute(new DownloadedEpisodesScanCommand() { Path = _downloadFolder, DownloadClientId = "sab1" }); - - Mocker.GetMock().Verify(c => c.ProcessFolder(It.IsAny(), null), Times.Once()); - - ExceptionVerification.ExpectedWarns(1); - } + } } \ No newline at end of file diff --git a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index 5e711b73c..e94b8cd7e 100644 --- a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -117,6 +117,7 @@ + diff --git a/src/NzbDrone.Core/Blacklisting/BlacklistService.cs b/src/NzbDrone.Core/Blacklisting/BlacklistService.cs index b7eae3da4..f72a349c5 100644 --- a/src/NzbDrone.Core/Blacklisting/BlacklistService.cs +++ b/src/NzbDrone.Core/Blacklisting/BlacklistService.cs @@ -11,7 +11,7 @@ namespace NzbDrone.Core.Blacklisting { public interface IBlacklistService { - bool Blacklisted(int seriesId,string sourceTitle, DateTime publishedDate); + bool Blacklisted(int seriesId, string sourceTitle, DateTime publishedDate); PagingSpec Paged(PagingSpec pagingSpec); void Delete(int id); } @@ -19,16 +19,13 @@ namespace NzbDrone.Core.Blacklisting public class BlacklistService : IBlacklistService, IExecute, IHandle, - IHandle + IHandleAsync { private readonly IBlacklistRepository _blacklistRepository; - private readonly IRedownloadFailedDownloads _redownloadFailedDownloadService; - public BlacklistService(IBlacklistRepository blacklistRepository, - IRedownloadFailedDownloads redownloadFailedDownloadService) + public BlacklistService(IBlacklistRepository blacklistRepository) { _blacklistRepository = blacklistRepository; - _redownloadFailedDownloadService = redownloadFailedDownloadService; } public bool Blacklisted(int seriesId, string sourceTitle, DateTime publishedDate) @@ -48,7 +45,7 @@ namespace NzbDrone.Core.Blacklisting _blacklistRepository.Delete(id); } - private bool HasSamePublishedDate(Blacklist item, DateTime publishedDate) + private static bool HasSamePublishedDate(Blacklist item, DateTime publishedDate) { if (!item.PublishedDate.HasValue) return true; @@ -70,15 +67,13 @@ namespace NzbDrone.Core.Blacklisting SourceTitle = message.SourceTitle, Quality = message.Quality, Date = DateTime.UtcNow, - PublishedDate = DateTime.Parse(message.Data.GetValueOrDefault("publishedDate", null)) + PublishedDate = DateTime.Parse(message.Data.GetValueOrDefault("publishedDate")) }; _blacklistRepository.Insert(blacklist); - - _redownloadFailedDownloadService.Redownload(message.SeriesId, message.EpisodeIds); } - public void Handle(SeriesDeletedEvent message) + public void HandleAsync(SeriesDeletedEvent message) { var blacklisted = _blacklistRepository.BlacklistedBySeries(message.Series.Id); diff --git a/src/NzbDrone.Core/Configuration/ConfigService.cs b/src/NzbDrone.Core/Configuration/ConfigService.cs index 9f6a0ab66..6d8b7d257 100644 --- a/src/NzbDrone.Core/Configuration/ConfigService.cs +++ b/src/NzbDrone.Core/Configuration/ConfigService.cs @@ -124,7 +124,7 @@ namespace NzbDrone.Core.Configuration public Boolean EnableCompletedDownloadHandling { - get { return GetValueBoolean("EnableCompletedDownloadHandling", false); } + get { return GetValueBoolean("EnableCompletedDownloadHandling", true); } set { SetValue("EnableCompletedDownloadHandling", value); } } @@ -136,13 +136,6 @@ namespace NzbDrone.Core.Configuration set { SetValue("RemoveCompletedDownloads", value); } } - public Boolean EnableFailedDownloadHandling - { - get { return GetValueBoolean("EnableFailedDownloadHandling", true); } - - set { SetValue("EnableFailedDownloadHandling", value); } - } - public Boolean AutoRedownloadFailed { get { return GetValueBoolean("AutoRedownloadFailed", true); } @@ -157,27 +150,6 @@ namespace NzbDrone.Core.Configuration set { SetValue("RemoveFailedDownloads", value); } } - public Int32 BlacklistGracePeriod - { - get { return GetValueInt("BlacklistGracePeriod", 2); } - - set { SetValue("BlacklistGracePeriod", value); } - } - - public Int32 BlacklistRetryInterval - { - get { return GetValueInt("BlacklistRetryInterval", 60); } - - set { SetValue("BlacklistRetryInterval", value); } - } - - public Int32 BlacklistRetryLimit - { - get { return GetValueInt("BlacklistRetryLimit", 1); } - - set { SetValue("BlacklistRetryLimit", value); } - } - public Boolean CreateEmptySeriesFolders { get { return GetValueBoolean("CreateEmptySeriesFolders", false); } diff --git a/src/NzbDrone.Core/Configuration/IConfigService.cs b/src/NzbDrone.Core/Configuration/IConfigService.cs index 423c7fd96..53927c0b7 100644 --- a/src/NzbDrone.Core/Configuration/IConfigService.cs +++ b/src/NzbDrone.Core/Configuration/IConfigService.cs @@ -22,12 +22,8 @@ namespace NzbDrone.Core.Configuration Boolean EnableCompletedDownloadHandling { get; set; } Boolean RemoveCompletedDownloads { get; set; } - Boolean EnableFailedDownloadHandling { get; set; } Boolean AutoRedownloadFailed { get; set; } Boolean RemoveFailedDownloads { get; set; } - Int32 BlacklistGracePeriod { get; set; } - Int32 BlacklistRetryInterval { get; set; } - Int32 BlacklistRetryLimit { get; set; } //Media Management Boolean AutoUnmonitorPreviouslyDownloadedEpisodes { get; set; } diff --git a/src/NzbDrone.Core/Datastore/Migration/072_history_grabid.cs b/src/NzbDrone.Core/Datastore/Migration/072_history_grabid.cs new file mode 100644 index 000000000..a296fd219 --- /dev/null +++ b/src/NzbDrone.Core/Datastore/Migration/072_history_grabid.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using FluentMigrator; +using NzbDrone.Common.Extensions; +using NzbDrone.Common.Serializer; +using NzbDrone.Core.Datastore.Migration.Framework; + +namespace NzbDrone.Core.Datastore.Migration +{ + [Migration(72)] + public class history_downloadId : NzbDroneMigrationBase + { + protected override void MainDbUpgrade() + { + Alter.Table("History") + .AddColumn("DownloadId").AsString() + .Nullable() + .Indexed(); + + Execute.WithConnection(MoveToColumn); + } + + private void MoveToColumn(IDbConnection conn, IDbTransaction tran) + { + using (IDbCommand getHistory = conn.CreateCommand()) + { + getHistory.Transaction = tran; + getHistory.CommandText = @"SELECT Id, Data FROM History WHERE Data LIKE '%downloadClientId%'"; + + using (var historyReader = getHistory.ExecuteReader()) + { + while (historyReader.Read()) + { + var id = historyReader.GetInt32(0); + var data = historyReader.GetString(1); + + UpdateHistory(tran, conn, id, data); + } + } + } + } + + private void UpdateHistory(IDbTransaction tran, IDbConnection conn, int id, string data) + { + var dic = Json.Deserialize>(data); + + var downloadId = dic["downloadClientId"]; + dic.Remove("downloadClientId"); + + using (var updateHistoryCmd = conn.CreateCommand()) + { + updateHistoryCmd.Transaction = tran; + updateHistoryCmd.CommandText = @"UPDATE History SET DownloadId = ? , Data = ? WHERE Id = ?"; + + updateHistoryCmd.AddParameter(downloadId); + updateHistoryCmd.AddParameter(dic.ToJson()); + updateHistoryCmd.AddParameter(id); + + updateHistoryCmd.ExecuteNonQuery(); + + } + } + } +} diff --git a/src/NzbDrone.Core/DecisionEngine/Decision.cs b/src/NzbDrone.Core/DecisionEngine/Decision.cs index 4f3199079..a4402588a 100644 --- a/src/NzbDrone.Core/DecisionEngine/Decision.cs +++ b/src/NzbDrone.Core/DecisionEngine/Decision.cs @@ -7,12 +7,14 @@ namespace NzbDrone.Core.DecisionEngine public Boolean Accepted { get; private set; } public String Reason { get; private set; } + private static readonly Decision AcceptDecision = new Decision { Accepted = true }; + private Decision() + { + } + public static Decision Accept() { - return new Decision - { - Accepted = true - }; + return AcceptDecision; } public static Decision Reject(String reason, params object[] args) diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/BlacklistSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/BlacklistSpecification.cs index 38db97b71..0bb86450e 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/BlacklistSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/BlacklistSpecification.cs @@ -1,6 +1,6 @@ using NLog; using NzbDrone.Core.Blacklisting; -using NzbDrone.Core.Configuration; +using NzbDrone.Core.Indexers; using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.Parser.Model; @@ -9,13 +9,11 @@ namespace NzbDrone.Core.DecisionEngine.Specifications public class BlacklistSpecification : IDecisionEngineSpecification { private readonly IBlacklistService _blacklistService; - private readonly IConfigService _configService; private readonly Logger _logger; - public BlacklistSpecification(IBlacklistService blacklistService, IConfigService configService, Logger logger) + public BlacklistSpecification(IBlacklistService blacklistService, Logger logger) { _blacklistService = blacklistService; - _configService = configService; _logger = logger; } @@ -23,12 +21,12 @@ namespace NzbDrone.Core.DecisionEngine.Specifications public Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) { - if (!_configService.EnableFailedDownloadHandling) + if (subject.Release.DownloadProtocol == DownloadProtocol.Torrent) { - _logger.Debug("Failed Download Handling is not enabled"); return Decision.Accept(); } + if (_blacklistService.Blacklisted(subject.Series.Id, subject.Release.Title, subject.Release.PublishDate)) { _logger.Debug("{0} is blacklisted, rejecting.", subject.Release.Title); diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/NotInQueueSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/NotInQueueSpecification.cs index 29c187c68..3fa9915ca 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/NotInQueueSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/NotInQueueSpecification.cs @@ -1,21 +1,21 @@ using System.Collections.Generic; using System.Linq; using NLog; -using NzbDrone.Core.Download; using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Qualities; +using NzbDrone.Core.Queue; namespace NzbDrone.Core.DecisionEngine.Specifications { public class NotInQueueSpecification : IDecisionEngineSpecification { - private readonly IDownloadTrackingService _downloadTrackingService; + private readonly IQueueService _queueService; private readonly Logger _logger; - public NotInQueueSpecification(IDownloadTrackingService downloadTrackingService, Logger logger) + public NotInQueueSpecification(IQueueService queueService, Logger logger) { - _downloadTrackingService = downloadTrackingService; + _queueService = queueService; _logger = logger; } @@ -23,8 +23,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications public Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) { - var queue = _downloadTrackingService.GetQueuedDownloads() - .Where(v => v.State == TrackedDownloadState.Downloading) + var queue = _queueService.GetQueue() .Select(q => q.RemoteEpisode).ToList(); if (IsInQueue(subject, queue)) @@ -36,9 +35,9 @@ namespace NzbDrone.Core.DecisionEngine.Specifications return Decision.Accept(); } - private bool IsInQueue(RemoteEpisode newEpisode, IEnumerable queue) + private bool IsInQueue(RemoteEpisode newEpisode, IEnumerable episodesInQueue) { - var matchingSeries = queue.Where(q => q.Series.Id == newEpisode.Series.Id); + var matchingSeries = episodesInQueue.Where(q => q.Series.Id == newEpisode.Series.Id); var matchingSeriesAndQuality = matchingSeries.Where(q => new QualityModelComparer(q.Series.Profile).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()); diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/RetrySpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/RetrySpecification.cs deleted file mode 100644 index 667890734..000000000 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/RetrySpecification.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System; -using System.Linq; -using NLog; -using NzbDrone.Common.Extensions; -using NzbDrone.Core.Configuration; -using NzbDrone.Core.History; -using NzbDrone.Core.IndexerSearch.Definitions; -using NzbDrone.Core.Parser.Model; - -namespace NzbDrone.Core.DecisionEngine.Specifications -{ - public class RetrySpecification : IDecisionEngineSpecification - { - private readonly IHistoryService _historyService; - private readonly IConfigService _configService; - private readonly Logger _logger; - - public RetrySpecification(IHistoryService historyService, IConfigService configService, Logger logger) - { - _historyService = historyService; - _configService = configService; - _logger = logger; - } - - public RejectionType Type { get { return RejectionType.Permanent; } } - - public Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) - { - if (!_configService.EnableFailedDownloadHandling) - { - _logger.Debug("Failed Download Handling is not enabled"); - return Decision.Accept(); - } - - var history = _historyService.FindBySourceTitle(subject.Release.Title); - - if (history.Count(h => h.EventType == HistoryEventType.Grabbed && - HasSamePublishedDate(h, subject.Release.PublishDate)) > - _configService.BlacklistRetryLimit) - { - _logger.Debug("Release has been attempted more times than allowed, rejecting"); - return Decision.Reject("Retried too many times"); - } - - return Decision.Accept(); - } - - private bool HasSamePublishedDate(History.History item, DateTime publishedDate) - { - DateTime itemsPublishedDate; - - if (!DateTime.TryParse(item.Data.GetValueOrDefault("PublishedDate", null), out itemsPublishedDate)) return true; - - return itemsPublishedDate.AddDays(-2) <= publishedDate && itemsPublishedDate.AddDays(2) >= publishedDate; - } - } -} diff --git a/src/NzbDrone.Core/Download/Clients/Deluge/Deluge.cs b/src/NzbDrone.Core/Download/Clients/Deluge/Deluge.cs index ffc86addc..52bc34961 100644 --- a/src/NzbDrone.Core/Download/Clients/Deluge/Deluge.cs +++ b/src/NzbDrone.Core/Download/Clients/Deluge/Deluge.cs @@ -103,12 +103,11 @@ namespace NzbDrone.Core.Download.Clients.Deluge foreach (var torrent in torrents) { var item = new DownloadClientItem(); - item.DownloadClientId = torrent.Hash.ToUpper(); + item.DownloadId = torrent.Hash.ToUpper(); item.Title = torrent.Name; item.Category = Settings.TvCategory; item.DownloadClient = Definition.Name; - item.DownloadTime = TimeSpan.FromSeconds(torrent.SecondsDownloading); var outputPath = _remotePathMappingService.RemapRemoteToLocal(Settings.Host, new OsPath(torrent.DownloadPath)); item.OutputPath = outputPath + torrent.Name; diff --git a/src/NzbDrone.Core/Download/Clients/Deluge/DelugeProxy.cs b/src/NzbDrone.Core/Download/Clients/Deluge/DelugeProxy.cs index 970af4939..723c988cc 100644 --- a/src/NzbDrone.Core/Download/Clients/Deluge/DelugeProxy.cs +++ b/src/NzbDrone.Core/Download/Clients/Deluge/DelugeProxy.cs @@ -283,7 +283,7 @@ namespace NzbDrone.Core.Download.Clients.Deluge if (resultHosts.Result != null) { // The returned list contains the id, ip, port and status of each available connection. We want the 127.0.0.1 - var connection = resultHosts.Result.Where(v => "127.0.0.1" == (v[1] as String)).FirstOrDefault(); + var connection = resultHosts.Result.FirstOrDefault(v => "127.0.0.1" == (v[1] as String)); if (connection != null) { diff --git a/src/NzbDrone.Core/Download/Clients/Nzbget/Nzbget.cs b/src/NzbDrone.Core/Download/Clients/Nzbget/Nzbget.cs index 95f3804be..ac2001f04 100644 --- a/src/NzbDrone.Core/Download/Clients/Nzbget/Nzbget.cs +++ b/src/NzbDrone.Core/Download/Clients/Nzbget/Nzbget.cs @@ -69,10 +69,11 @@ namespace NzbDrone.Core.Download.Clients.Nzbget var droneParameter = item.Parameters.SingleOrDefault(p => p.Name == "drone"); var queueItem = new DownloadClientItem(); - queueItem.DownloadClientId = droneParameter == null ? item.NzbId.ToString() : droneParameter.Value.ToString(); + queueItem.DownloadId = droneParameter == null ? item.NzbId.ToString() : droneParameter.Value.ToString(); queueItem.Title = item.NzbName; queueItem.TotalSize = totalSize; queueItem.Category = item.Category; + queueItem.DownloadClient = Definition.Name; if (globalStatus.DownloadPaused || remainingSize == pausedSize) { @@ -128,7 +129,7 @@ namespace NzbDrone.Core.Download.Clients.Nzbget var historyItem = new DownloadClientItem(); historyItem.DownloadClient = Definition.Name; - historyItem.DownloadClientId = droneParameter == null ? item.Id.ToString() : droneParameter.Value.ToString(); + historyItem.DownloadId = droneParameter == null ? item.Id.ToString() : droneParameter.Value.ToString(); historyItem.Title = item.Name; historyItem.TotalSize = MakeInt64(item.FileSizeHi, item.FileSizeLo); historyItem.OutputPath = _remotePathMappingService.RemapRemoteToLocal(Settings.Host, new OsPath(item.DestDir)); @@ -181,13 +182,7 @@ namespace NzbDrone.Core.Download.Clients.Nzbget { MigrateLocalCategoryPath(); - foreach (var downloadClientItem in GetQueue().Concat(GetHistory())) - { - if (downloadClientItem.Category == Settings.TvCategory) - { - yield return downloadClientItem; - } - } + return GetQueue().Concat(GetHistory()).Where(downloadClientItem => downloadClientItem.Category == Settings.TvCategory); } public override void RemoveItem(String id) diff --git a/src/NzbDrone.Core/Download/Clients/Pneumatic/Pneumatic.cs b/src/NzbDrone.Core/Download/Clients/Pneumatic/Pneumatic.cs index 78a497c53..add632462 100644 --- a/src/NzbDrone.Core/Download/Clients/Pneumatic/Pneumatic.cs +++ b/src/NzbDrone.Core/Download/Clients/Pneumatic/Pneumatic.cs @@ -84,7 +84,7 @@ namespace NzbDrone.Core.Download.Clients.Pneumatic var historyItem = new DownloadClientItem { DownloadClient = Definition.Name, - DownloadClientId = GetDownloadClientId(file), + DownloadId = GetDownloadClientId(file), Title = title, TotalSize = _diskProvider.GetFileSize(file), diff --git a/src/NzbDrone.Core/Download/Clients/Sabnzbd/Sabnzbd.cs b/src/NzbDrone.Core/Download/Clients/Sabnzbd/Sabnzbd.cs index f0a9db29a..f86b4ee41 100644 --- a/src/NzbDrone.Core/Download/Clients/Sabnzbd/Sabnzbd.cs +++ b/src/NzbDrone.Core/Download/Clients/Sabnzbd/Sabnzbd.cs @@ -67,7 +67,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd { var queueItem = new DownloadClientItem(); queueItem.DownloadClient = Definition.Name; - queueItem.DownloadClientId = sabQueueItem.Id; + queueItem.DownloadId = sabQueueItem.Id; queueItem.Category = sabQueueItem.Category; queueItem.Title = sabQueueItem.Title; queueItem.TotalSize = (long)(sabQueueItem.Size * 1024 * 1024); @@ -122,13 +122,12 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd var historyItem = new DownloadClientItem { DownloadClient = Definition.Name, - DownloadClientId = sabHistoryItem.Id, + DownloadId = sabHistoryItem.Id, Category = sabHistoryItem.Category, Title = sabHistoryItem.Title, TotalSize = sabHistoryItem.Size, RemainingSize = 0, - DownloadTime = TimeSpan.FromSeconds(sabHistoryItem.DownloadTime), RemainingTime = TimeSpan.Zero, Message = sabHistoryItem.FailMessage @@ -193,7 +192,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd public override void RemoveItem(String id) { - if (GetQueue().Any(v => v.DownloadClientId == id)) + if (GetQueue().Any(v => v.DownloadId == id)) { _proxy.RemoveFrom("queue", id, Settings); } @@ -209,7 +208,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd // Check both the queue and history because sometimes SAB keeps item in history to retry post processing (depends on failure reason) var currentHistory = GetHistory().ToList(); - var currentHistoryItems = currentHistory.Where(v => v.DownloadClientId == id).ToList(); + var currentHistoryItems = currentHistory.Where(v => v.DownloadId == id).ToList(); if (currentHistoryItems.Count != 1) { @@ -219,7 +218,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd var currentHistoryItem = currentHistoryItems.First(); var otherItemsWithSameTitle = currentHistory.Where(h => h.Title == currentHistoryItem.Title && - h.DownloadClientId != currentHistoryItem.DownloadClientId).ToList(); + h.DownloadId != currentHistoryItem.DownloadId).ToList(); var newId = _proxy.RetryDownload(id, Settings); @@ -235,17 +234,17 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd var history = GetHistory().Where(v => v.Category == currentHistoryItem.Category && v.Title == currentHistoryItem.Title && - !otherItemsWithSameTitle.Select(h => h.DownloadClientId) - .Contains(v.DownloadClientId)).ToList(); + !otherItemsWithSameTitle.Select(h => h.DownloadId) + .Contains(v.DownloadId)).ToList(); if (queue.Count == 1) { - return queue.First().DownloadClientId; + return queue.First().DownloadId; } if (history.Count == 1) { - return history.First().DownloadClientId; + return history.First().DownloadId; } if (queue.Count > 1 || history.Count > 1) diff --git a/src/NzbDrone.Core/Download/Clients/TorrentBlackhole/TorrentBlackhole.cs b/src/NzbDrone.Core/Download/Clients/TorrentBlackhole/TorrentBlackhole.cs index 04774f381..cdee8dd18 100644 --- a/src/NzbDrone.Core/Download/Clients/TorrentBlackhole/TorrentBlackhole.cs +++ b/src/NzbDrone.Core/Download/Clients/TorrentBlackhole/TorrentBlackhole.cs @@ -70,7 +70,7 @@ namespace NzbDrone.Core.Download.Clients.TorrentBlackhole var historyItem = new DownloadClientItem { DownloadClient = Definition.Name, - DownloadClientId = Definition.Name + "_" + Path.GetFileName(folder) + "_" + _diskProvider.FolderGetCreationTime(folder).Ticks, + DownloadId = Definition.Name + "_" + Path.GetFileName(folder) + "_" + _diskProvider.FolderGetCreationTime(folder).Ticks, Category = "nzbdrone", Title = title, @@ -100,7 +100,7 @@ namespace NzbDrone.Core.Download.Clients.TorrentBlackhole var historyItem = new DownloadClientItem { DownloadClient = Definition.Name, - DownloadClientId = Definition.Name + "_" + Path.GetFileName(videoFile) + "_" + _diskProvider.FileGetLastWrite(videoFile).Ticks, + DownloadId = Definition.Name + "_" + Path.GetFileName(videoFile) + "_" + _diskProvider.FileGetLastWrite(videoFile).Ticks, Category = "nzbdrone", Title = title, diff --git a/src/NzbDrone.Core/Download/Clients/Transmission/Transmission.cs b/src/NzbDrone.Core/Download/Clients/Transmission/Transmission.cs index 2eb1e42d9..7d0fb661e 100644 --- a/src/NzbDrone.Core/Download/Clients/Transmission/Transmission.cs +++ b/src/NzbDrone.Core/Download/Clients/Transmission/Transmission.cs @@ -102,12 +102,11 @@ namespace NzbDrone.Core.Download.Clients.Transmission } var item = new DownloadClientItem(); - item.DownloadClientId = torrent.HashString.ToUpper(); + item.DownloadId = torrent.HashString.ToUpper(); item.Category = Settings.TvCategory; item.Title = torrent.Name; item.DownloadClient = Definition.Name; - item.DownloadTime = TimeSpan.FromSeconds(torrent.SecondsDownloading); item.OutputPath = outputPath + torrent.Name; item.RemainingSize = torrent.LeftUntilDone; diff --git a/src/NzbDrone.Core/Download/Clients/UsenetBlackhole/UsenetBlackhole.cs b/src/NzbDrone.Core/Download/Clients/UsenetBlackhole/UsenetBlackhole.cs index 032bfc2ea..11ca1843e 100644 --- a/src/NzbDrone.Core/Download/Clients/UsenetBlackhole/UsenetBlackhole.cs +++ b/src/NzbDrone.Core/Download/Clients/UsenetBlackhole/UsenetBlackhole.cs @@ -68,7 +68,7 @@ namespace NzbDrone.Core.Download.Clients.UsenetBlackhole var historyItem = new DownloadClientItem { DownloadClient = Definition.Name, - DownloadClientId = Definition.Name + "_" + Path.GetFileName(folder) + "_" + _diskProvider.FolderGetCreationTime(folder).Ticks, + DownloadId = Definition.Name + "_" + Path.GetFileName(folder) + "_" + _diskProvider.FolderGetCreationTime(folder).Ticks, Category = "nzbdrone", Title = title, @@ -98,7 +98,7 @@ namespace NzbDrone.Core.Download.Clients.UsenetBlackhole var historyItem = new DownloadClientItem { DownloadClient = Definition.Name, - DownloadClientId = Definition.Name + "_" + Path.GetFileName(videoFile) + "_" + _diskProvider.FileGetLastWrite(videoFile).Ticks, + DownloadId = Definition.Name + "_" + Path.GetFileName(videoFile) + "_" + _diskProvider.FileGetLastWrite(videoFile).Ticks, Category = "nzbdrone", Title = title, diff --git a/src/NzbDrone.Core/Download/Clients/uTorrent/UTorrent.cs b/src/NzbDrone.Core/Download/Clients/uTorrent/UTorrent.cs index 13c3088f7..6aad6070a 100644 --- a/src/NzbDrone.Core/Download/Clients/uTorrent/UTorrent.cs +++ b/src/NzbDrone.Core/Download/Clients/uTorrent/UTorrent.cs @@ -89,7 +89,7 @@ namespace NzbDrone.Core.Download.Clients.UTorrent } var item = new DownloadClientItem(); - item.DownloadClientId = torrent.Hash; + item.DownloadId = torrent.Hash; item.Title = torrent.Name; item.TotalSize = torrent.Size; item.Category = torrent.Label; diff --git a/src/NzbDrone.Core/Download/CompletedDownloadService.cs b/src/NzbDrone.Core/Download/CompletedDownloadService.cs index 9b15bd390..6b8226f13 100644 --- a/src/NzbDrone.Core/Download/CompletedDownloadService.cs +++ b/src/NzbDrone.Core/Download/CompletedDownloadService.cs @@ -1,244 +1,98 @@ -using System; -using System.Collections.Generic; +using System.IO; using System.Linq; using NLog; using NzbDrone.Common.Disk; using NzbDrone.Common.Extensions; using NzbDrone.Core.Configuration; +using NzbDrone.Core.Download.TrackedDownloads; using NzbDrone.Core.History; using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MediaFiles.EpisodeImport; -using System.IO; +using NzbDrone.Core.Messaging.Events; namespace NzbDrone.Core.Download { public interface ICompletedDownloadService { - void CheckForCompletedItem(IDownloadClient downloadClient, TrackedDownload trackedDownload, List grabbedHistory, List importedHistory); - List Import(TrackedDownload trackedDownload, String overrideOutputPath = null); + void Process(TrackedDownload trackedDownload); } public class CompletedDownloadService : ICompletedDownloadService { private readonly IConfigService _configService; - private readonly IDiskProvider _diskProvider; - private readonly IDownloadedEpisodesImportService _downloadedEpisodesImportService; + private readonly IEventAggregator _eventAggregator; private readonly IHistoryService _historyService; - private readonly Logger _logger; + private readonly IDownloadedEpisodesImportService _downloadedEpisodesImportService; public CompletedDownloadService(IConfigService configService, - IDiskProvider diskProvider, - IDownloadedEpisodesImportService downloadedEpisodesImportService, + IEventAggregator eventAggregator, IHistoryService historyService, - Logger logger) + IDownloadedEpisodesImportService downloadedEpisodesImportService) { _configService = configService; - _diskProvider = diskProvider; - _downloadedEpisodesImportService = downloadedEpisodesImportService; + _eventAggregator = eventAggregator; _historyService = historyService; - _logger = logger; - } - - private List GetHistoryItems(List grabbedHistory, string downloadClientId) - { - return grabbedHistory.Where(h => downloadClientId.Equals(h.Data.GetValueOrDefault(DownloadTrackingService.DOWNLOAD_CLIENT_ID))) - .ToList(); + _downloadedEpisodesImportService = downloadedEpisodesImportService; } - public void CheckForCompletedItem(IDownloadClient downloadClient, TrackedDownload trackedDownload, List grabbedHistory, List importedHistory) + public void Process(TrackedDownload trackedDownload) { - if (!_configService.EnableCompletedDownloadHandling) + if (trackedDownload.DownloadItem.Status != DownloadItemStatus.Completed) { return; } - if (trackedDownload.DownloadItem.Status == DownloadItemStatus.Completed && trackedDownload.State == TrackedDownloadState.Downloading) - { - var grabbedItems = GetHistoryItems(grabbedHistory, trackedDownload.DownloadItem.DownloadClientId); - - if (!grabbedItems.Any() && trackedDownload.DownloadItem.Category.IsNullOrWhiteSpace()) - { - UpdateStatusMessage(trackedDownload, LogLevel.Warn, "Download wasn't grabbed by drone or not in a category, ignoring download."); - return; - } - - var importedItems = GetHistoryItems(importedHistory, trackedDownload.DownloadItem.DownloadClientId); - - if (importedItems.Any()) - { - trackedDownload.State = TrackedDownloadState.Imported; - - UpdateStatusMessage(trackedDownload, LogLevel.Debug, "Already added to history as imported."); - } - - else if (trackedDownload.Status != TrackedDownloadStatus.Ok) - { - _logger.Debug("Tracked download status is: {0}, skipping import. {1}", trackedDownload.Status, - String.Join(". ", trackedDownload.StatusMessages)); - return; - } - - else - { - var downloadedEpisodesFolder = new OsPath(_configService.DownloadedEpisodesFolder); - var downloadItemOutputPath = trackedDownload.DownloadItem.OutputPath; - - if (downloadItemOutputPath.IsEmpty) - { - UpdateStatusMessage(trackedDownload, LogLevel.Warn, "Download doesn't contain intermediate path, ignoring download."); - return; - } - - if (!downloadedEpisodesFolder.IsEmpty && downloadedEpisodesFolder.Contains(downloadItemOutputPath)) - { - UpdateStatusMessage(trackedDownload, LogLevel.Warn, "Intermediate Download path inside drone factory, ignoring download."); - return; - } - - var importResults = Import(trackedDownload); - - //Only attempt to associate it with a previous import if its still in the downloading state - if (trackedDownload.State == TrackedDownloadState.Downloading && importResults.Empty()) - { - AssociateWithPreviouslyImported(trackedDownload, grabbedItems, importedHistory); - } - } - } + var historyItem = _historyService.MostRecentForDownloadId(trackedDownload.DownloadItem.DownloadId); - if (_configService.RemoveCompletedDownloads) + if (historyItem == null && trackedDownload.DownloadItem.Category.IsNullOrWhiteSpace()) { - RemoveCompleted(trackedDownload, downloadClient); + trackedDownload.Warn("Download wasn't grabbed by Sonarr and not in a category, Skipping."); + return; } - } - public List Import(TrackedDownload trackedDownload, String overrideOutputPath = null) - { - var importResults = new List(); - var outputPath = overrideOutputPath ?? trackedDownload.DownloadItem.OutputPath.FullPath; + var downloadItemOutputPath = trackedDownload.DownloadItem.OutputPath; - if (_diskProvider.FolderExists(outputPath)) + if (downloadItemOutputPath.IsEmpty) { - importResults = _downloadedEpisodesImportService.ProcessFolder(new DirectoryInfo(outputPath), trackedDownload.RemoteEpisode.Series, trackedDownload.DownloadItem); - - ProcessImportResults(trackedDownload, outputPath, importResults); + trackedDownload.Warn("Download doesn't contain intermediate path, Skipping."); + return; } - else if (_diskProvider.FileExists(outputPath)) + var downloadedEpisodesFolder = new OsPath(_configService.DownloadedEpisodesFolder); + if (downloadedEpisodesFolder.Contains(downloadItemOutputPath)) { - importResults = _downloadedEpisodesImportService.ProcessFile(new FileInfo(outputPath), trackedDownload.RemoteEpisode.Series, trackedDownload.DownloadItem); - - ProcessImportResults(trackedDownload, outputPath, importResults); + trackedDownload.Warn("Intermediate Download path inside drone factory, Skipping."); + return; } - return importResults; + Import(trackedDownload); } - private void UpdateStatusMessage(TrackedDownload trackedDownload, LogLevel logLevel, String message, params object[] args) + private void Import(TrackedDownload trackedDownload) { - var statusMessage = String.Format(message, args); - var logMessage = String.Format("[{0}] {1}", trackedDownload.DownloadItem.Title, statusMessage); - - if (trackedDownload.StatusMessage != statusMessage) - { - trackedDownload.SetStatusLevel(logLevel); - trackedDownload.StatusMessage = statusMessage; - _logger.Log(logLevel, logMessage); - } - else - { - _logger.Debug(logMessage); - } - } + var outputPath = trackedDownload.DownloadItem.OutputPath.FullPath; + var importResults = _downloadedEpisodesImportService.ProcessPath(outputPath, trackedDownload.DownloadItem); - private void ProcessImportResults(TrackedDownload trackedDownload, String outputPath, List importResults) - { if (importResults.Empty()) { - UpdateStatusMessage(trackedDownload, LogLevel.Error, "No files found are eligible for import in {0}", outputPath); + trackedDownload.Warn("No files found are eligible for import in {0}", outputPath); + return; } - else if (importResults.Any(v => v.Result == ImportResultType.Imported) && importResults.All(v => v.Result == ImportResultType.Imported || v.Result == ImportResultType.Rejected)) - { - UpdateStatusMessage(trackedDownload, LogLevel.Info, "Imported {0} files.", importResults.Count(v => v.Result == ImportResultType.Imported)); - trackedDownload.State = TrackedDownloadState.Imported; - } - else + if (importResults.Any(c => c.Result != ImportResultType.Imported)) { - var errors = importResults - .Where(v => v.Result == ImportResultType.Skipped || v.Result == ImportResultType.Rejected) - .Select(v => v.Errors.Aggregate(Path.GetFileName(v.ImportDecision.LocalEpisode.Path), (a, r) => a + "\r\n- " + r)) - .Aggregate("Failed to import:", (a, r) => a + "\r\n" + r); - - trackedDownload.StatusMessages = importResults.Where(v => v.Result == ImportResultType.Skipped || v.Result == ImportResultType.Rejected) - .Select(v => new TrackedDownloadStatusMessage(Path.GetFileName(v.ImportDecision.LocalEpisode.Path), v.Errors)).ToList(); + var statusMessages = importResults + .Where(v => v.Result != ImportResultType.Imported) + .Select(v => new TrackedDownloadStatusMessage(Path.GetFileName(v.ImportDecision.LocalEpisode.Path), v.Errors)) + .ToArray(); - UpdateStatusMessage(trackedDownload, LogLevel.Error, errors); - } - } - - private void AssociateWithPreviouslyImported(TrackedDownload trackedDownload, List grabbedItems, List importedHistory) - { - if (grabbedItems.Any()) - { - var episodeIds = trackedDownload.RemoteEpisode.Episodes.Select(v => v.Id).ToList(); - - // Check if we can associate it with a previous drone factory import. - var importedItems = importedHistory.Where(v => v.Data.GetValueOrDefault(DownloadTrackingService.DOWNLOAD_CLIENT_ID) == null && - episodeIds.Contains(v.EpisodeId) && - v.Data.GetValueOrDefault("droppedPath") != null && - new FileInfo(v.Data["droppedPath"]).Directory.Name == grabbedItems.First().SourceTitle - ).ToList(); - if (importedItems.Count == 1) - { - var importedFile = new FileInfo(importedItems.First().Data["droppedPath"]); - - if (importedFile.Directory.Name == grabbedItems.First().SourceTitle) - { - trackedDownload.State = TrackedDownloadState.Imported; - - importedItems.First().Data[DownloadTrackingService.DOWNLOAD_CLIENT] = grabbedItems.First().Data[DownloadTrackingService.DOWNLOAD_CLIENT]; - importedItems.First().Data[DownloadTrackingService.DOWNLOAD_CLIENT_ID] = grabbedItems.First().Data[DownloadTrackingService.DOWNLOAD_CLIENT_ID]; - _historyService.UpdateHistoryData(importedItems.First().Id, importedItems.First().Data); - - UpdateStatusMessage(trackedDownload, LogLevel.Debug, "Intermediate Download path does not exist, but found probable drone factory ImportEvent."); - return; - } - } + trackedDownload.Warn(statusMessages); + return; } - UpdateStatusMessage(trackedDownload, LogLevel.Error, "Intermediate Download path does not exist: {0}", trackedDownload.DownloadItem.OutputPath); - } + trackedDownload.State = TrackedDownloadStage.Imported; + _eventAggregator.PublishEvent(new DownloadCompletedEvent(trackedDownload)); - private void RemoveCompleted(TrackedDownload trackedDownload, IDownloadClient downloadClient) - { - if (trackedDownload.State == TrackedDownloadState.Imported && !trackedDownload.DownloadItem.IsReadOnly) - { - try - { - _logger.Debug("[{0}] Removing completed download from history.", trackedDownload.DownloadItem.Title); - downloadClient.RemoveItem(trackedDownload.DownloadItem.DownloadClientId); - - if (_diskProvider.FolderExists(trackedDownload.DownloadItem.OutputPath.FullPath)) - { - _logger.Debug("Removing completed download directory: {0}", - trackedDownload.DownloadItem.OutputPath); - _diskProvider.DeleteFolder(trackedDownload.DownloadItem.OutputPath.FullPath, true); - } - else if (_diskProvider.FileExists(trackedDownload.DownloadItem.OutputPath.FullPath)) - { - _logger.Debug("Removing completed download file: {0}", trackedDownload.DownloadItem.OutputPath); - _diskProvider.DeleteFile(trackedDownload.DownloadItem.OutputPath.FullPath); - } - - trackedDownload.State = TrackedDownloadState.Removed; - } - catch (NotSupportedException) - { - UpdateStatusMessage(trackedDownload, LogLevel.Debug, - "Removing item not supported by your download client."); - } - } } - } } diff --git a/src/NzbDrone.Core/Download/DownloadClientItem.cs b/src/NzbDrone.Core/Download/DownloadClientItem.cs index 3476bb33f..88586c4c7 100644 --- a/src/NzbDrone.Core/Download/DownloadClientItem.cs +++ b/src/NzbDrone.Core/Download/DownloadClientItem.cs @@ -1,18 +1,19 @@ using System; +using System.Diagnostics; using NzbDrone.Common.Disk; namespace NzbDrone.Core.Download { + [DebuggerDisplay("{DownloadClient}:{Title}")] public class DownloadClientItem { public String DownloadClient { get; set; } - public String DownloadClientId { get; set; } + public String DownloadId { get; set; } public String Category { get; set; } public String Title { get; set; } public Int64 TotalSize { get; set; } public Int64 RemainingSize { get; set; } - public TimeSpan? DownloadTime { get; set; } public TimeSpan? RemainingTime { get; set; } public OsPath OutputPath { get; set; } @@ -21,5 +22,7 @@ namespace NzbDrone.Core.Download public DownloadItemStatus Status { get; set; } public Boolean IsEncrypted { get; set; } public Boolean IsReadOnly { get; set; } + + public bool Removed { get; set; } } } diff --git a/src/NzbDrone.Core/Download/DownloadClientProvider.cs b/src/NzbDrone.Core/Download/DownloadClientProvider.cs index 471a9901c..e0c12eace 100644 --- a/src/NzbDrone.Core/Download/DownloadClientProvider.cs +++ b/src/NzbDrone.Core/Download/DownloadClientProvider.cs @@ -29,10 +29,9 @@ namespace NzbDrone.Core.Download { return _downloadClientFactory.GetAvailableProviders(); } - public IDownloadClient Get(int id) { return _downloadClientFactory.GetAvailableProviders().Single(d => d.Definition.Id == id); } } -} \ No newline at end of file +} diff --git a/src/NzbDrone.Core/Download/DownloadClientRepository.cs b/src/NzbDrone.Core/Download/DownloadClientRepository.cs index 25c1ea15c..460ce7230 100644 --- a/src/NzbDrone.Core/Download/DownloadClientRepository.cs +++ b/src/NzbDrone.Core/Download/DownloadClientRepository.cs @@ -2,7 +2,6 @@ using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.ThingiProvider; - namespace NzbDrone.Core.Download { public interface IDownloadClientRepository : IProviderRepository diff --git a/src/NzbDrone.Core/Download/DownloadEventHub.cs b/src/NzbDrone.Core/Download/DownloadEventHub.cs new file mode 100644 index 000000000..3479b9b04 --- /dev/null +++ b/src/NzbDrone.Core/Download/DownloadEventHub.cs @@ -0,0 +1,83 @@ +using System; +using NLog; +using NzbDrone.Common.Messaging; +using NzbDrone.Core.Configuration; +using NzbDrone.Core.Download.TrackedDownloads; +using NzbDrone.Core.Messaging.Events; + +namespace NzbDrone.Core.Download +{ + public class DownloadCompletedEvent : IEvent + { + public TrackedDownload TrackedDownload { get; private set; } + + public DownloadCompletedEvent(TrackedDownload trackedDownload) + { + TrackedDownload = trackedDownload; + } + } + + + public class DownloadEventHub : IHandle, + IHandle + { + private readonly IConfigService _configService; + private readonly IProvideDownloadClient _downloadClientProvider; + private readonly ITrackedDownloadService _trackedDownloadService; + private readonly Logger _logger; + + public DownloadEventHub(IConfigService configService, + IProvideDownloadClient downloadClientProvider, + ITrackedDownloadService trackedDownloadService, + Logger logger) + { + _configService = configService; + _downloadClientProvider = downloadClientProvider; + _trackedDownloadService = trackedDownloadService; + _logger = logger; + } + + public void Handle(DownloadCompletedEvent message) + { + if (message.TrackedDownload.DownloadItem.Removed || message.TrackedDownload.DownloadItem.IsReadOnly || !_configService.RemoveCompletedDownloads) + { + return; + } + + RemoveFromDownloadClient(message.TrackedDownload); + } + + public void Handle(DownloadFailedEvent message) + { + var trackedDownload = _trackedDownloadService.Find(message.DownloadId); + + + if (trackedDownload == null || trackedDownload.DownloadItem.IsReadOnly || !_configService.RemoveFailedDownloads) + { + return; + } + + RemoveFromDownloadClient(trackedDownload); + } + + + private void RemoveFromDownloadClient(TrackedDownload trackedDownload) + { + var downloadClient = _downloadClientProvider.Get(trackedDownload.DownloadClient); + try + { + _logger.Debug("[{0}] Removing download from {1} history", trackedDownload.DownloadItem.DownloadClient); + downloadClient.RemoveItem(trackedDownload.DownloadItem.DownloadId); + trackedDownload.DownloadItem.Removed = true; + } + catch (NotSupportedException) + { + _logger.Warn("Removing item not supported by your download client ({0}).", downloadClient.Definition.Name); + } + catch (Exception e) + { + _logger.ErrorException("Couldn't remove item from client " + trackedDownload.DownloadItem.Title, e); + } + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Core/Download/DownloadFailedEvent.cs b/src/NzbDrone.Core/Download/DownloadFailedEvent.cs index 527b77e41..5dd90ea6f 100644 --- a/src/NzbDrone.Core/Download/DownloadFailedEvent.cs +++ b/src/NzbDrone.Core/Download/DownloadFailedEvent.cs @@ -17,7 +17,7 @@ namespace NzbDrone.Core.Download public QualityModel Quality { get; set; } public String SourceTitle { get; set; } public String DownloadClient { get; set; } - public String DownloadClientId { get; set; } + public String DownloadId { get; set; } public String Message { get; set; } public Dictionary Data { get; set; } } diff --git a/src/NzbDrone.Core/Download/DownloadService.cs b/src/NzbDrone.Core/Download/DownloadService.cs index 2e3a66344..6f0737dc0 100644 --- a/src/NzbDrone.Core/Download/DownloadService.cs +++ b/src/NzbDrone.Core/Download/DownloadService.cs @@ -47,7 +47,7 @@ namespace NzbDrone.Core.Download if (!String.IsNullOrWhiteSpace(downloadClientId)) { - episodeGrabbedEvent.DownloadClientId = downloadClientId; + episodeGrabbedEvent.DownloadId = downloadClientId; } _logger.ProgressInfo("Report sent to download client. {0}", downloadTitle); diff --git a/src/NzbDrone.Core/Download/DownloadTrackingService.cs b/src/NzbDrone.Core/Download/DownloadTrackingService.cs deleted file mode 100644 index 44190560c..000000000 --- a/src/NzbDrone.Core/Download/DownloadTrackingService.cs +++ /dev/null @@ -1,320 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using NLog; -using NzbDrone.Common.Cache; -using NzbDrone.Common.Extensions; -using NzbDrone.Core.Configuration; -using NzbDrone.Core.DataAugmentation.Scene; -using NzbDrone.Core.History; -using NzbDrone.Core.Messaging.Commands; -using NzbDrone.Core.Messaging.Events; -using NzbDrone.Core.Lifecycle; -using NzbDrone.Core.Parser; -using NzbDrone.Core.Queue; - -namespace NzbDrone.Core.Download -{ - public interface IDownloadTrackingService - { - TrackedDownload[] GetCompletedDownloads(); - TrackedDownload[] GetQueuedDownloads(); - TrackedDownload Find(string trackingId); - void MarkAsFailed(Int32 historyId); - } - - public class DownloadTrackingService : IDownloadTrackingService, - IExecute, - IHandleAsync, - IHandle, - IHandle - { - private readonly IProvideDownloadClient _downloadClientProvider; - private readonly IHistoryService _historyService; - private readonly IEventAggregator _eventAggregator; - private readonly IConfigService _configService; - private readonly IFailedDownloadService _failedDownloadService; - private readonly ICompletedDownloadService _completedDownloadService; - private readonly IParsingService _parsingService; - private readonly Logger _logger; - - private readonly ICached _trackedDownloadCache; - - public static string DOWNLOAD_CLIENT = "downloadClient"; - public static string DOWNLOAD_CLIENT_ID = "downloadClientId"; - - public DownloadTrackingService(IProvideDownloadClient downloadClientProvider, - IHistoryService historyService, - IEventAggregator eventAggregator, - IConfigService configService, - ICacheManager cacheManager, - IFailedDownloadService failedDownloadService, - ICompletedDownloadService completedDownloadService, - IParsingService parsingService, - Logger logger) - { - _downloadClientProvider = downloadClientProvider; - _historyService = historyService; - _eventAggregator = eventAggregator; - _configService = configService; - _failedDownloadService = failedDownloadService; - _completedDownloadService = completedDownloadService; - _parsingService = parsingService; - _logger = logger; - - _trackedDownloadCache = cacheManager.GetCache(GetType()); - } - - private TrackedDownload[] GetTrackedDownloads() - { - return _trackedDownloadCache.Get("tracked", () => new TrackedDownload[0]); - } - - public TrackedDownload[] GetCompletedDownloads() - { - return GetTrackedDownloads() - .Where(v => v.State == TrackedDownloadState.Downloading && v.DownloadItem.Status == DownloadItemStatus.Completed) - .ToArray(); - } - - public TrackedDownload[] GetQueuedDownloads() - { - return _trackedDownloadCache.Get("queued", () => - { - UpdateTrackedDownloads(_historyService.Grabbed()); - - return FilterQueuedDownloads(GetTrackedDownloads()); - - }, TimeSpan.FromSeconds(5.0)); - } - - public TrackedDownload Find(string trackingId) - { - return GetQueuedDownloads().SingleOrDefault(t => t.TrackingId == trackingId); - } - - public void MarkAsFailed(Int32 historyId) - { - var item = _historyService.Get(historyId); - - var trackedDownload = GetTrackedDownloads() - .FirstOrDefault(h => h.DownloadItem.DownloadClientId.Equals(item.Data.GetValueOrDefault(DOWNLOAD_CLIENT_ID))); - - if (trackedDownload != null && trackedDownload.State == TrackedDownloadState.Unknown) - { - ProcessTrackedDownloads(); - } - - _failedDownloadService.MarkAsFailed(trackedDownload, item); - } - - private TrackedDownload[] FilterQueuedDownloads(IEnumerable trackedDownloads) - { - var enabledFailedDownloadHandling = _configService.EnableFailedDownloadHandling; - var enabledCompletedDownloadHandling = _configService.EnableCompletedDownloadHandling; - - return trackedDownloads - .Where(v => v.State == TrackedDownloadState.Downloading) - .Where(v => - v.DownloadItem.Status == DownloadItemStatus.Queued || - v.DownloadItem.Status == DownloadItemStatus.Paused || - v.DownloadItem.Status == DownloadItemStatus.Downloading || - v.DownloadItem.Status == DownloadItemStatus.Warning || - v.DownloadItem.Status == DownloadItemStatus.Failed && enabledFailedDownloadHandling || - v.DownloadItem.Status == DownloadItemStatus.Completed && enabledCompletedDownloadHandling) - .ToArray(); - } - - private List GetHistoryItems(List grabbedHistory, string downloadClientId) - { - return grabbedHistory.Where(h => downloadClientId.Equals(h.Data.GetValueOrDefault(DOWNLOAD_CLIENT_ID))) - .ToList(); - } - - private Boolean UpdateTrackedDownloads(List grabbedHistory) - { - var downloadClients = _downloadClientProvider.GetDownloadClients(); - - var oldTrackedDownloads = GetTrackedDownloads().ToDictionary(v => v.TrackingId); - var newTrackedDownloads = new Dictionary(); - - var stateChanged = false; - - foreach (var downloadClient in downloadClients) - { - List downloadClientHistory; - try - { - downloadClientHistory = downloadClient.GetItems().ToList(); - } - catch (Exception ex) - { - _logger.WarnException("Unable to retrieve queue and history items from " + downloadClient.Definition.Name, ex); - continue; - } - foreach (var downloadItem in downloadClientHistory) - { - var trackingId = String.Format("{0}-{1}", downloadClient.Definition.Id, downloadItem.DownloadClientId); - TrackedDownload trackedDownload; - - if (newTrackedDownloads.ContainsKey(trackingId)) continue; - - if (!oldTrackedDownloads.TryGetValue(trackingId, out trackedDownload)) - { - trackedDownload = GetTrackedDownload(trackingId, downloadClient.Definition.Id, downloadItem, grabbedHistory); - - if (trackedDownload == null) continue; - - _logger.Debug("[{0}] Started tracking download with id {1}.", downloadItem.Title, trackingId); - stateChanged = true; - } - - trackedDownload.DownloadItem = downloadItem; - - newTrackedDownloads[trackingId] = trackedDownload; - } - } - - foreach (var trackedDownload in oldTrackedDownloads.Values.Where(v => !newTrackedDownloads.ContainsKey(v.TrackingId))) - { - if (trackedDownload.State != TrackedDownloadState.Removed) - { - trackedDownload.State = TrackedDownloadState.Removed; - stateChanged = true; - - _logger.Debug("[{0}] Item with id {1} removed from download client directly (possibly by user).", trackedDownload.DownloadItem.Title, trackedDownload.TrackingId); - } - - _logger.Debug("[{0}] Stopped tracking download with id {1}.", trackedDownload.DownloadItem.Title, trackedDownload.TrackingId); - } - - _trackedDownloadCache.Set("tracked", newTrackedDownloads.Values.ToArray()); - - return stateChanged; - } - - private void ProcessTrackedDownloads() - { - var grabbedHistory = _historyService.Grabbed(); - var failedHistory = _historyService.Failed(); - var importedHistory = _historyService.Imported(); - - var stateChanged = UpdateTrackedDownloads(grabbedHistory); - - var downloadClients = _downloadClientProvider.GetDownloadClients().ToList(); - var trackedDownloads = GetTrackedDownloads(); - - foreach (var trackedDownload in trackedDownloads) - { - var downloadClient = downloadClients.SingleOrDefault(v => v.Definition.Id == trackedDownload.DownloadClient); - - if (downloadClient == null) - { - _logger.Debug("TrackedDownload for unknown download client, download client was probably removed or disabled between scans."); - continue; - } - - var state = trackedDownload.State; - - if (trackedDownload.State == TrackedDownloadState.Unknown) - { - trackedDownload.State = TrackedDownloadState.Downloading; - } - - _failedDownloadService.CheckForFailedItem(downloadClient, trackedDownload, grabbedHistory, failedHistory); - _completedDownloadService.CheckForCompletedItem(downloadClient, trackedDownload, grabbedHistory, importedHistory); - - if (state != trackedDownload.State) - { - stateChanged = true; - } - } - - _trackedDownloadCache.Set("queued", FilterQueuedDownloads(trackedDownloads), TimeSpan.FromSeconds(5.0)); - - if (stateChanged) - { - _eventAggregator.PublishEvent(new UpdateQueueEvent()); - } - } - - private TrackedDownload GetTrackedDownload(String trackingId, Int32 downloadClient, DownloadClientItem downloadItem, List grabbedHistory) - { - var trackedDownload = new TrackedDownload - { - TrackingId = trackingId, - DownloadClient = downloadClient, - DownloadItem = downloadItem, - StartedTracking = DateTime.UtcNow, - State = TrackedDownloadState.Unknown, - Status = TrackedDownloadStatus.Ok, - }; - - try - { - var historyItems = grabbedHistory.Where(h => - { - var downloadClientId = h.Data.GetValueOrDefault(DOWNLOAD_CLIENT_ID); - - if (downloadClientId == null) return false; - - return downloadClientId.Equals(trackedDownload.DownloadItem.DownloadClientId); - }).ToList(); - - var parsedEpisodeInfo = Parser.Parser.ParseTitle(trackedDownload.DownloadItem.Title); - if (parsedEpisodeInfo == null) return null; - - var remoteEpisode = _parsingService.Map(parsedEpisodeInfo, 0); - if (remoteEpisode.Series == null) - { - if (historyItems.Empty()) return null; - - trackedDownload.Status = TrackedDownloadStatus.Warning; - trackedDownload.StatusMessages.Add(new TrackedDownloadStatusMessage( - trackedDownload.DownloadItem.Title, - "Series title mismatch, automatic import is not possible") - ); - - remoteEpisode = _parsingService.Map(parsedEpisodeInfo, historyItems.First().SeriesId, historyItems.Select(h => h.EpisodeId)); - } - - trackedDownload.RemoteEpisode = remoteEpisode; - } - catch (Exception e) - { - _logger.DebugException("Failed to find episode for " + downloadItem.Title, e); - return null; - } - - return trackedDownload; - } - - public void Execute(CheckForFinishedDownloadCommand message) - { - ProcessTrackedDownloads(); - } - - public void HandleAsync(ApplicationStartedEvent message) - { - ProcessTrackedDownloads(); - } - - public void Handle(EpisodeGrabbedEvent message) - { - ProcessTrackedDownloads(); - } - - public void Handle(SceneMappingsUpdatedEvent message) - { - var grabbedHistory = _historyService.Grabbed(); - - foreach (var trackedDownload in GetTrackedDownloads().Where(t => t.Status == TrackedDownloadStatus.Warning)) - { - var newTrackedDownload = GetTrackedDownload(trackedDownload.TrackingId, trackedDownload.DownloadClient, trackedDownload.DownloadItem, grabbedHistory); - - trackedDownload.Status = newTrackedDownload.Status; - trackedDownload.StatusMessages = newTrackedDownload.StatusMessages; - } - } - } -} diff --git a/src/NzbDrone.Core/Download/EpisodeGrabbedEvent.cs b/src/NzbDrone.Core/Download/EpisodeGrabbedEvent.cs index a6a9f0b52..53c64695e 100644 --- a/src/NzbDrone.Core/Download/EpisodeGrabbedEvent.cs +++ b/src/NzbDrone.Core/Download/EpisodeGrabbedEvent.cs @@ -8,7 +8,7 @@ namespace NzbDrone.Core.Download { public RemoteEpisode Episode { get; private set; } public String DownloadClient { get; set; } - public String DownloadClientId { get; set; } + public String DownloadId { get; set; } public EpisodeGrabbedEvent(RemoteEpisode episode) { diff --git a/src/NzbDrone.Core/Download/FailedDownloadService.cs b/src/NzbDrone.Core/Download/FailedDownloadService.cs index e00507741..363ccfe44 100644 --- a/src/NzbDrone.Core/Download/FailedDownloadService.cs +++ b/src/NzbDrone.Core/Download/FailedDownloadService.cs @@ -1,9 +1,7 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; -using NLog; using NzbDrone.Common.Extensions; -using NzbDrone.Core.Configuration; +using NzbDrone.Core.Download.TrackedDownloads; using NzbDrone.Core.History; using NzbDrone.Core.Messaging.Events; @@ -11,208 +9,59 @@ namespace NzbDrone.Core.Download { public interface IFailedDownloadService { - void MarkAsFailed(TrackedDownload trackedDownload, History.History grabbedHistory); - void CheckForFailedItem(IDownloadClient downloadClient, TrackedDownload trackedDownload, List grabbedHistory, List failedHistory); + void MarkAsFailed(int historyId); + void Process(TrackedDownload trackedDownload); } public class FailedDownloadService : IFailedDownloadService { private readonly IHistoryService _historyService; private readonly IEventAggregator _eventAggregator; - private readonly IConfigService _configService; - private readonly Logger _logger; public FailedDownloadService(IHistoryService historyService, - IEventAggregator eventAggregator, - IConfigService configService, - Logger logger) + IEventAggregator eventAggregator) { _historyService = historyService; _eventAggregator = eventAggregator; - _configService = configService; - _logger = logger; } - public void MarkAsFailed(TrackedDownload trackedDownload, History.History history) + public void MarkAsFailed(int historyId) { - if (trackedDownload != null && trackedDownload.State == TrackedDownloadState.Downloading) - { - trackedDownload.State = TrackedDownloadState.DownloadFailed; - } + var history = _historyService.Get(historyId); - var downloadClientId = history.Data.GetValueOrDefault(DownloadTrackingService.DOWNLOAD_CLIENT_ID); - if (downloadClientId.IsNullOrWhiteSpace()) + var downloadId = history.DownloadId; + if (downloadId.IsNullOrWhiteSpace()) { PublishDownloadFailedEvent(new List { history }, "Manually marked as failed"); } else { - var grabbedHistory = GetHistoryItems(_historyService.Grabbed(), downloadClientId); - + var grabbedHistory = _historyService.Find(downloadId, HistoryEventType.Grabbed).ToList(); PublishDownloadFailedEvent(grabbedHistory, "Manually marked as failed"); } } - public void CheckForFailedItem(IDownloadClient downloadClient, TrackedDownload trackedDownload, List grabbedHistory, List failedHistory) - { - if (!_configService.EnableFailedDownloadHandling) - { - return; - } - - if (trackedDownload.DownloadItem.IsEncrypted && trackedDownload.State == TrackedDownloadState.Downloading) - { - var grabbedItems = GetHistoryItems(grabbedHistory, trackedDownload.DownloadItem.DownloadClientId); - - if (!grabbedItems.Any()) - { - UpdateStatusMessage(trackedDownload, LogLevel.Warn, "Download wasn't grabbed by drone, ignoring download"); - return; - } - - trackedDownload.State = TrackedDownloadState.DownloadFailed; - - var failedItems = GetHistoryItems(failedHistory, trackedDownload.DownloadItem.DownloadClientId); - - if (failedItems.Any()) - { - UpdateStatusMessage(trackedDownload, LogLevel.Debug, "Already added to history as failed."); - } - else - { - PublishDownloadFailedEvent(grabbedItems, "Encrypted download detected"); - } - } - - if (trackedDownload.DownloadItem.Status == DownloadItemStatus.Failed && trackedDownload.State == TrackedDownloadState.Downloading) - { - var grabbedItems = GetHistoryItems(grabbedHistory, trackedDownload.DownloadItem.DownloadClientId); - - if (!grabbedItems.Any()) - { - UpdateStatusMessage(trackedDownload, LogLevel.Warn, "Download wasn't grabbed by drone, ignoring download"); - return; - } - - var failedItems = GetHistoryItems(failedHistory, trackedDownload.DownloadItem.DownloadClientId); - - if (failedItems.Any()) - { - trackedDownload.State = TrackedDownloadState.DownloadFailed; - UpdateStatusMessage(trackedDownload, LogLevel.Debug, "Already added to history as failed."); - } - else - { - if (FailedDownloadForRecentRelease(downloadClient, trackedDownload, grabbedItems)) - { - _logger.Debug("[{0}] Recent release Failed, do not blacklist.", trackedDownload.DownloadItem.Title); - return; - } - - trackedDownload.State = TrackedDownloadState.DownloadFailed; - - PublishDownloadFailedEvent(grabbedItems, trackedDownload.DownloadItem.Message); - } - } - - if (trackedDownload.DownloadItem.Status != DownloadItemStatus.Failed && trackedDownload.State == TrackedDownloadState.Downloading) - { - var grabbedItems = GetHistoryItems(grabbedHistory, trackedDownload.DownloadItem.DownloadClientId); - var failedItems = GetHistoryItems(failedHistory, trackedDownload.DownloadItem.DownloadClientId); - - if (grabbedItems.Any() && failedItems.Any()) - { - UpdateStatusMessage(trackedDownload, LogLevel.Debug, "Already added to history as failed, updating tracked state."); - trackedDownload.State = TrackedDownloadState.DownloadFailed; - } - } - - if (_configService.RemoveFailedDownloads && trackedDownload.State == TrackedDownloadState.DownloadFailed) - { - try - { - _logger.Debug("[{0}] Removing failed download from client.", trackedDownload.DownloadItem.Title); - downloadClient.RemoveItem(trackedDownload.DownloadItem.DownloadClientId); - - trackedDownload.State = TrackedDownloadState.Removed; - } - catch (NotSupportedException) - { - UpdateStatusMessage(trackedDownload, LogLevel.Debug, "Removing item not supported by your download client."); - } - } - } - - private bool FailedDownloadForRecentRelease(IDownloadClient downloadClient, TrackedDownload trackedDownload, List matchingHistoryItems) + public void Process(TrackedDownload trackedDownload) { - double ageHours; - - if (!Double.TryParse(matchingHistoryItems.First().Data.GetValueOrDefault("ageHours"), out ageHours)) - { - UpdateStatusMessage(trackedDownload, LogLevel.Info, "Unable to determine age of failed download."); - return false; - } - - if (ageHours > _configService.BlacklistGracePeriod) - { - UpdateStatusMessage(trackedDownload, LogLevel.Info, "Download Failed, Failed download is older than the grace period."); - return false; - } + var grabbedItems = _historyService.Find(trackedDownload.DownloadItem.DownloadId, HistoryEventType.Grabbed) + .ToList(); - if (trackedDownload.RetryCount >= _configService.BlacklistRetryLimit) + if (grabbedItems.Empty()) { - UpdateStatusMessage(trackedDownload, LogLevel.Info, "Download Failed, Retry limit reached."); - return false; - } - - if (trackedDownload.LastRetry == new DateTime()) - { - trackedDownload.LastRetry = DateTime.UtcNow; + trackedDownload.Warn("Download wasn't grabbed by sonarr, skipping"); + return; } - if (trackedDownload.LastRetry.AddMinutes(_configService.BlacklistRetryInterval) < DateTime.UtcNow) + if (trackedDownload.DownloadItem.IsEncrypted) { - trackedDownload.LastRetry = DateTime.UtcNow; - trackedDownload.RetryCount++; - - UpdateStatusMessage(trackedDownload, LogLevel.Info, "Download Failed, initiating retry attempt {0}/{1}.", trackedDownload.RetryCount, _configService.BlacklistRetryLimit); - - try - { - var newDownloadClientId = downloadClient.RetryDownload(trackedDownload.DownloadItem.DownloadClientId); - - if (newDownloadClientId != trackedDownload.DownloadItem.DownloadClientId) - { - var oldTrackingId = trackedDownload.TrackingId; - var newTrackingId = String.Format("{0}-{1}", downloadClient.Definition.Id, newDownloadClientId); - - trackedDownload.TrackingId = newTrackingId; - trackedDownload.DownloadItem.DownloadClientId = newDownloadClientId; - - _logger.Debug("[{0}] Changed id from {1} to {2}.", trackedDownload.DownloadItem.Title, oldTrackingId, newTrackingId); - var newHistoryData = new Dictionary(matchingHistoryItems.First().Data); - newHistoryData[DownloadTrackingService.DOWNLOAD_CLIENT_ID] = newDownloadClientId; - _historyService.UpdateHistoryData(matchingHistoryItems.First().Id, newHistoryData); - } - } - catch (NotSupportedException) - { - UpdateStatusMessage(trackedDownload, LogLevel.Debug, "Retrying failed downloads is not supported by your download client."); - return false; - } + trackedDownload.State = TrackedDownloadStage.DownloadFailed; + PublishDownloadFailedEvent(grabbedItems, "Encrypted download detected"); } - else + else if (trackedDownload.DownloadItem.Status == DownloadItemStatus.Failed) { - UpdateStatusMessage(trackedDownload, LogLevel.Warn, "Download Failed, waiting for retry interval to expire."); + trackedDownload.State = TrackedDownloadStage.DownloadFailed; + PublishDownloadFailedEvent(grabbedItems, trackedDownload.DownloadItem.Message); } - - return true; - } - - private List GetHistoryItems(List grabbedHistory, string downloadClientId) - { - return grabbedHistory.Where(h => downloadClientId.Equals(h.Data.GetValueOrDefault(DownloadTrackingService.DOWNLOAD_CLIENT_ID))) - .ToList(); } private void PublishDownloadFailedEvent(List historyItems, string message) @@ -225,35 +74,15 @@ namespace NzbDrone.Core.Download EpisodeIds = historyItems.Select(h => h.EpisodeId).ToList(), Quality = historyItem.Quality, SourceTitle = historyItem.SourceTitle, - DownloadClient = historyItem.Data.GetValueOrDefault(DownloadTrackingService.DOWNLOAD_CLIENT), - DownloadClientId = historyItem.Data.GetValueOrDefault(DownloadTrackingService.DOWNLOAD_CLIENT_ID), - Message = message + DownloadClient = historyItem.Data.GetValueOrDefault(History.History.DOWNLOAD_CLIENT), + DownloadId = historyItem.DownloadId, + Message = message, + Data = historyItem.Data }; - downloadFailedEvent.Data = downloadFailedEvent.Data.Merge(historyItem.Data); - _eventAggregator.PublishEvent(downloadFailedEvent); } - private void UpdateStatusMessage(TrackedDownload trackedDownload, LogLevel logLevel, String message, params object[] args) - { - var statusMessage = String.Format(message, args); - var logMessage = String.Format("[{0}] {1}", trackedDownload.DownloadItem.Title, statusMessage); - if (trackedDownload.StatusMessage != statusMessage) - { - trackedDownload.SetStatusLevel(logLevel); - trackedDownload.StatusMessage = statusMessage; - trackedDownload.StatusMessages = new List - { - new TrackedDownloadStatusMessage(trackedDownload.DownloadItem.Title, statusMessage) - }; - _logger.Log(logLevel, logMessage); - } - else - { - _logger.Debug(logMessage); - } - } } } diff --git a/src/NzbDrone.Core/Download/Pending/PendingReleaseService.cs b/src/NzbDrone.Core/Download/Pending/PendingReleaseService.cs index 4fe19d9d0..c879f1b23 100644 --- a/src/NzbDrone.Core/Download/Pending/PendingReleaseService.cs +++ b/src/NzbDrone.Core/Download/Pending/PendingReleaseService.cs @@ -108,10 +108,10 @@ namespace NzbDrone.Core.Download.Pending Title = pendingRelease.Title, Size = pendingRelease.RemoteEpisode.Release.Size, Sizeleft = pendingRelease.RemoteEpisode.Release.Size, + RemoteEpisode = pendingRelease.RemoteEpisode, Timeleft = ect.Subtract(DateTime.UtcNow), EstimatedCompletionTime = ect, - Status = "Pending", - RemoteEpisode = pendingRelease.RemoteEpisode + Status = "Pending" }; queued.Add(queue); } diff --git a/src/NzbDrone.Core/Download/RedownloadFailedDownloadService.cs b/src/NzbDrone.Core/Download/RedownloadFailedDownloadService.cs index 95914a5a7..94d1d16ae 100644 --- a/src/NzbDrone.Core/Download/RedownloadFailedDownloadService.cs +++ b/src/NzbDrone.Core/Download/RedownloadFailedDownloadService.cs @@ -1,19 +1,14 @@ -using System.Collections.Generic; -using System.Linq; +using System.Linq; using NLog; using NzbDrone.Core.Configuration; using NzbDrone.Core.IndexerSearch; using NzbDrone.Core.Messaging.Commands; +using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Tv; namespace NzbDrone.Core.Download { - public interface IRedownloadFailedDownloads - { - void Redownload(int seriesId, List episodeIds); - } - - public class RedownloadFailedDownloadService : IRedownloadFailedDownloads + public class RedownloadFailedDownloadService : IHandleAsync { private readonly IConfigService _configService; private readonly IEpisodeService _episodeService; @@ -28,7 +23,7 @@ namespace NzbDrone.Core.Download _logger = logger; } - public void Redownload(int seriesId, List episodeIds) + public void HandleAsync(DownloadFailedEvent message) { if (!_configService.AutoRedownloadFailed) { @@ -36,34 +31,34 @@ namespace NzbDrone.Core.Download return; } - if (episodeIds.Count == 1) + if (message.EpisodeIds.Count == 1) { _logger.Debug("Failed download only contains one episode, searching again"); - _commandExecutor.PublishCommandAsync(new EpisodeSearchCommand(episodeIds)); + _commandExecutor.PublishCommandAsync(new EpisodeSearchCommand(message.EpisodeIds)); return; } - var seasonNumber = _episodeService.GetEpisode(episodeIds.First()).SeasonNumber; - var episodesInSeason = _episodeService.GetEpisodesBySeason(seriesId, seasonNumber); + var seasonNumber = _episodeService.GetEpisode(message.EpisodeIds.First()).SeasonNumber; + var episodesInSeason = _episodeService.GetEpisodesBySeason(message.SeriesId, seasonNumber); - if (episodeIds.Count == episodesInSeason.Count) + if (message.EpisodeIds.Count == episodesInSeason.Count) { _logger.Debug("Failed download was entire season, searching again"); _commandExecutor.PublishCommandAsync(new SeasonSearchCommand - { - SeriesId = seriesId, - SeasonNumber = seasonNumber - }); + { + SeriesId = message.SeriesId, + SeasonNumber = seasonNumber + }); return; } _logger.Debug("Failed download contains multiple episodes, probably a double episode, searching again"); - _commandExecutor.PublishCommandAsync(new EpisodeSearchCommand(episodeIds)); + _commandExecutor.PublishCommandAsync(new EpisodeSearchCommand(message.EpisodeIds)); } } } diff --git a/src/NzbDrone.Core/Download/TrackedDownload.cs b/src/NzbDrone.Core/Download/TrackedDownload.cs deleted file mode 100644 index c0cad8bb2..000000000 --- a/src/NzbDrone.Core/Download/TrackedDownload.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; -using System.Collections.Generic; -using NLog; -using NzbDrone.Core.Parser.Model; - -namespace NzbDrone.Core.Download -{ - public class TrackedDownload - { - public String TrackingId { get; set; } - public Int32 DownloadClient { get; set; } - public DownloadClientItem DownloadItem { get; set; } - public TrackedDownloadState State { get; set; } - public TrackedDownloadStatus Status { get; set; } - public DateTime StartedTracking { get; set; } - public DateTime LastRetry { get; set; } - public Int32 RetryCount { get; set; } - public String StatusMessage { get; set; } - public RemoteEpisode RemoteEpisode { get; set; } - public List StatusMessages { get; set; } - - public TrackedDownload() - { - StatusMessages = new List(); - } - - public void SetStatusLevel(LogLevel logLevel) - { - if (logLevel == LogLevel.Warn) - { - Status = TrackedDownloadStatus.Warning; - } - - if (logLevel >= LogLevel.Error) - { - Status = TrackedDownloadStatus.Error; - } - - else Status = TrackedDownloadStatus.Ok; - } - } - - public enum TrackedDownloadState - { - Unknown, - Downloading, - Imported, - DownloadFailed, - Removed - } - - public enum TrackedDownloadStatus - { - Ok, - Warning, - Error - } -} diff --git a/src/NzbDrone.Core/Download/TrackedDownloads/DownloadMonitoringService.cs b/src/NzbDrone.Core/Download/TrackedDownloads/DownloadMonitoringService.cs new file mode 100644 index 000000000..ab14ab457 --- /dev/null +++ b/src/NzbDrone.Core/Download/TrackedDownloads/DownloadMonitoringService.cs @@ -0,0 +1,137 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NLog; +using NzbDrone.Common.TPL; +using NzbDrone.Core.Configuration; +using NzbDrone.Core.MediaFiles.Events; +using NzbDrone.Core.Messaging.Commands; +using NzbDrone.Core.Messaging.Events; + +namespace NzbDrone.Core.Download.TrackedDownloads +{ + public class DownloadMonitoringService : IExecute, + IHandleAsync, + IHandleAsync + { + private readonly IProvideDownloadClient _downloadClientProvider; + private readonly IEventAggregator _eventAggregator; + private readonly IConfigService _configService; + private readonly IFailedDownloadService _failedDownloadService; + private readonly ICompletedDownloadService _completedDownloadService; + private readonly ITrackedDownloadService _trackedDownloadService; + private readonly Logger _logger; + private readonly Debouncer _refreshDebounce; + + public DownloadMonitoringService(IProvideDownloadClient downloadClientProvider, + IEventAggregator eventAggregator, + IConfigService configService, + IFailedDownloadService failedDownloadService, + ICompletedDownloadService completedDownloadService, + ITrackedDownloadService trackedDownloadService, + Logger logger) + { + _downloadClientProvider = downloadClientProvider; + _eventAggregator = eventAggregator; + _configService = configService; + _failedDownloadService = failedDownloadService; + _completedDownloadService = completedDownloadService; + _trackedDownloadService = trackedDownloadService; + _logger = logger; + + _refreshDebounce = new Debouncer(Refresh, TimeSpan.FromSeconds(5)); + } + + private void Refresh() + { + var downloadClients = _downloadClientProvider.GetDownloadClients(); + + var trackedDownload = new List(); + + foreach (var downloadClient in downloadClients) + { + var clientTrackedDowmloads = ProcessClientDownloads(downloadClient); + trackedDownload.AddRange(clientTrackedDowmloads.Where(c => c.State == TrackedDownloadStage.Downloading)); + } + + _eventAggregator.PublishEvent(new TrackedDownloadRefreshedEvent(trackedDownload)); + } + + private List ProcessClientDownloads(IDownloadClient downloadClient) + { + List downloadClientHistory = new List(); + var trackedDownloads = new List(); + + try + { + downloadClientHistory = downloadClient.GetItems().ToList(); + } + catch (Exception ex) + { + _logger.WarnException("Unable to retrieve queue and history items from " + downloadClient.Definition.Name, ex); + } + + foreach (var downloadItem in downloadClientHistory) + { + trackedDownloads.AddRange(ProcessClientItems(downloadClient, downloadItem)); + } + + if (_configService.RemoveCompletedDownloads) + { + RemoveCompletedDownloads(trackedDownloads); + } + + return trackedDownloads; + + } + + private void RemoveCompletedDownloads(List trackedDownloads) + { + foreach (var trakedDownload in trackedDownloads.Where(c => !c.DownloadItem.IsReadOnly && c.State == TrackedDownloadStage.Imported)) + { + _eventAggregator.PublishEvent(new DownloadCompletedEvent(trakedDownload)); + } + } + + private List ProcessClientItems(IDownloadClient downloadClient, DownloadClientItem downloadItem) + { + var trackedDownloads = new List(); + try + { + var trackedDownload = _trackedDownloadService.TrackDownload((DownloadClientDefinition)downloadClient.Definition, downloadItem); + if (trackedDownload != null && trackedDownload.State == TrackedDownloadStage.Downloading) + { + _failedDownloadService.Process(trackedDownload); + + if (_configService.EnableCompletedDownloadHandling) + { + _completedDownloadService.Process(trackedDownload); + } + + trackedDownloads.Add(trackedDownload); + } + } + catch (Exception e) + { + _logger.ErrorException("Couldn't process tracked download " + downloadItem.Title, e); + } + + return trackedDownloads; + } + + public void Execute(CheckForFinishedDownloadCommand message) + { + Refresh(); + } + + public void HandleAsync(EpisodeGrabbedEvent message) + { + _refreshDebounce.Execute(); + } + + public void HandleAsync(EpisodeImportedEvent message) + { + _refreshDebounce.Execute(); + } + } +} diff --git a/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownload.cs b/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownload.cs new file mode 100644 index 000000000..e9a41eec2 --- /dev/null +++ b/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownload.cs @@ -0,0 +1,48 @@ +using System; +using NzbDrone.Core.Indexers; +using NzbDrone.Core.Parser.Model; + +namespace NzbDrone.Core.Download.TrackedDownloads +{ + public class TrackedDownload + { + public String TrackingId { get; set; } + public Int32 DownloadClient { get; set; } + public DownloadClientItem DownloadItem { get; set; } + public TrackedDownloadStage State { get; set; } + public TrackedDownloadStatus Status { get; private set; } + public RemoteEpisode RemoteEpisode { get; set; } + public TrackedDownloadStatusMessage[] StatusMessages { get; private set; } + public DownloadProtocol Protocol { get; set; } + + public TrackedDownload() + { + StatusMessages = new TrackedDownloadStatusMessage[] {}; + } + + public void Warn(String message, params object[] args) + { + var statusMessage = String.Format(message, args); + Warn(new TrackedDownloadStatusMessage(DownloadItem.Title, statusMessage)); + } + + public void Warn(params TrackedDownloadStatusMessage[] statusMessages) + { + Status = TrackedDownloadStatus.Warning; + StatusMessages = statusMessages; + } + } + + public enum TrackedDownloadStage + { + Downloading, + Imported, + DownloadFailed + } + + public enum TrackedDownloadStatus + { + Ok, + Warning + } +} diff --git a/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadRefreshedEvent.cs b/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadRefreshedEvent.cs new file mode 100644 index 000000000..e95d337ec --- /dev/null +++ b/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadRefreshedEvent.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using NzbDrone.Common.Messaging; + +namespace NzbDrone.Core.Download.TrackedDownloads +{ + public class TrackedDownloadRefreshedEvent : IEvent + { + public List TrackedDownloads { get; private set; } + + public TrackedDownloadRefreshedEvent(List trackedDownloads) + { + TrackedDownloads = trackedDownloads; + } + } +} diff --git a/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadService.cs b/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadService.cs new file mode 100644 index 000000000..fe6059053 --- /dev/null +++ b/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadService.cs @@ -0,0 +1,110 @@ +using System; +using NLog; +using NzbDrone.Common.Cache; +using NzbDrone.Core.DataAugmentation.Scene; +using NzbDrone.Core.History; +using NzbDrone.Core.Messaging.Events; +using NzbDrone.Core.Parser; + +namespace NzbDrone.Core.Download.TrackedDownloads +{ + public interface ITrackedDownloadService + { + TrackedDownload Find(string downloadId); + TrackedDownload TrackDownload(DownloadClientDefinition downloadClient, DownloadClientItem downloadItem); + } + + public class TrackedDownloadService : ITrackedDownloadService, + IHandle + { + private readonly IParsingService _parsingService; + private readonly IHistoryService _historyService; + private readonly Logger _logger; + private readonly ICached _cache; + + public TrackedDownloadService(IParsingService parsingService, + ICacheManager cacheManager, + IHistoryService historyService, + Logger logger) + { + _parsingService = parsingService; + _historyService = historyService; + _cache = cacheManager.GetCache(GetType()); + _logger = logger; + } + + public TrackedDownload Find(string downloadId) + { + return _cache.Find(downloadId); + } + + public TrackedDownload TrackDownload(DownloadClientDefinition downloadClient, DownloadClientItem downloadItem) + { + var existingItem = Find(downloadItem.DownloadId); + + if (existingItem != null) + { + existingItem.DownloadItem = downloadItem; + return existingItem; + } + + var trackedDownload = new TrackedDownload + { + TrackingId = downloadClient.Id + "-" + downloadItem.DownloadId, + DownloadClient = downloadClient.Id, + DownloadItem = downloadItem, + Protocol = downloadClient.Protocol + }; + + var historyItem = _historyService.MostRecentForDownloadId(downloadItem.DownloadId); + if (historyItem != null) + { + trackedDownload.State = GetStateFromHistory(historyItem.EventType); + } + + try + { + var parsedEpisodeInfo = Parser.Parser.ParseTitle(trackedDownload.DownloadItem.Title); + if (parsedEpisodeInfo == null) return null; + + var remoteEpisode = _parsingService.Map(parsedEpisodeInfo); + if (remoteEpisode.Series == null) + { + return null; + } + + trackedDownload.RemoteEpisode = remoteEpisode; + } + catch (Exception e) + { + _logger.DebugException("Failed to find episode for " + downloadItem.Title, e); + return null; + } + + if (trackedDownload.State != TrackedDownloadStage.Downloading) + { + _cache.Set(downloadItem.DownloadId, trackedDownload); + } + + return trackedDownload; + } + + private static TrackedDownloadStage GetStateFromHistory(HistoryEventType eventType) + { + switch (eventType) + { + case HistoryEventType.DownloadFolderImported: + return TrackedDownloadStage.Imported; + case HistoryEventType.DownloadFailed: + return TrackedDownloadStage.DownloadFailed; + default: + return TrackedDownloadStage.Downloading; + } + } + + public void Handle(SceneMappingsUpdatedEvent message) + { + _cache.Clear(); + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Core/Download/TrackedDownloadStatusMessage.cs b/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadStatusMessage.cs similarity index 84% rename from src/NzbDrone.Core/Download/TrackedDownloadStatusMessage.cs rename to src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadStatusMessage.cs index 0f03d2279..63a2758e1 100644 --- a/src/NzbDrone.Core/Download/TrackedDownloadStatusMessage.cs +++ b/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadStatusMessage.cs @@ -1,16 +1,13 @@ using System; using System.Collections.Generic; -namespace NzbDrone.Core.Download +namespace NzbDrone.Core.Download.TrackedDownloads { public class TrackedDownloadStatusMessage { public String Title { get; set; } public List Messages { get; set; } - private TrackedDownloadStatusMessage() - { - } public TrackedDownloadStatusMessage(String title, List messages) { diff --git a/src/NzbDrone.Core/HealthCheck/Checks/ImportMechanismCheck.cs b/src/NzbDrone.Core/HealthCheck/Checks/ImportMechanismCheck.cs index 8ad3933f7..373d47bfb 100644 --- a/src/NzbDrone.Core/HealthCheck/Checks/ImportMechanismCheck.cs +++ b/src/NzbDrone.Core/HealthCheck/Checks/ImportMechanismCheck.cs @@ -2,8 +2,8 @@ using NzbDrone.Common.Disk; using NzbDrone.Core.Configuration; using NzbDrone.Core.Download; -using NzbDrone.Core.Download.Clients.Sabnzbd; using NzbDrone.Core.Download.Clients.Nzbget; +using NzbDrone.Core.Download.Clients.Sabnzbd; namespace NzbDrone.Core.HealthCheck.Checks { @@ -11,13 +11,12 @@ namespace NzbDrone.Core.HealthCheck.Checks { private readonly IConfigService _configService; private readonly IProvideDownloadClient _provideDownloadClient; - private readonly IDownloadTrackingService _downloadTrackingService; - public ImportMechanismCheck(IConfigService configService, IProvideDownloadClient provideDownloadClient, IDownloadTrackingService downloadTrackingService) + + public ImportMechanismCheck(IConfigService configService, IProvideDownloadClient provideDownloadClient) { _configService = configService; _provideDownloadClient = provideDownloadClient; - _downloadTrackingService = downloadTrackingService; } public override HealthCheck Check() @@ -65,13 +64,6 @@ namespace NzbDrone.Core.HealthCheck.Checks return new HealthCheck(GetType(), HealthCheckResult.Warning, "Enable Completed Download Handling or configure Drone factory"); } - if (_configService.EnableCompletedDownloadHandling && !droneFactoryFolder.IsEmpty) - { - if (_downloadTrackingService.GetCompletedDownloads().Any(v => droneFactoryFolder.Contains(v.DownloadItem.OutputPath))) - { - return new HealthCheck(GetType(), HealthCheckResult.Warning, "Completed Download Handling conflict with Drone Factory (Conflicting History Item)", "Migrating-to-Completed-Download-Handling#conflicting-download-client-category"); - } - } return new HealthCheck(GetType()); } diff --git a/src/NzbDrone.Core/History/History.cs b/src/NzbDrone.Core/History/History.cs index 21341430a..be35637c8 100644 --- a/src/NzbDrone.Core/History/History.cs +++ b/src/NzbDrone.Core/History/History.cs @@ -8,6 +8,8 @@ namespace NzbDrone.Core.History { public class History : ModelBase { + public const string DOWNLOAD_CLIENT = "downloadClient"; + public History() { Data = new Dictionary(); @@ -22,6 +24,9 @@ namespace NzbDrone.Core.History public Series Series { get; set; } public HistoryEventType EventType { get; set; } public Dictionary Data { get; set; } + + public string DownloadId { get; set; } + } public enum HistoryEventType diff --git a/src/NzbDrone.Core/History/HistoryRepository.cs b/src/NzbDrone.Core/History/HistoryRepository.cs index b76a3be12..1e1013b98 100644 --- a/src/NzbDrone.Core/History/HistoryRepository.cs +++ b/src/NzbDrone.Core/History/HistoryRepository.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using Marr.Data.QGen; using NzbDrone.Core.Datastore; @@ -11,31 +10,21 @@ namespace NzbDrone.Core.History { public interface IHistoryRepository : IBasicRepository { - void Trim(); List GetBestQualityInHistory(int episodeId); - List BetweenDates(DateTime startDate, DateTime endDate, HistoryEventType eventType); - List Failed(); - List Grabbed(); - List Imported(); History MostRecentForEpisode(int episodeId); - List FindBySourceTitle(string sourceTitle); + History MostRecentForDownloadId(string downloadId); + List FindByDownloadId(string downloadId); + List FindDownloadHistory(int idSeriesId, QualityModel quality); } public class HistoryRepository : BasicRepository, IHistoryRepository { - private readonly IDatabase _database; public HistoryRepository(IDatabase database, IEventAggregator eventAggregator) : base(database, eventAggregator) { - _database = database; } - public void Trim() - { - var cutoff = DateTime.UtcNow.AddDays(-30).Date; - Delete(c=> c.Date < cutoff); - } public List GetBestQualityInHistory(int episodeId) { @@ -44,30 +33,6 @@ namespace NzbDrone.Core.History return history.Select(h => h.Quality).ToList(); } - public List BetweenDates(DateTime startDate, DateTime endDate, HistoryEventType eventType) - { - return Query.Join(JoinType.Inner, h => h.Series, (h, s) => h.SeriesId == s.Id) - .Join(JoinType.Inner, h => h.Episode, (h, e) => h.EpisodeId == e.Id) - .Where(h => h.Date >= startDate) - .AndWhere(h => h.Date <= endDate) - .AndWhere(h => h.EventType == eventType); - } - - public List Failed() - { - return Query.Where(h => h.EventType == HistoryEventType.DownloadFailed); - } - - public List Grabbed() - { - return Query.Where(h => h.EventType == HistoryEventType.Grabbed); - } - - public List Imported() - { - return Query.Where(h => h.EventType == HistoryEventType.DownloadFolderImported); - } - public History MostRecentForEpisode(int episodeId) { return Query.Where(h => h.EpisodeId == episodeId) @@ -75,14 +40,27 @@ namespace NzbDrone.Core.History .FirstOrDefault(); } - public List FindBySourceTitle(string sourceTitle) + public History MostRecentForDownloadId(string downloadId) + { + return Query.Where(h => h.DownloadId == downloadId) + .OrderByDescending(h => h.Date) + .FirstOrDefault(); + } + + public List FindByDownloadId(string downloadId) { - return Query.Where(h => h.SourceTitle.Contains(sourceTitle)); + return Query.Where(h => h.DownloadId == downloadId); } - public List AllForEpisode(int episodeId) + public List FindDownloadHistory(int idSeriesId, QualityModel quality) { - return Query.Where(h => h.EpisodeId == episodeId); + return Query.Where(h => + h.SeriesId == idSeriesId && + h.Quality == quality && + (h.EventType == HistoryEventType.Grabbed || + h.EventType == HistoryEventType.DownloadFailed || + h.EventType == HistoryEventType.DownloadFolderImported) + ).ToList(); } protected override SortBuilder GetPagedQuery(QueryBuilder query, PagingSpec pagingSpec) diff --git a/src/NzbDrone.Core/History/HistoryService.cs b/src/NzbDrone.Core/History/HistoryService.cs index 0d8493ff3..e550964be 100644 --- a/src/NzbDrone.Core/History/HistoryService.cs +++ b/src/NzbDrone.Core/History/HistoryService.cs @@ -16,19 +16,12 @@ namespace NzbDrone.Core.History { public interface IHistoryService { - List All(); - void Purge(); - void Trim(); QualityModel GetBestQualityInHistory(Profile profile, int episodeId); PagingSpec Paged(PagingSpec pagingSpec); - List BetweenDates(DateTime startDate, DateTime endDate, HistoryEventType eventType); - List Failed(); - List Grabbed(); - List Imported(); History MostRecentForEpisode(int episodeId); - History Get(int id); - List FindBySourceTitle(string sourceTitle); - void UpdateHistoryData(Int32 historyId, Dictionary data); + History MostRecentForDownloadId(string downloadId); + History Get(int historyId); + List Find(string downloadId, HistoryEventType eventType); } public class HistoryService : IHistoryService, @@ -46,60 +39,31 @@ namespace NzbDrone.Core.History _logger = logger; } - public List All() - { - return _historyRepository.All().ToList(); - } - public PagingSpec Paged(PagingSpec pagingSpec) { return _historyRepository.GetPaged(pagingSpec); } - public List BetweenDates(DateTime startDate, DateTime endDate, HistoryEventType eventType) - { - return _historyRepository.BetweenDates(startDate, endDate, eventType); - } - - public List Failed() - { - return _historyRepository.Failed(); - } - - public List Grabbed() - { - return _historyRepository.Grabbed(); - } - - public List Imported() - { - return _historyRepository.Imported(); - } - public History MostRecentForEpisode(int episodeId) { return _historyRepository.MostRecentForEpisode(episodeId); } - public History Get(int id) + public History MostRecentForDownloadId(string downloadId) { - return _historyRepository.Get(id); + return _historyRepository.MostRecentForDownloadId(downloadId); } - public List FindBySourceTitle(string sourceTitle) + public History Get(int historyId) { - return _historyRepository.FindBySourceTitle(sourceTitle); + return _historyRepository.Get(historyId); } - public void Purge() + public List Find(string downloadId, HistoryEventType eventType) { - _historyRepository.Purge(); + return _historyRepository.FindByDownloadId(downloadId).Where(c => c.EventType == eventType).ToList(); } - public virtual void Trim() - { - _historyRepository.Trim(); - } public QualityModel GetBestQualityInHistory(Profile profile, int episodeId) { @@ -109,13 +73,6 @@ namespace NzbDrone.Core.History .FirstOrDefault(); } - public void UpdateHistoryData(Int32 historyId, Dictionary data) - { - var history = _historyRepository.Get(historyId); - history.Data = data; - _historyRepository.Update(history); - } - public void Handle(EpisodeGrabbedEvent message) { foreach (var episode in message.Episode.Episodes) @@ -128,6 +85,7 @@ namespace NzbDrone.Core.History SourceTitle = message.Episode.Release.Title, SeriesId = episode.SeriesId, EpisodeId = episode.Id, + DownloadId = message.DownloadId }; history.Data.Add("Indexer", message.Episode.Release.Indexer); @@ -138,11 +96,6 @@ namespace NzbDrone.Core.History history.Data.Add("PublishedDate", message.Episode.Release.PublishDate.ToString("s") + "Z"); history.Data.Add("DownloadClient", message.DownloadClient); - if (!String.IsNullOrWhiteSpace(message.DownloadClientId)) - { - history.Data.Add("DownloadClientId", message.DownloadClientId); - } - if (!message.Episode.ParsedEpisodeInfo.ReleaseHash.IsNullOrWhiteSpace()) { history.Data.Add("ReleaseHash", message.Episode.ParsedEpisodeInfo.ReleaseHash); @@ -159,6 +112,13 @@ namespace NzbDrone.Core.History return; } + var downloadId = message.DownloadId; + + if (downloadId.IsNullOrWhiteSpace()) + { + downloadId = FindDownloadId(message); + } + foreach (var episode in message.EpisodeInfo.Episodes) { var history = new History @@ -168,7 +128,8 @@ namespace NzbDrone.Core.History Quality = message.EpisodeInfo.Quality, SourceTitle = message.ImportedEpisode.SceneName ?? Path.GetFileNameWithoutExtension(message.EpisodeInfo.Path), SeriesId = message.ImportedEpisode.SeriesId, - EpisodeId = episode.Id + EpisodeId = episode.Id, + DownloadId = downloadId }; //Won't have a value since we publish this event before saving to DB. @@ -176,12 +137,57 @@ namespace NzbDrone.Core.History history.Data.Add("DroppedPath", message.EpisodeInfo.Path); history.Data.Add("ImportedPath", Path.Combine(message.EpisodeInfo.Series.Path, message.ImportedEpisode.RelativePath)); history.Data.Add("DownloadClient", message.DownloadClient); - history.Data.Add("DownloadClientId", message.DownloadClientId); _historyRepository.Insert(history); } } + + private string FindDownloadId(EpisodeImportedEvent trackedDownload) + { + _logger.Debug("Trying to find downloadId for {0} from history", trackedDownload.ImportedEpisode.Path); + + var episodeIds = trackedDownload.EpisodeInfo.Episodes.Select(c => c.Id).ToList(); + + var allHistory = _historyRepository.FindDownloadHistory(trackedDownload.EpisodeInfo.Series.Id, trackedDownload.ImportedEpisode.Quality); + + + //Find download related items for these episdoes + var episodesHistory = allHistory.Where(h => episodeIds.Contains(h.EpisodeId)).ToList(); + + var processedDownloadId = episodesHistory + .Where(c => c.EventType != HistoryEventType.Grabbed && c.DownloadId != null) + .Select(c => c.DownloadId); + + var stillDownloading = episodesHistory.Where(c => c.EventType == HistoryEventType.Grabbed && !processedDownloadId.Contains(c.DownloadId)).ToList(); + + string downloadId = null; + + if (stillDownloading.Any()) + { + foreach (var matchingHistory in trackedDownload.EpisodeInfo.Episodes.Select(e => stillDownloading.Where(c => c.EpisodeId == e.Id).ToList())) + { + if (matchingHistory.Count != 1) + { + return null; + } + + var newDownloadId = matchingHistory.Single().DownloadId; + + if (downloadId == null || downloadId == newDownloadId) + { + downloadId = newDownloadId; + } + else + { + return null; + } + } + } + + return downloadId; + } + public void Handle(DownloadFailedEvent message) { foreach (var episodeId in message.EpisodeIds) @@ -194,10 +200,10 @@ namespace NzbDrone.Core.History SourceTitle = message.SourceTitle, SeriesId = message.SeriesId, EpisodeId = episodeId, + DownloadId = message.DownloadId }; history.Data.Add("DownloadClient", message.DownloadClient); - history.Data.Add("DownloadClientId", message.DownloadClientId); history.Data.Add("Message", message.Message); _historyRepository.Insert(history); diff --git a/src/NzbDrone.Core/MediaFiles/Commands/DownloadedEpisodesScanCommand.cs b/src/NzbDrone.Core/MediaFiles/Commands/DownloadedEpisodesScanCommand.cs index c5424d608..4d09685e8 100644 --- a/src/NzbDrone.Core/MediaFiles/Commands/DownloadedEpisodesScanCommand.cs +++ b/src/NzbDrone.Core/MediaFiles/Commands/DownloadedEpisodesScanCommand.cs @@ -14,7 +14,5 @@ namespace NzbDrone.Core.MediaFiles.Commands } public Boolean SendUpdates { get; set; } - public String Path { get; set; } - public String DownloadClientId { get; set; } } } \ No newline at end of file diff --git a/src/NzbDrone.Core/MediaFiles/DownloadedEpisodesCommandService.cs b/src/NzbDrone.Core/MediaFiles/DownloadedEpisodesCommandService.cs index 42627de70..ff8caf8ab 100644 --- a/src/NzbDrone.Core/MediaFiles/DownloadedEpisodesCommandService.cs +++ b/src/NzbDrone.Core/MediaFiles/DownloadedEpisodesCommandService.cs @@ -4,9 +4,7 @@ using System.IO; using System.Linq; using NLog; using NzbDrone.Common.Disk; -using NzbDrone.Common.Extensions; using NzbDrone.Core.Configuration; -using NzbDrone.Core.Download; using NzbDrone.Core.MediaFiles.Commands; using NzbDrone.Core.MediaFiles.EpisodeImport; using NzbDrone.Core.Messaging.Commands; @@ -16,22 +14,16 @@ namespace NzbDrone.Core.MediaFiles public class DownloadedEpisodesCommandService : IExecute { private readonly IDownloadedEpisodesImportService _downloadedEpisodesImportService; - private readonly IDownloadTrackingService _downloadTrackingService; - private readonly ICompletedDownloadService _completedDownloadService; private readonly IDiskProvider _diskProvider; private readonly IConfigService _configService; private readonly Logger _logger; public DownloadedEpisodesCommandService(IDownloadedEpisodesImportService downloadedEpisodesImportService, - IDownloadTrackingService downloadTrackingService, - ICompletedDownloadService completedDownloadService, IDiskProvider diskProvider, IConfigService configService, Logger logger) { _downloadedEpisodesImportService = downloadedEpisodesImportService; - _downloadTrackingService = downloadTrackingService; - _completedDownloadService = completedDownloadService; _diskProvider = diskProvider; _configService = configService; _logger = logger; @@ -56,43 +48,12 @@ namespace NzbDrone.Core.MediaFiles return _downloadedEpisodesImportService.ProcessRootFolder(new DirectoryInfo(downloadedEpisodesFolder)); } - private List ProcessFolder(DownloadedEpisodesScanCommand message) - { - if (!_diskProvider.FolderExists(message.Path)) - { - _logger.Warn("Folder specified for import scan [{0}] doesn't exist.", message.Path); - return new List(); - } - if (message.DownloadClientId.IsNotNullOrWhiteSpace()) - { - var trackedDownload = _downloadTrackingService.GetQueuedDownloads().Where(v => v.DownloadItem.DownloadClientId == message.DownloadClientId).FirstOrDefault(); - - if (trackedDownload == null) - { - _logger.Warn("External directory scan request for unknown download {0}, attempting normal import. [{1}]", message.DownloadClientId, message.Path); - - return _downloadedEpisodesImportService.ProcessFolder(new DirectoryInfo(message.Path)); - } - return _completedDownloadService.Import(trackedDownload, message.Path); - } - return _downloadedEpisodesImportService.ProcessFolder(new DirectoryInfo(message.Path)); - } public void Execute(DownloadedEpisodesScanCommand message) { - List importResults; - - if (message.Path.IsNotNullOrWhiteSpace()) - { - importResults = ProcessFolder(message); - } - else - { - importResults = ProcessDroneFactoryFolder(); - } - - if (importResults == null || !importResults.Any(v => v.Result == ImportResultType.Imported)) + var importResults = ProcessDroneFactoryFolder(); + if (importResults == null || importResults.All(v => v.Result != ImportResultType.Imported)) { // Atm we don't report it as a command failure, coz that would cause the download to be failed. // Changing the message won't do a thing either, coz it will get set to 'Completed' a msec later. diff --git a/src/NzbDrone.Core/MediaFiles/DownloadedEpisodesImportService.cs b/src/NzbDrone.Core/MediaFiles/DownloadedEpisodesImportService.cs index 201e80f54..bd27654a6 100644 --- a/src/NzbDrone.Core/MediaFiles/DownloadedEpisodesImportService.cs +++ b/src/NzbDrone.Core/MediaFiles/DownloadedEpisodesImportService.cs @@ -16,9 +16,7 @@ namespace NzbDrone.Core.MediaFiles { List ProcessRootFolder(DirectoryInfo directoryInfo); List ProcessFolder(DirectoryInfo directoryInfo, DownloadClientItem downloadClientItem = null); - List ProcessFolder(DirectoryInfo directoryInfo, Series series, DownloadClientItem downloadClientItem = null); - List ProcessFile(FileInfo fileInfo, DownloadClientItem downloadClientItem = null); - List ProcessFile(FileInfo fileInfo, Series series, DownloadClientItem downloadClientItem = null); + List ProcessPath(string path, DownloadClientItem downloadClientItem = null); } public class DownloadedEpisodesImportService : IDownloadedEpisodesImportService @@ -88,12 +86,12 @@ namespace NzbDrone.Core.MediaFiles return ProcessFolder(directoryInfo, series, downloadClientItem); } - public List ProcessFolder(DirectoryInfo directoryInfo, Series series, + private List ProcessFolder(DirectoryInfo directoryInfo, Series series, DownloadClientItem downloadClientItem = null) { if (_seriesService.SeriesPathExists(directoryInfo.FullName)) { - _logger.Warn("Unable to process folder that contains sorted TV Shows"); + _logger.Warn("Unable to process folder that is mapped to an existing show"); return new List(); } @@ -147,7 +145,7 @@ namespace NzbDrone.Core.MediaFiles return ProcessFile(fileInfo, series, downloadClientItem); } - public List ProcessFile(FileInfo fileInfo, Series series, DownloadClientItem downloadClientItem = null) + private List ProcessFile(FileInfo fileInfo, Series series, DownloadClientItem downloadClientItem = null) { if (downloadClientItem == null) { @@ -164,6 +162,16 @@ namespace NzbDrone.Core.MediaFiles return _importApprovedEpisodes.Import(decisions, true, downloadClientItem); } + public List ProcessPath(string path, DownloadClientItem downloadClientItem = null) + { + if (_diskProvider.FolderExists(path)) + { + return ProcessFolder(new DirectoryInfo(path), downloadClientItem); + } + + return ProcessFile(new FileInfo(path), downloadClientItem); + } + private string GetCleanedUpFolderName(string folder) { folder = folder.Replace("_UNPACK_", "") diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs index 2d68516a8..ea106f0d9 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs @@ -99,7 +99,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport if (downloadClientItem != null) { - _eventAggregator.PublishEvent(new EpisodeImportedEvent(localEpisode, episodeFile, newDownload, downloadClientItem.DownloadClient, downloadClientItem.DownloadClientId)); + _eventAggregator.PublishEvent(new EpisodeImportedEvent(localEpisode, episodeFile, newDownload, downloadClientItem.DownloadClient, downloadClientItem.DownloadId)); } else { diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportDecision.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportDecision.cs index ec990860b..da7e8d092 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportDecision.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportDecision.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using NzbDrone.Common.Extensions; using NzbDrone.Core.Parser.Model; namespace NzbDrone.Core.MediaFiles.EpisodeImport @@ -13,7 +14,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport { get { - return !Rejections.Any(); + return Rejections.Empty(); } } diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportResult.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportResult.cs index e658ca709..64ccb47b2 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportResult.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportResult.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Linq; using NzbDrone.Common.EnsureThat; -using NzbDrone.Common.Extensions; namespace NzbDrone.Core.MediaFiles.EpisodeImport { @@ -15,14 +14,17 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport { get { - //Approved and imported - if (Errors.Empty()) return ImportResultType.Imported; + if (Errors.Any()) + { + if (ImportDecision.Approved) + { + return ImportResultType.Skipped; + } - //Decision was approved, but it was not imported - if (ImportDecision.Approved) return ImportResultType.Skipped; + return ImportResultType.Rejected; + } - //Decision was rejected - return ImportResultType.Rejected; + return ImportResultType.Imported; } } diff --git a/src/NzbDrone.Core/MediaFiles/Events/EpisodeImportedEvent.cs b/src/NzbDrone.Core/MediaFiles/Events/EpisodeImportedEvent.cs index 445a1b3a8..84b40d7b6 100644 --- a/src/NzbDrone.Core/MediaFiles/Events/EpisodeImportedEvent.cs +++ b/src/NzbDrone.Core/MediaFiles/Events/EpisodeImportedEvent.cs @@ -8,9 +8,9 @@ namespace NzbDrone.Core.MediaFiles.Events { public LocalEpisode EpisodeInfo { get; private set; } public EpisodeFile ImportedEpisode { get; private set; } - public Boolean NewDownload { get; set; } - public String DownloadClient { get; set; } - public String DownloadClientId { get; set; } + public Boolean NewDownload { get; private set; } + public String DownloadClient { get; private set; } + public String DownloadId { get; private set; } public EpisodeImportedEvent(LocalEpisode episodeInfo, EpisodeFile importedEpisode, bool newDownload) { @@ -19,13 +19,13 @@ namespace NzbDrone.Core.MediaFiles.Events NewDownload = newDownload; } - public EpisodeImportedEvent(LocalEpisode episodeInfo, EpisodeFile importedEpisode, bool newDownload, string downloadClient, string downloadClientId) + public EpisodeImportedEvent(LocalEpisode episodeInfo, EpisodeFile importedEpisode, bool newDownload, string downloadClient, string downloadId) { EpisodeInfo = episodeInfo; ImportedEpisode = importedEpisode; NewDownload = newDownload; DownloadClient = downloadClient; - DownloadClientId = downloadClientId; + DownloadId = downloadId; } } } \ No newline at end of file diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index 4c63cd740..b08657bcf 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -231,6 +231,7 @@ + @@ -270,7 +271,6 @@ - @@ -353,7 +353,12 @@ - + + + + + + @@ -367,7 +372,6 @@ - @@ -378,7 +382,6 @@ - @@ -765,9 +768,8 @@ - - + @@ -909,6 +911,7 @@ PreserveNewest + diff --git a/src/NzbDrone.Core/Parser/ParsingService.cs b/src/NzbDrone.Core/Parser/ParsingService.cs index 1726e0395..6bd022d49 100644 --- a/src/NzbDrone.Core/Parser/ParsingService.cs +++ b/src/NzbDrone.Core/Parser/ParsingService.cs @@ -15,8 +15,7 @@ namespace NzbDrone.Core.Parser { LocalEpisode GetLocalEpisode(string filename, Series series, bool sceneSource); Series GetSeries(string title); - RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, Int32 tvRageId, SearchCriteriaBase searchCriteria = null); - RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, Int32 seriesId, IEnumerable episodeIds); + RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, Int32 tvRageId = 0, SearchCriteriaBase searchCriteria = null); List GetEpisodes(ParsedEpisodeInfo parsedEpisodeInfo, Series series, bool sceneSource, SearchCriteriaBase searchCriteria = null); ParsedEpisodeInfo ParseSpecialEpisodeTitle(string title, int tvRageId, SearchCriteriaBase searchCriteria = null); } @@ -119,18 +118,6 @@ namespace NzbDrone.Core.Parser return remoteEpisode; } - public RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, Int32 seriesId, IEnumerable episodeIds) - { - var remoteEpisode = new RemoteEpisode - { - ParsedEpisodeInfo = parsedEpisodeInfo, - Series = _seriesService.GetSeries(seriesId), - Episodes = _episodeService.GetEpisodes(episodeIds) - }; - - return remoteEpisode; - } - public List GetEpisodes(ParsedEpisodeInfo parsedEpisodeInfo, Series series, bool sceneSource, SearchCriteriaBase searchCriteria = null) { var result = new List(); diff --git a/src/NzbDrone.Core/Queue/Queue.cs b/src/NzbDrone.Core/Queue/Queue.cs index 803812735..1f8746b18 100644 --- a/src/NzbDrone.Core/Queue/Queue.cs +++ b/src/NzbDrone.Core/Queue/Queue.cs @@ -1,10 +1,10 @@ using System; using System.Collections.Generic; using NzbDrone.Core.Datastore; -using NzbDrone.Core.Download; +using NzbDrone.Core.Download.TrackedDownloads; +using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Qualities; using NzbDrone.Core.Tv; -using NzbDrone.Core.Parser.Model; namespace NzbDrone.Core.Queue { @@ -21,7 +21,7 @@ namespace NzbDrone.Core.Queue public String Status { get; set; } public String TrackedDownloadStatus { get; set; } public List StatusMessages { get; set; } - public RemoteEpisode RemoteEpisode { get; set; } public String TrackingId { get; set; } + public RemoteEpisode RemoteEpisode { get; set; } } } diff --git a/src/NzbDrone.Core/Queue/QueueScheduler.cs b/src/NzbDrone.Core/Queue/QueueScheduler.cs deleted file mode 100644 index 55a8d69e4..000000000 --- a/src/NzbDrone.Core/Queue/QueueScheduler.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; -using NLog; -using NzbDrone.Common.TPL; -using NzbDrone.Core.Lifecycle; -using NzbDrone.Core.Messaging.Events; -using Timer = System.Timers.Timer; - -namespace NzbDrone.Core.Queue -{ - public class QueueScheduler : IHandle, - IHandle - { - private readonly IEventAggregator _eventAggregator; - private readonly Logger _logger; - private static readonly Timer Timer = new Timer(); - private static CancellationTokenSource _cancellationTokenSource; - - public QueueScheduler(IEventAggregator eventAggregator, Logger logger) - { - _eventAggregator = eventAggregator; - _logger = logger; - } - - private void CheckQueue() - { - try - { - Timer.Enabled = false; - _eventAggregator.PublishEvent(new UpdateQueueEvent()); - } - finally - { - if (!_cancellationTokenSource.IsCancellationRequested) - { - Timer.Enabled = true; - } - } - } - - public void Handle(ApplicationStartedEvent message) - { - _cancellationTokenSource = new CancellationTokenSource(); - Timer.Interval = 1000 * 30; - Timer.Elapsed += (o, args) => Task.Factory.StartNew(CheckQueue, _cancellationTokenSource.Token) - .LogExceptions(); - - Timer.Start(); - } - - public void Handle(ApplicationShutdownRequested message) - { - _logger.Info("Shutting down queue scheduler"); - _cancellationTokenSource.Cancel(true); - Timer.Stop(); - } - } -} diff --git a/src/NzbDrone.Core/Queue/QueueService.cs b/src/NzbDrone.Core/Queue/QueueService.cs index cfd87c944..525c5d66e 100644 --- a/src/NzbDrone.Core/Queue/QueueService.cs +++ b/src/NzbDrone.Core/Queue/QueueService.cs @@ -1,7 +1,8 @@ using System; using System.Linq; using System.Collections.Generic; -using NzbDrone.Core.Download; +using NzbDrone.Core.Download.TrackedDownloads; +using NzbDrone.Core.Messaging.Events; namespace NzbDrone.Core.Queue { @@ -11,64 +12,63 @@ namespace NzbDrone.Core.Queue Queue Find(int id); } - public class QueueService : IQueueService + public class QueueService : IQueueService, IHandle { - private readonly IDownloadTrackingService _downloadTrackingService; + private readonly IEventAggregator _eventAggregator; + private static List _queue = new List(); - public QueueService(IDownloadTrackingService downloadTrackingService) + public QueueService(IEventAggregator eventAggregator) { - _downloadTrackingService = downloadTrackingService; + _eventAggregator = eventAggregator; } public List GetQueue() { - var queueItems = _downloadTrackingService.GetQueuedDownloads() - .OrderBy(v => v.DownloadItem.RemainingTime) - .ToList(); - - return MapQueue(queueItems); + return _queue; } public Queue Find(int id) { - return GetQueue().SingleOrDefault(q => q.Id == id); + return _queue.SingleOrDefault(q => q.Id == id); } - private List MapQueue(IEnumerable trackedDownloads) + public void Handle(TrackedDownloadRefreshedEvent message) { - var queued = new List(); + _queue = message.TrackedDownloads.OrderBy(c => c.DownloadItem.RemainingTime).SelectMany(MapQueue) + .ToList(); + + _eventAggregator.PublishEvent(new QueueUpdatedEvent()); + } - foreach (var trackedDownload in trackedDownloads) + private static IEnumerable MapQueue(TrackedDownload trackedDownload) + { + foreach (var episode in trackedDownload.RemoteEpisode.Episodes) { - foreach (var episode in trackedDownload.RemoteEpisode.Episodes) + var queue = new Queue { - var queue = new Queue - { - Id = episode.Id ^ (trackedDownload.DownloadItem.DownloadClientId.GetHashCode() << 16), - Series = trackedDownload.RemoteEpisode.Series, - Episode = episode, - Quality = trackedDownload.RemoteEpisode.ParsedEpisodeInfo.Quality, - Title = trackedDownload.DownloadItem.Title, - Size = trackedDownload.DownloadItem.TotalSize, - Sizeleft = trackedDownload.DownloadItem.RemainingSize, - Timeleft = trackedDownload.DownloadItem.RemainingTime, - Status = trackedDownload.DownloadItem.Status.ToString(), - RemoteEpisode = trackedDownload.RemoteEpisode, - TrackedDownloadStatus = trackedDownload.Status.ToString(), - StatusMessages = trackedDownload.StatusMessages, - TrackingId = trackedDownload.TrackingId - }; - - if (queue.Timeleft.HasValue) - { - queue.EstimatedCompletionTime = DateTime.UtcNow.Add(queue.Timeleft.Value); - } + Id = episode.Id ^ (trackedDownload.DownloadItem.DownloadId.GetHashCode() << 16), + Series = trackedDownload.RemoteEpisode.Series, + Episode = episode, + Quality = trackedDownload.RemoteEpisode.ParsedEpisodeInfo.Quality, + Title = trackedDownload.DownloadItem.Title, + Size = trackedDownload.DownloadItem.TotalSize, + Sizeleft = trackedDownload.DownloadItem.RemainingSize, + Timeleft = trackedDownload.DownloadItem.RemainingTime, + Status = trackedDownload.DownloadItem.Status.ToString(), + TrackedDownloadStatus = trackedDownload.Status.ToString(), + StatusMessages = trackedDownload.StatusMessages.ToList(), + RemoteEpisode = trackedDownload.RemoteEpisode, + TrackingId = trackedDownload.TrackingId + }; - queued.Add(queue); + if (queue.Timeleft.HasValue) + { + queue.EstimatedCompletionTime = DateTime.UtcNow.Add(queue.Timeleft.Value); } + + yield return queue; } - return queued; } } } diff --git a/src/NzbDrone.Core/Queue/UpdateQueueEvent.cs b/src/NzbDrone.Core/Queue/QueueUpdatedEvent.cs similarity index 65% rename from src/NzbDrone.Core/Queue/UpdateQueueEvent.cs rename to src/NzbDrone.Core/Queue/QueueUpdatedEvent.cs index 38535a89d..c9ce3bbde 100644 --- a/src/NzbDrone.Core/Queue/UpdateQueueEvent.cs +++ b/src/NzbDrone.Core/Queue/QueueUpdatedEvent.cs @@ -2,7 +2,7 @@ namespace NzbDrone.Core.Queue { - public class UpdateQueueEvent : IEvent + public class QueueUpdatedEvent : IEvent { } } diff --git a/src/UI/Activity/History/Details/HistoryDetailsViewTemplate.hbs b/src/UI/Activity/History/Details/HistoryDetailsViewTemplate.hbs index 0bdde911a..89a757660 100644 --- a/src/UI/Activity/History/Details/HistoryDetailsViewTemplate.hbs +++ b/src/UI/Activity/History/Details/HistoryDetailsViewTemplate.hbs @@ -25,9 +25,9 @@
{{downloadClient}}
{{/if}} - {{#if downloadClientId}} -
Download Client ID:
-
{{downloadClientId}}
+ {{#if downloadId}} +
Grab ID:
+
{{downloadId}}
{{/if}} {{#if age}} diff --git a/src/UI/Settings/DownloadClient/DownloadHandling/DownloadHandlingView.js b/src/UI/Settings/DownloadClient/DownloadHandling/DownloadHandlingView.js index d88d628ef..944913b79 100644 --- a/src/UI/Settings/DownloadClient/DownloadHandling/DownloadHandlingView.js +++ b/src/UI/Settings/DownloadClient/DownloadHandling/DownloadHandlingView.js @@ -12,20 +12,20 @@ define( ui: { completedDownloadHandlingCheckbox : '.x-completed-download-handling', completedDownloadOptions : '.x-completed-download-options', - failedDownloadHandlingCheckbox : '.x-failed-download-handling', + failedAutoRedownladCheckbox : '.x-failed-auto-redownload', failedDownloadOptions : '.x-failed-download-options' }, events: { 'change .x-completed-download-handling' : '_setCompletedDownloadOptionsVisibility', - 'change .x-failed-download-handling' : '_setFailedDownloadOptionsVisibility' + 'change .x-failed-auto-redownload' : '_setFailedDownloadOptionsVisibility' }, onRender: function () { if (!this.ui.completedDownloadHandlingCheckbox.prop('checked')) { this.ui.completedDownloadOptions.hide(); } - if (!this.ui.failedDownloadHandlingCheckbox.prop('checked')) { + if (!this.ui.failedAutoRedownladCheckbox.prop('checked')) { this.ui.failedDownloadOptions.hide(); } }, @@ -42,7 +42,7 @@ define( }, _setFailedDownloadOptionsVisibility: function () { - var checked = this.ui.failedDownloadHandlingCheckbox.prop('checked'); + var checked = this.ui.failedAutoRedownladCheckbox.prop('checked'); if (checked) { this.ui.failedDownloadOptions.slideDown(); } diff --git a/src/UI/Settings/DownloadClient/DownloadHandling/DownloadHandlingViewTemplate.hbs b/src/UI/Settings/DownloadClient/DownloadHandling/DownloadHandlingViewTemplate.hbs index d32b8f72f..62315a562 100644 --- a/src/UI/Settings/DownloadClient/DownloadHandling/DownloadHandlingViewTemplate.hbs +++ b/src/UI/Settings/DownloadClient/DownloadHandling/DownloadHandlingViewTemplate.hbs @@ -14,15 +14,13 @@
- - - +
- + - +
@@ -44,20 +42,19 @@
- + - -
+ +
Failed Download Handling -
- +
-
- -
-
- - -
-
-
- -
+
+
+
-
- -
- - -
- -
- -
- -
-
- -
- - -
- -
- -
- -
-
- -
- - -
- -
- -
- -
-
\ No newline at end of file diff --git a/src/UI/Settings/DownloadClient/DroneFactory/DroneFactoryViewTemplate.hbs b/src/UI/Settings/DownloadClient/DroneFactory/DroneFactoryViewTemplate.hbs index afd61ea24..236f993bf 100644 --- a/src/UI/Settings/DownloadClient/DroneFactory/DroneFactoryViewTemplate.hbs +++ b/src/UI/Settings/DownloadClient/DroneFactory/DroneFactoryViewTemplate.hbs @@ -1,11 +1,12 @@ -
+
Drone Factory Options
- + +
@@ -13,7 +14,7 @@
-
+