New: Rebuilt Completed/Failed download handling from scratch
This commit is contained in:
parent
264bb66c16
commit
a6d34caf2c
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>(TModel model)
|
||||
{
|
||||
var resource = base.ToResource<TModel>(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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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; }
|
||||
|
||||
|
|
|
@ -63,10 +63,6 @@
|
|||
<Reference Include="DDay.iCal">
|
||||
<HintPath>..\packages\DDay.iCal.1.0.2.575\lib\DDay.iCal.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.AspNet.SignalR.Core, Version=1.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\packages\Microsoft.AspNet.SignalR.Core.1.1.3\lib\net40\Microsoft.AspNet.SignalR.Core.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="NLog">
|
||||
<HintPath>..\packages\NLog.2.1.0\lib\net40\NLog.dll</HintPath>
|
||||
</Reference>
|
||||
|
|
|
@ -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<QueueResource>
|
||||
{
|
||||
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<QueueResource>();
|
||||
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)
|
||||
{
|
||||
|
|
|
@ -9,7 +9,7 @@ using NzbDrone.SignalR;
|
|||
namespace NzbDrone.Api.Queue
|
||||
{
|
||||
public class QueueModule : NzbDroneRestModuleWithSignalR<QueueResource, Core.Queue.Queue>,
|
||||
IHandle<UpdateQueueEvent>, IHandle<PendingReleasesUpdatedEvent>
|
||||
IHandle<QueueUpdatedEvent>, IHandle<PendingReleasesUpdatedEvent>
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -88,6 +88,7 @@
|
|||
<Compile Include="ReflectionTests\ReflectionExtensionFixture.cs" />
|
||||
<Compile Include="ServiceFactoryFixture.cs" />
|
||||
<Compile Include="ServiceProviderTests.cs" />
|
||||
<Compile Include="TPLTests\DebouncerFixture.cs" />
|
||||
<Compile Include="WebClientTests.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -182,6 +182,7 @@
|
|||
<Compile Include="ServiceProvider.cs" />
|
||||
<Compile Include="Extensions\StringExtensions.cs" />
|
||||
<Compile Include="TinyIoC.cs" />
|
||||
<Compile Include="TPL\Debouncer.cs" />
|
||||
<Compile Include="TPL\LimitedConcurrencyLevelTaskScheduler.cs" />
|
||||
<Compile Include="TPL\TaskExtensions.cs" />
|
||||
<Compile Include="Extensions\TryParseExtensions.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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<IRedownloadFailedDownloads>()
|
||||
.Verify(v => v.Redownload(_event.SeriesId, _event.EpisodeIds), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_add_to_repository()
|
||||
|
|
|
@ -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<history_downloadId>
|
||||
{
|
||||
[Test]
|
||||
public void should_move_grab_id_from_date_to_columns()
|
||||
{
|
||||
WithTestDb(c =>
|
||||
{
|
||||
InsertHistory(c, new Dictionary<string, string>
|
||||
{
|
||||
{"indexer","test"},
|
||||
{"downloadClientId","123"}
|
||||
});
|
||||
|
||||
InsertHistory(c, new Dictionary<string, string>
|
||||
{
|
||||
{"indexer","test"},
|
||||
{"downloadClientId","abc"}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
var allProfiles = Mocker.Resolve<HistoryRepository>().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<string, string>
|
||||
{
|
||||
{"indexer","test"},
|
||||
{"downloadClientId","123"}
|
||||
});
|
||||
|
||||
InsertHistory(c, new Dictionary<string, string>
|
||||
{
|
||||
{"indexer","test"}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
var allProfiles = Mocker.Resolve<HistoryRepository>().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<string, string>
|
||||
{
|
||||
{"indexer","test"},
|
||||
{"group","test2"},
|
||||
{"downloadClientId","123"}
|
||||
});
|
||||
});
|
||||
|
||||
var allProfiles = Mocker.Resolve<HistoryRepository>().All().Single();
|
||||
|
||||
allProfiles.Data.Should().NotContainKey("downloadClientId");
|
||||
allProfiles.Data.Should().Contain(new KeyValuePair<string, string>("indexer", "test"));
|
||||
allProfiles.Data.Should().Contain(new KeyValuePair<string, string>("group", "test2"));
|
||||
|
||||
allProfiles.DownloadId.Should().Be("123");
|
||||
}
|
||||
|
||||
|
||||
private void InsertHistory(MigrationBase migrationBase, Dictionary<string, string> data)
|
||||
{
|
||||
migrationBase.Insert.IntoTable("History").Row(new
|
||||
{
|
||||
EpisodeId = 1,
|
||||
SeriesId = 1,
|
||||
SourceTitle = "Test",
|
||||
Date = DateTime.Now,
|
||||
Quality = "{}",
|
||||
Data = data.ToJson(),
|
||||
EventType = 1
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<RemoteEpisode>.CreateNew()
|
||||
.With(r => r.Series = _series)
|
||||
.With(r => r.Episodes = new List<Episode> { _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<IDownloadTrackingService>()
|
||||
.Setup(s => s.GetQueuedDownloads())
|
||||
.Returns(new TrackedDownload[0]);
|
||||
Mocker.GetMock<IQueueService>()
|
||||
.Setup(s => s.GetQueue())
|
||||
.Returns(new List<Queue.Queue>());
|
||||
}
|
||||
|
||||
private void GivenQueue(IEnumerable<RemoteEpisode> remoteEpisodes, TrackedDownloadState state = TrackedDownloadState.Downloading)
|
||||
private void GivenQueue(IEnumerable<RemoteEpisode> remoteEpisodes)
|
||||
{
|
||||
var queue = new List<TrackedDownload>();
|
||||
|
||||
foreach (var remoteEpisode in remoteEpisodes)
|
||||
var queue = remoteEpisodes.Select(remoteEpisode => new Queue.Queue
|
||||
{
|
||||
queue.Add(new TrackedDownload
|
||||
{
|
||||
State = state,
|
||||
RemoteEpisode = remoteEpisode
|
||||
});
|
||||
}
|
||||
RemoteEpisode = remoteEpisode
|
||||
});
|
||||
|
||||
Mocker.GetMock<IDownloadTrackingService>()
|
||||
.Setup(s => s.GetQueuedDownloads())
|
||||
.Returns(queue.ToArray());
|
||||
Mocker.GetMock<IQueueService>()
|
||||
.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<RemoteEpisode>.CreateNew()
|
||||
.With(r => r.Series = _series)
|
||||
.With(r => r.Episodes = new List<Episode> { _episode })
|
||||
.With(r => r.ParsedEpisodeInfo = new ParsedEpisodeInfo
|
||||
{
|
||||
Quality = new QualityModel(Quality.DVD)
|
||||
})
|
||||
.Build();
|
||||
|
||||
GivenQueue(new List<RemoteEpisode> { 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> {_episode})
|
||||
.With(r => r.Episodes = new List<Episode> { _episode })
|
||||
.TheNext(1)
|
||||
.With(r => r.Episodes = new List<Episode> {_otherEpisode})
|
||||
.With(r => r.Episodes = new List<Episode> { _otherEpisode })
|
||||
.Build();
|
||||
|
||||
_remoteEpisode.Episodes.Add(_otherEpisode);
|
||||
|
|
|
@ -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<DownloadTrackingService>
|
||||
public class CompletedDownloadServiceFixture : CoreTest<CompletedDownloadService>
|
||||
{
|
||||
private List<DownloadClientItem> _completed;
|
||||
private TrackedDownload _trackedDownload;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_completed = Builder<DownloadClientItem>.CreateListOfSize(1)
|
||||
.All()
|
||||
var completed = Builder<DownloadClientItem>.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<Episode> {new Episode {Id = 1}}
|
||||
Episodes = new List<Episode> { new Episode { Id = 1 } }
|
||||
};
|
||||
|
||||
Mocker.GetMock<IProvideDownloadClient>()
|
||||
.Setup(c => c.GetDownloadClients())
|
||||
.Returns( new[] { Mocker.GetMock<IDownloadClient>().Object });
|
||||
|
||||
_trackedDownload = Builder<TrackedDownload>.CreateNew()
|
||||
.With(c => c.State = TrackedDownloadStage.Downloading)
|
||||
.With(c => c.DownloadItem = completed)
|
||||
.With(c => c.RemoteEpisode = remoteEpisode)
|
||||
.Build();
|
||||
|
||||
|
||||
Mocker.GetMock<IDownloadClient>()
|
||||
.SetupGet(c => c.Definition)
|
||||
.Returns(new DownloadClientDefinition { Id = 1, Name = "testClient" });
|
||||
.SetupGet(c => c.Definition)
|
||||
.Returns(new DownloadClientDefinition { Id = 1, Name = "testClient" });
|
||||
|
||||
Mocker.GetMock<IConfigService>()
|
||||
.SetupGet(s => s.EnableCompletedDownloadHandling)
|
||||
.Returns(true);
|
||||
|
||||
Mocker.GetMock<IConfigService>()
|
||||
.SetupGet(s => s.RemoveCompletedDownloads)
|
||||
.Returns(true);
|
||||
Mocker.GetMock<IProvideDownloadClient>()
|
||||
.Setup(c => c.Get(It.IsAny<int>()))
|
||||
.Returns(Mocker.GetMock<IDownloadClient>().Object);
|
||||
|
||||
Mocker.GetMock<IHistoryService>()
|
||||
.Setup(s => s.Failed())
|
||||
.Returns(new List<History.History>());
|
||||
.Setup(s => s.MostRecentForDownloadId(_trackedDownload.DownloadItem.DownloadId))
|
||||
.Returns(new History.History());
|
||||
|
||||
Mocker.GetMock<IParsingService>()
|
||||
.Setup(s => s.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<Int32>(), It.IsAny<IEnumerable<Int32>>()))
|
||||
.Returns(remoteEpisode);
|
||||
|
||||
Mocker.GetMock<IParsingService>()
|
||||
.Setup(s => s.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<Int32>(), (SearchCriteriaBase)null))
|
||||
.Returns(remoteEpisode);
|
||||
|
||||
Mocker.SetConstant<ICompletedDownloadService>(Mocker.Resolve<CompletedDownloadService>());
|
||||
}
|
||||
|
||||
private void GivenNoGrabbedHistory()
|
||||
{
|
||||
Mocker.GetMock<IHistoryService>()
|
||||
.Setup(s => s.Grabbed())
|
||||
.Returns(new List<History.History>());
|
||||
.Setup(s => s.MostRecentForDownloadId(_trackedDownload.DownloadItem.DownloadId))
|
||||
.Returns((History.History)null);
|
||||
}
|
||||
|
||||
private void GivenGrabbedHistory(List<History.History> history)
|
||||
{
|
||||
Mocker.GetMock<IHistoryService>()
|
||||
.Setup(s => s.Grabbed())
|
||||
.Returns(history);
|
||||
}
|
||||
|
||||
private void GivenNoImportedHistory()
|
||||
{
|
||||
Mocker.GetMock<IHistoryService>()
|
||||
.Setup(s => s.Imported())
|
||||
.Returns(new List<History.History>());
|
||||
}
|
||||
|
||||
private void GivenImportedHistory(List<History.History> importedHistory)
|
||||
{
|
||||
Mocker.GetMock<IHistoryService>()
|
||||
.Setup(s => s.Imported())
|
||||
.Returns(importedHistory);
|
||||
}
|
||||
|
||||
private void GivenCompletedDownloadClientHistory(bool hasStorage = true)
|
||||
{
|
||||
Mocker.GetMock<IDownloadClient>()
|
||||
.Setup(s => s.GetItems())
|
||||
.Returns(_completed);
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(c => c.FolderExists(It.IsAny<string>()))
|
||||
.Returns(hasStorage);
|
||||
}
|
||||
|
||||
private void GivenCompletedImport()
|
||||
private void GivenSuccessfulImport()
|
||||
{
|
||||
Mocker.GetMock<IDownloadedEpisodesImportService>()
|
||||
.Setup(v => v.ProcessFolder(It.IsAny<DirectoryInfo>(), It.IsAny<Series>(), It.IsAny<DownloadClientItem>()))
|
||||
.Setup(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<DownloadClientItem>()))
|
||||
.Returns(new List<ImportResult>
|
||||
{
|
||||
new ImportResult(new ImportDecision(new LocalEpisode() { Path = @"C:\TestPath\Droned.S01E01.mkv" }))
|
||||
});
|
||||
}
|
||||
|
||||
private void GivenFailedImport()
|
||||
|
||||
[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)
|
||||
{
|
||||
Mocker.GetMock<IDownloadedEpisodesImportService>()
|
||||
.Setup(v => v.ProcessFolder(It.IsAny<DirectoryInfo>(), It.IsAny<Series>(), It.IsAny<DownloadClientItem>()))
|
||||
.Returns(new List<ImportResult>()
|
||||
{
|
||||
new ImportResult(new ImportDecision(new LocalEpisode() { Path = @"C:\TestPath\Droned.S01E01.mkv" }, "Test Failure"))
|
||||
});
|
||||
_trackedDownload.DownloadItem.Status = status;
|
||||
|
||||
Subject.Process(_trackedDownload);
|
||||
|
||||
AssertNoAttemptedImport();
|
||||
}
|
||||
|
||||
private void VerifyNoImports()
|
||||
{
|
||||
Mocker.GetMock<IDownloadedEpisodesImportService>()
|
||||
.Verify(v => v.ProcessFolder(It.IsAny<DirectoryInfo>(), It.IsAny<Series>(), It.IsAny<DownloadClientItem>()), Times.Never());
|
||||
}
|
||||
|
||||
private void VerifyImports()
|
||||
[Test]
|
||||
public void should_not_process_if_matching_history_is_not_found_and_no_category_specified()
|
||||
{
|
||||
Mocker.GetMock<IDownloadedEpisodesImportService>()
|
||||
.Verify(v => v.ProcessFolder(It.IsAny<DirectoryInfo>(), It.IsAny<Series>(), It.IsAny<DownloadClientItem>()), Times.Once());
|
||||
_trackedDownload.DownloadItem.Category = null;
|
||||
GivenNoGrabbedHistory();
|
||||
|
||||
Subject.Process(_trackedDownload);
|
||||
|
||||
AssertNoAttemptedImport();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_process_if_matching_history_is_not_found_but_category_specified()
|
||||
{
|
||||
_completed.First().Category = "tv";
|
||||
|
||||
GivenCompletedDownloadClientHistory();
|
||||
_trackedDownload.DownloadItem.Category = "tv";
|
||||
GivenNoGrabbedHistory();
|
||||
GivenNoImportedHistory();
|
||||
GivenCompletedImport();
|
||||
GivenSuccessfulImport();
|
||||
|
||||
Subject.Execute(new CheckForFinishedDownloadCommand());
|
||||
Subject.Process(_trackedDownload);
|
||||
|
||||
VerifyImports();
|
||||
AssertCompletedDownload();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_process_if_matching_history_is_not_found_and_no_category_specified()
|
||||
{
|
||||
_completed.First().Category = null;
|
||||
|
||||
GivenCompletedDownloadClientHistory();
|
||||
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<History.History>.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<History.History>.CreateListOfSize(1)
|
||||
.Build()
|
||||
.ToList();
|
||||
|
||||
historyGrabbed.First().Data.Add("downloadClient", "SabnzbdClient");
|
||||
historyGrabbed.First().Data.Add("downloadClientId", _completed.First().DownloadClientId);
|
||||
|
||||
GivenGrabbedHistory(historyGrabbed);
|
||||
|
||||
var historyImported = Builder<History.History>.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<History.History>.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());
|
||||
|
||||
VerifyNoImports();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_process_if_not_already_in_imported_history()
|
||||
{
|
||||
GivenCompletedDownloadClientHistory();
|
||||
|
||||
var history = Builder<History.History>.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());
|
||||
|
||||
VerifyImports();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_process_if_storage_directory_does_not_exist()
|
||||
{
|
||||
GivenCompletedDownloadClientHistory(false);
|
||||
|
||||
var history = Builder<History.History>.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<History.History>.CreateListOfSize(1)
|
||||
.Build()
|
||||
.ToList();
|
||||
|
||||
GivenGrabbedHistory(history);
|
||||
GivenNoImportedHistory();
|
||||
|
||||
Mocker.GetMock<IConfigService>()
|
||||
.SetupGet(v => v.DownloadedEpisodesFolder)
|
||||
.Returns(@"C:\DropFolder".AsOsAgnostic());
|
||||
|
||||
history.First().Data.Add("downloadClient", "SabnzbdClient");
|
||||
history.First().Data.Add("downloadClientId", _completed.First().DownloadClientId);
|
||||
_trackedDownload.DownloadItem.OutputPath = new OsPath(@"C:\DropFolder\SomeOtherFolder".AsOsAgnostic());
|
||||
|
||||
Subject.Execute(new CheckForFinishedDownloadCommand());
|
||||
Subject.Process(_trackedDownload);
|
||||
|
||||
VerifyNoImports();
|
||||
|
||||
ExceptionVerification.IgnoreWarns();
|
||||
AssertNoAttemptedImport();
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void should_process_as_already_imported_if_drone_factory_import_history_exists()
|
||||
public void should_not_process_if_output_path_is_empty()
|
||||
{
|
||||
GivenCompletedDownloadClientHistory(false);
|
||||
_trackedDownload.DownloadItem.OutputPath = new OsPath();
|
||||
|
||||
_completed.Clear();
|
||||
_completed.AddRange(Builder<DownloadClientItem>.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());
|
||||
Subject.Process(_trackedDownload);
|
||||
|
||||
var grabbedHistory = Builder<History.History>.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<History.History>.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<IHistoryService>()
|
||||
.Verify(v => v.UpdateHistoryData(It.IsAny<int>(), It.IsAny<Dictionary<String, String>>()), Times.Exactly(2));
|
||||
AssertNoAttemptedImport();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_remove_if_config_disabled()
|
||||
{
|
||||
GivenCompletedDownloadClientHistory();
|
||||
|
||||
var history = Builder<History.History>.CreateListOfSize(1)
|
||||
.Build()
|
||||
.ToList();
|
||||
|
||||
GivenGrabbedHistory(history);
|
||||
GivenNoImportedHistory();
|
||||
GivenCompletedImport();
|
||||
|
||||
history.First().Data.Add("downloadClient", "SabnzbdClient");
|
||||
history.First().Data.Add("downloadClientId", _completed.First().DownloadClientId);
|
||||
|
||||
Mocker.GetMock<IConfigService>()
|
||||
.SetupGet(s => s.RemoveCompletedDownloads)
|
||||
.Returns(false);
|
||||
|
||||
Subject.Execute(new CheckForFinishedDownloadCommand());
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Verify(c => c.DeleteFolder(It.IsAny<string>(), true), Times.Never());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_remove_while_readonly()
|
||||
{
|
||||
GivenCompletedDownloadClientHistory();
|
||||
|
||||
var history = Builder<History.History>.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<IDiskProvider>()
|
||||
.Verify(c => c.DeleteFolder(It.IsAny<string>(), true), Times.Never());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_remove_if_imported_failed()
|
||||
{
|
||||
GivenCompletedDownloadClientHistory();
|
||||
|
||||
var history = Builder<History.History>.CreateListOfSize(1)
|
||||
.Build()
|
||||
.ToList();
|
||||
|
||||
GivenGrabbedHistory(history);
|
||||
GivenNoImportedHistory();
|
||||
GivenFailedImport();
|
||||
|
||||
_completed.First().IsReadOnly = true;
|
||||
|
||||
history.First().Data.Add("downloadClient", "SabnzbdClient");
|
||||
history.First().Data.Add("downloadClientId", _completed.First().DownloadClientId);
|
||||
|
||||
Subject.Execute(new CheckForFinishedDownloadCommand());
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Verify(c => c.DeleteFolder(It.IsAny<string>(), true), Times.Never());
|
||||
|
||||
ExceptionVerification.IgnoreErrors();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_remove_if_imported()
|
||||
{
|
||||
GivenCompletedDownloadClientHistory();
|
||||
|
||||
var history = Builder<History.History>.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<IDiskProvider>()
|
||||
.Verify(c => c.DeleteFolder(It.IsAny<string>(), true), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_mark_as_imported_if_all_files_were_rejected()
|
||||
{
|
||||
GivenCompletedDownloadClientHistory();
|
||||
|
||||
var history = Builder<History.History>.CreateListOfSize(1)
|
||||
.Build()
|
||||
.ToList();
|
||||
|
||||
GivenGrabbedHistory(history);
|
||||
GivenNoImportedHistory();
|
||||
|
||||
Mocker.GetMock<IDownloadedEpisodesImportService>()
|
||||
.Setup(v => v.ProcessFolder(It.IsAny<DirectoryInfo>(), It.IsAny<Series>(), It.IsAny<DownloadClientItem>()))
|
||||
.Setup(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<DownloadClientItem>()))
|
||||
.Returns(new List<ImportResult>
|
||||
{
|
||||
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<IDiskProvider>()
|
||||
.Verify(c => c.DeleteFolder(It.IsAny<string>(), 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<History.History>.CreateListOfSize(1)
|
||||
.Build()
|
||||
.ToList();
|
||||
|
||||
GivenGrabbedHistory(history);
|
||||
GivenNoImportedHistory();
|
||||
|
||||
Mocker.GetMock<IDownloadedEpisodesImportService>()
|
||||
.Setup(v => v.ProcessFolder(It.IsAny<DirectoryInfo>(), It.IsAny<Series>(), It.IsAny<DownloadClientItem>()))
|
||||
.Setup(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<DownloadClientItem>()))
|
||||
.Returns(new List<ImportResult>
|
||||
{
|
||||
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());
|
||||
Subject.Process(_trackedDownload);
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Verify(c => c.DeleteFolder(It.IsAny<string>(), true), Times.Never());
|
||||
|
||||
ExceptionVerification.ExpectedErrors(1);
|
||||
AssertNoCompletedDownload();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_mark_as_imported_if_some_files_were_skipped()
|
||||
{
|
||||
GivenCompletedDownloadClientHistory();
|
||||
|
||||
var history = Builder<History.History>.CreateListOfSize(1)
|
||||
.Build()
|
||||
.ToList();
|
||||
|
||||
GivenGrabbedHistory(history);
|
||||
GivenNoImportedHistory();
|
||||
|
||||
Mocker.GetMock<IDownloadedEpisodesImportService>()
|
||||
.Setup(v => v.ProcessFolder(It.IsAny<DirectoryInfo>(), It.IsAny<Series>(), It.IsAny<DownloadClientItem>()))
|
||||
.Setup(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<DownloadClientItem>()))
|
||||
.Returns(new List<ImportResult>
|
||||
{
|
||||
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);
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Verify(c => c.DeleteFolder(It.IsAny<string>(), true), Times.Never());
|
||||
AssertNoCompletedDownload();
|
||||
}
|
||||
|
||||
ExceptionVerification.ExpectedErrors(1);
|
||||
|
||||
private void AssertNoAttemptedImport()
|
||||
{
|
||||
Mocker.GetMock<IDownloadedEpisodesImportService>()
|
||||
.Verify(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<DownloadClientItem>()), Times.Never());
|
||||
|
||||
AssertNoCompletedDownload();
|
||||
}
|
||||
|
||||
private void AssertNoCompletedDownload()
|
||||
{
|
||||
Mocker.GetMock<IEventAggregator>()
|
||||
.Verify(v => v.PublishEvent(It.IsAny<DownloadCompletedEvent>()), Times.Never());
|
||||
|
||||
_trackedDownload.State.Should().NotBe(TrackedDownloadStage.Imported);
|
||||
}
|
||||
|
||||
private void AssertCompletedDownload()
|
||||
{
|
||||
Mocker.GetMock<IDownloadedEpisodesImportService>()
|
||||
.Verify(v => v.ProcessPath(_trackedDownload.DownloadItem.OutputPath.FullPath, _trackedDownload.DownloadItem), Times.Once());
|
||||
|
||||
_trackedDownload.State.Should().Be(TrackedDownloadStage.Imported);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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<DownloadTrackingService>
|
||||
public class FailedDownloadServiceFixture : CoreTest<FailedDownloadService>
|
||||
{
|
||||
private List<DownloadClientItem> _completed;
|
||||
private List<DownloadClientItem> _failed;
|
||||
private TrackedDownload _trackedDownload;
|
||||
private List<History.History> _grabHistory;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_completed = Builder<DownloadClientItem>.CreateListOfSize(5)
|
||||
.All()
|
||||
var completed = Builder<DownloadClientItem>.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<DownloadClientItem>.CreateListOfSize(1)
|
||||
.All()
|
||||
.With(h => h.Status = DownloadItemStatus.Failed)
|
||||
.With(h => h.Title = "Drone.S01E01.HDTV")
|
||||
.Build()
|
||||
.ToList();
|
||||
_grabHistory = Builder<History.History>.CreateListOfSize(2).BuildList();
|
||||
|
||||
var remoteEpisode = new RemoteEpisode
|
||||
{
|
||||
|
@ -47,410 +38,74 @@ namespace NzbDrone.Core.Test.Download
|
|||
Episodes = new List<Episode> { new Episode { Id = 1 } }
|
||||
};
|
||||
|
||||
Mocker.GetMock<IProvideDownloadClient>()
|
||||
.Setup(c => c.GetDownloadClients())
|
||||
.Returns( new IDownloadClient[] { Mocker.GetMock<IDownloadClient>().Object });
|
||||
_trackedDownload = Builder<TrackedDownload>.CreateNew()
|
||||
.With(c => c.State = TrackedDownloadStage.Downloading)
|
||||
.With(c => c.DownloadItem = completed)
|
||||
.With(c => c.RemoteEpisode = remoteEpisode)
|
||||
.Build();
|
||||
|
||||
Mocker.GetMock<IDownloadClient>()
|
||||
.SetupGet(c => c.Definition)
|
||||
.Returns(new DownloadClientDefinition { Id = 1, Name = "testClient" });
|
||||
|
||||
Mocker.GetMock<IConfigService>()
|
||||
.SetupGet(s => s.EnableFailedDownloadHandling)
|
||||
.Returns(true);
|
||||
|
||||
Mocker.GetMock<IHistoryService>()
|
||||
.Setup(s => s.Imported())
|
||||
.Returns(new List<History.History>());
|
||||
.Setup(s => s.Find(_trackedDownload.DownloadItem.DownloadId, HistoryEventType.Grabbed))
|
||||
.Returns(_grabHistory);
|
||||
|
||||
Mocker.GetMock<IParsingService>()
|
||||
.Setup(s => s.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<Int32>(), It.IsAny<IEnumerable<Int32>>()))
|
||||
.Returns(remoteEpisode);
|
||||
|
||||
Mocker.GetMock<IParsingService>()
|
||||
.Setup(s => s.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<Int32>(), (SearchCriteriaBase)null))
|
||||
.Returns(remoteEpisode);
|
||||
|
||||
Mocker.SetConstant<IFailedDownloadService>(Mocker.Resolve<FailedDownloadService>());
|
||||
}
|
||||
|
||||
private void GivenNoGrabbedHistory()
|
||||
{
|
||||
Mocker.GetMock<IHistoryService>()
|
||||
.Setup(s => s.Grabbed())
|
||||
.Returns(new List<History.History>());
|
||||
}
|
||||
|
||||
private void GivenGrabbedHistory(List<History.History> history)
|
||||
{
|
||||
Mocker.GetMock<IHistoryService>()
|
||||
.Setup(s => s.Grabbed())
|
||||
.Returns(history);
|
||||
}
|
||||
|
||||
private void GivenNoFailedHistory()
|
||||
{
|
||||
Mocker.GetMock<IHistoryService>()
|
||||
.Setup(s => s.Failed())
|
||||
.Returns(new List<History.History>());
|
||||
}
|
||||
|
||||
private void GivenFailedHistory(List<History.History> failedHistory)
|
||||
{
|
||||
Mocker.GetMock<IHistoryService>()
|
||||
.Setup(s => s.Failed())
|
||||
.Returns(failedHistory);
|
||||
}
|
||||
|
||||
private void GivenFailedDownloadClientHistory()
|
||||
{
|
||||
Mocker.GetMock<IDownloadClient>()
|
||||
.Setup(s => s.GetItems())
|
||||
.Returns(_failed);
|
||||
}
|
||||
|
||||
private void GivenGracePeriod(int hours)
|
||||
{
|
||||
Mocker.GetMock<IConfigService>().SetupGet(s => s.BlacklistGracePeriod).Returns(hours);
|
||||
}
|
||||
|
||||
private void GivenRetryLimit(int count, int interval = 5)
|
||||
{
|
||||
Mocker.GetMock<IConfigService>().SetupGet(s => s.BlacklistRetryLimit).Returns(count);
|
||||
Mocker.GetMock<IConfigService>().SetupGet(s => s.BlacklistRetryInterval).Returns(interval);
|
||||
}
|
||||
|
||||
private void VerifyNoFailedDownloads()
|
||||
{
|
||||
Mocker.GetMock<IEventAggregator>()
|
||||
.Verify(v => v.PublishEvent(It.IsAny<DownloadFailedEvent>()), Times.Never());
|
||||
}
|
||||
|
||||
private void VerifyFailedDownloads(int count = 1)
|
||||
{
|
||||
Mocker.GetMock<IEventAggregator>()
|
||||
.Verify(v => v.PublishEvent(It.Is<DownloadFailedEvent>(d => d.EpisodeIds.Count == count)), Times.Once());
|
||||
}
|
||||
|
||||
private void VerifyRetryDownload()
|
||||
{
|
||||
Mocker.GetMock<IDownloadClient>()
|
||||
.Verify(v => v.RetryDownload(It.IsAny<String>()), Times.Once());
|
||||
}
|
||||
|
||||
private void VerifyNoRetryDownload()
|
||||
{
|
||||
Mocker.GetMock<IDownloadClient>()
|
||||
.Verify(v => v.RetryDownload(It.IsAny<String>()), Times.Never());
|
||||
.Setup(s => s.Find(_trackedDownload.DownloadItem.DownloadId, HistoryEventType.Grabbed))
|
||||
.Returns(new List<History.History>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_process_if_no_download_client_history()
|
||||
{
|
||||
Mocker.GetMock<IDownloadClient>()
|
||||
.Setup(s => s.GetItems())
|
||||
.Returns(new List<DownloadClientItem>());
|
||||
|
||||
Subject.Execute(new CheckForFinishedDownloadCommand());
|
||||
|
||||
Mocker.GetMock<IHistoryService>()
|
||||
.Verify(s => s.BetweenDates(It.IsAny<DateTime>(), It.IsAny<DateTime>(), HistoryEventType.Grabbed),
|
||||
Times.Never());
|
||||
|
||||
VerifyNoFailedDownloads();
|
||||
}
|
||||
|
||||
[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<IDownloadClient>()
|
||||
.Setup(s => s.GetItems())
|
||||
.Returns(_completed);
|
||||
Subject.Process(_trackedDownload);
|
||||
|
||||
Subject.Execute(new CheckForFinishedDownloadCommand());
|
||||
|
||||
Mocker.GetMock<IHistoryService>()
|
||||
.Verify(s => s.BetweenDates(It.IsAny<DateTime>(), It.IsAny<DateTime>(), HistoryEventType.Grabbed),
|
||||
Times.Never());
|
||||
|
||||
VerifyNoFailedDownloads();
|
||||
AssertDownloadNotFailed();
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void should_not_process_if_matching_history_is_not_found()
|
||||
public void should_mark_failed_if_encrypted()
|
||||
{
|
||||
GivenNoGrabbedHistory();
|
||||
GivenFailedDownloadClientHistory();
|
||||
_trackedDownload.DownloadItem.IsEncrypted = true;
|
||||
|
||||
Subject.Execute(new CheckForFinishedDownloadCommand());
|
||||
Subject.Process(_trackedDownload);
|
||||
|
||||
VerifyNoFailedDownloads();
|
||||
ExceptionVerification.ExpectedWarns(1);
|
||||
AssertDownloadFailed();
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void should_not_process_if_grabbed_history_contains_null_downloadclient_id()
|
||||
public void should_mark_failed_if_download_item_is_failed()
|
||||
{
|
||||
GivenFailedDownloadClientHistory();
|
||||
_trackedDownload.DownloadItem.Status = DownloadItemStatus.Failed;
|
||||
|
||||
var historyGrabbed = Builder<History.History>.CreateListOfSize(1)
|
||||
.Build()
|
||||
.ToList();
|
||||
Subject.Process(_trackedDownload);
|
||||
|
||||
historyGrabbed.First().Data.Add("downloadClient", "SabnzbdClient");
|
||||
historyGrabbed.First().Data.Add("downloadClientId", null);
|
||||
|
||||
GivenGrabbedHistory(historyGrabbed);
|
||||
GivenNoFailedHistory();
|
||||
|
||||
Subject.Execute(new CheckForFinishedDownloadCommand());
|
||||
|
||||
VerifyNoFailedDownloads();
|
||||
ExceptionVerification.ExpectedWarns(1);
|
||||
AssertDownloadFailed();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_process_if_failed_history_contains_null_downloadclient_id()
|
||||
|
||||
private void AssertDownloadNotFailed()
|
||||
{
|
||||
GivenFailedDownloadClientHistory();
|
||||
Mocker.GetMock<IEventAggregator>()
|
||||
.Verify(v => v.PublishEvent(It.IsAny<DownloadFailedEvent>()), Times.Never());
|
||||
|
||||
var historyGrabbed = Builder<History.History>.CreateListOfSize(1)
|
||||
.Build()
|
||||
.ToList();
|
||||
|
||||
historyGrabbed.First().Data.Add("downloadClient", "SabnzbdClient");
|
||||
historyGrabbed.First().Data.Add("downloadClientId", _failed.First().DownloadClientId);
|
||||
|
||||
GivenGrabbedHistory(historyGrabbed);
|
||||
|
||||
var historyFailed = Builder<History.History>.CreateListOfSize(1)
|
||||
.Build()
|
||||
.ToList();
|
||||
|
||||
historyFailed.First().Data.Add("downloadClient", "SabnzbdClient");
|
||||
historyFailed.First().Data.Add("downloadClientId", null);
|
||||
|
||||
GivenFailedHistory(historyFailed);
|
||||
|
||||
Subject.Execute(new CheckForFinishedDownloadCommand());
|
||||
|
||||
VerifyFailedDownloads();
|
||||
_trackedDownload.State.Should().NotBe(TrackedDownloadStage.DownloadFailed);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_process_if_already_added_to_history_as_failed()
|
||||
|
||||
private void AssertDownloadFailed()
|
||||
{
|
||||
GivenFailedDownloadClientHistory();
|
||||
|
||||
var history = Builder<History.History>.CreateListOfSize(1)
|
||||
.Build()
|
||||
.ToList();
|
||||
|
||||
GivenGrabbedHistory(history);
|
||||
GivenFailedHistory(history);
|
||||
Mocker.GetMock<IEventAggregator>()
|
||||
.Verify(v => v.PublishEvent(It.IsAny<DownloadFailedEvent>()), Times.Once());
|
||||
|
||||
history.First().Data.Add("downloadClient", "SabnzbdClient");
|
||||
history.First().Data.Add("downloadClientId", _failed.First().DownloadClientId);
|
||||
|
||||
Subject.Execute(new CheckForFinishedDownloadCommand());
|
||||
|
||||
VerifyNoFailedDownloads();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_process_if_not_already_in_failed_history()
|
||||
{
|
||||
GivenFailedDownloadClientHistory();
|
||||
|
||||
var history = Builder<History.History>.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<History.History>.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<IConfigService>()
|
||||
.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<History.History>.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<History.History>.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<History.History>.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()
|
||||
{
|
||||
GivenFailedDownloadClientHistory();
|
||||
|
||||
var historyGrabbed = Builder<History.History>.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());
|
||||
|
||||
VerifyFailedDownloads();
|
||||
VerifyNoRetryDownload();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_process_if_age_is_less_than_grace_period()
|
||||
{
|
||||
GivenFailedDownloadClientHistory();
|
||||
|
||||
var historyGrabbed = Builder<History.History>.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()
|
||||
{
|
||||
var historyFailed = Builder<History.History>.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<IHistoryService>()
|
||||
.Setup(s => s.Get(It.IsAny<Int32>()))
|
||||
.Returns<Int32>(i => historyFailed.FirstOrDefault(v => v.Id == i));
|
||||
|
||||
Subject.MarkAsFailed(1);
|
||||
|
||||
VerifyFailedDownloads(2);
|
||||
_trackedDownload.State.Should().Be(TrackedDownloadStage.DownloadFailed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<TrackedDownload> _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<TrackedDownload>.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<IDownloadTrackingService>()
|
||||
.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()
|
||||
|
|
|
@ -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<HistoryRepository, History.History>
|
||||
{
|
||||
[Test]
|
||||
public void Trim_Items()
|
||||
{
|
||||
var historyItem = Builder<History.History>.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<History.History>
|
||||
.CreateListOfSize(5)
|
||||
.All()
|
||||
.With(c => c.Quality = new QualityModel())
|
||||
.With(c => c.EventType = HistoryEventType.Unknown)
|
||||
.Random(3)
|
||||
var historyBluray = Builder<History.History>.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<History.History>.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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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<IHistoryRepository>()
|
||||
.Verify(v => v.Insert(It.Is<History.History>(h => h.SourceTitle == Path.GetFileNameWithoutExtension(localEpisode.Path))));
|
||||
|
|
|
@ -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<DownloadedEpisodesCommandService>
|
||||
{
|
||||
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<IDownloadedEpisodesImportService>()
|
||||
.Setup(v => v.ProcessFolder(It.IsAny<DirectoryInfo>(), It.IsAny<DownloadClientItem>()))
|
||||
.Returns(new List<ImportResult>());
|
||||
|
||||
var downloadItem = Builder<DownloadClientItem>.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<DownloadClientItem>.CreateNew()
|
||||
.With(v => v.DownloadClientId = "sab1")
|
||||
.With(v => v.Status = DownloadItemStatus.Downloading)
|
||||
.Build();
|
||||
|
||||
Mocker.GetMock<IDownloadTrackingService>()
|
||||
.Setup(s => s.GetQueuedDownloads())
|
||||
.Returns(new [] { _trackedDownload });
|
||||
}
|
||||
|
||||
private void GivenSuccessfulImport()
|
||||
{
|
||||
Mocker.GetMock<IDownloadedEpisodesImportService>()
|
||||
.Setup(v => v.ProcessFolder(It.IsAny<DirectoryInfo>(), It.IsAny<DownloadClientItem>()))
|
||||
.Returns(new List<ImportResult>() {
|
||||
new ImportResult(new ImportDecision(new LocalEpisode() { Path = @"C:\TestPath\Droned.S01E01.mkv" }))
|
||||
});
|
||||
}
|
||||
|
||||
private void GivenRejectedImport()
|
||||
{
|
||||
Mocker.GetMock<IDownloadedEpisodesImportService>()
|
||||
.Setup(v => v.ProcessFolder(It.IsAny<DirectoryInfo>(), It.IsAny<DownloadClientItem>()))
|
||||
.Returns(new List<ImportResult>() {
|
||||
new ImportResult(new ImportDecision(new LocalEpisode() { Path = @"C:\TestPath\Droned.S01E01.mkv" }, "Some Rejection"), "I was rejected")
|
||||
});
|
||||
}
|
||||
|
||||
private void GivenSkippedImport()
|
||||
{
|
||||
Mocker.GetMock<IDownloadedEpisodesImportService>()
|
||||
.Setup(v => v.ProcessFolder(It.IsAny<DirectoryInfo>(), It.IsAny<DownloadClientItem>()))
|
||||
.Returns(new List<ImportResult>() {
|
||||
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<IDownloadedEpisodesImportService>().Verify(c => c.ProcessRootFolder(It.IsAny<DirectoryInfo>()), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_process_folder_if_downloadclientid_is_not_specified()
|
||||
{
|
||||
Subject.Execute(new DownloadedEpisodesScanCommand() { Path = _downloadFolder });
|
||||
|
||||
Mocker.GetMock<IDownloadedEpisodesImportService>().Verify(c => c.ProcessFolder(It.IsAny<DirectoryInfo>(), null), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_process_folder_with_downloadclientitem_if_available()
|
||||
{
|
||||
GivenValidQueueItem();
|
||||
|
||||
Subject.Execute(new DownloadedEpisodesScanCommand() { Path = _downloadFolder, DownloadClientId = "sab1" });
|
||||
|
||||
Mocker.GetMock<ICompletedDownloadService>().Verify(c => c.Import(It.Is<TrackedDownload>(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<IDownloadedEpisodesImportService>().Verify(c => c.ProcessFolder(It.IsAny<DirectoryInfo>(), null), Times.Once());
|
||||
|
||||
ExceptionVerification.ExpectedWarns(1);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -117,6 +117,7 @@
|
|||
<Compile Include="Datastore\MappingExtentionFixture.cs" />
|
||||
<Compile Include="Datastore\MarrDataLazyLoadingFixture.cs" />
|
||||
<Compile Include="Datastore\Migration\071_unknown_quality_in_profileFixture.cs" />
|
||||
<Compile Include="Datastore\Migration\072_history_grabIdFixture.cs" />
|
||||
<Compile Include="Datastore\Migration\070_delay_profileFixture.cs" />
|
||||
<Compile Include="Datastore\ObjectDatabaseFixture.cs" />
|
||||
<Compile Include="Datastore\PagingSpecExtensionsTests\PagingOffsetFixture.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<Blacklist> Paged(PagingSpec<Blacklist> pagingSpec);
|
||||
void Delete(int id);
|
||||
}
|
||||
|
@ -19,16 +19,13 @@ namespace NzbDrone.Core.Blacklisting
|
|||
public class BlacklistService : IBlacklistService,
|
||||
IExecute<ClearBlacklistCommand>,
|
||||
IHandle<DownloadFailedEvent>,
|
||||
IHandle<SeriesDeletedEvent>
|
||||
IHandleAsync<SeriesDeletedEvent>
|
||||
{
|
||||
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);
|
||||
|
||||
|
|
|
@ -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); }
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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<Dictionary<string, string>>(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();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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<RemoteEpisode> queue)
|
||||
private bool IsInQueue(RemoteEpisode newEpisode, IEnumerable<RemoteEpisode> 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());
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<History.History> grabbedHistory, List<History.History> importedHistory);
|
||||
List<ImportResult> 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;
|
||||
_downloadedEpisodesImportService = downloadedEpisodesImportService;
|
||||
}
|
||||
|
||||
private List<History.History> GetHistoryItems(List<History.History> grabbedHistory, string downloadClientId)
|
||||
public void Process(TrackedDownload trackedDownload)
|
||||
{
|
||||
return grabbedHistory.Where(h => downloadClientId.Equals(h.Data.GetValueOrDefault(DownloadTrackingService.DOWNLOAD_CLIENT_ID)))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public void CheckForCompletedItem(IDownloadClient downloadClient, TrackedDownload trackedDownload, List<History.History> grabbedHistory, List<History.History> importedHistory)
|
||||
{
|
||||
if (!_configService.EnableCompletedDownloadHandling)
|
||||
if (trackedDownload.DownloadItem.Status != DownloadItemStatus.Completed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (trackedDownload.DownloadItem.Status == DownloadItemStatus.Completed && trackedDownload.State == TrackedDownloadState.Downloading)
|
||||
var historyItem = _historyService.MostRecentForDownloadId(trackedDownload.DownloadItem.DownloadId);
|
||||
|
||||
if (historyItem == null && trackedDownload.DownloadItem.Category.IsNullOrWhiteSpace())
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
trackedDownload.Warn("Download wasn't grabbed by Sonarr and not in a category, Skipping.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (_configService.RemoveCompletedDownloads)
|
||||
var downloadItemOutputPath = trackedDownload.DownloadItem.OutputPath;
|
||||
|
||||
if (downloadItemOutputPath.IsEmpty)
|
||||
{
|
||||
RemoveCompleted(trackedDownload, downloadClient);
|
||||
trackedDownload.Warn("Download doesn't contain intermediate path, Skipping.");
|
||||
return;
|
||||
}
|
||||
|
||||
var downloadedEpisodesFolder = new OsPath(_configService.DownloadedEpisodesFolder);
|
||||
if (downloadedEpisodesFolder.Contains(downloadItemOutputPath))
|
||||
{
|
||||
trackedDownload.Warn("Intermediate Download path inside drone factory, Skipping.");
|
||||
return;
|
||||
}
|
||||
|
||||
Import(trackedDownload);
|
||||
}
|
||||
|
||||
public List<ImportResult> Import(TrackedDownload trackedDownload, String overrideOutputPath = null)
|
||||
private void Import(TrackedDownload trackedDownload)
|
||||
{
|
||||
var importResults = new List<ImportResult>();
|
||||
var outputPath = overrideOutputPath ?? trackedDownload.DownloadItem.OutputPath.FullPath;
|
||||
var outputPath = trackedDownload.DownloadItem.OutputPath.FullPath;
|
||||
var importResults = _downloadedEpisodesImportService.ProcessPath(outputPath, trackedDownload.DownloadItem);
|
||||
|
||||
if (_diskProvider.FolderExists(outputPath))
|
||||
{
|
||||
importResults = _downloadedEpisodesImportService.ProcessFolder(new DirectoryInfo(outputPath), trackedDownload.RemoteEpisode.Series, trackedDownload.DownloadItem);
|
||||
|
||||
ProcessImportResults(trackedDownload, outputPath, importResults);
|
||||
}
|
||||
|
||||
else if (_diskProvider.FileExists(outputPath))
|
||||
{
|
||||
importResults = _downloadedEpisodesImportService.ProcessFile(new FileInfo(outputPath), trackedDownload.RemoteEpisode.Series, trackedDownload.DownloadItem);
|
||||
|
||||
ProcessImportResults(trackedDownload, outputPath, importResults);
|
||||
}
|
||||
|
||||
return importResults;
|
||||
}
|
||||
|
||||
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;
|
||||
_logger.Log(logLevel, logMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Debug(logMessage);
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessImportResults(TrackedDownload trackedDownload, String outputPath, List<ImportResult> 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))
|
||||
|
||||
if (importResults.Any(c => c.Result != ImportResultType.Imported))
|
||||
{
|
||||
UpdateStatusMessage(trackedDownload, LogLevel.Info, "Imported {0} files.", importResults.Count(v => v.Result == ImportResultType.Imported));
|
||||
var statusMessages = importResults
|
||||
.Where(v => v.Result != ImportResultType.Imported)
|
||||
.Select(v => new TrackedDownloadStatusMessage(Path.GetFileName(v.ImportDecision.LocalEpisode.Path), v.Errors))
|
||||
.ToArray();
|
||||
|
||||
trackedDownload.State = TrackedDownloadState.Imported;
|
||||
trackedDownload.Warn(statusMessages);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
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();
|
||||
trackedDownload.State = TrackedDownloadStage.Imported;
|
||||
_eventAggregator.PublishEvent(new DownloadCompletedEvent(trackedDownload));
|
||||
|
||||
UpdateStatusMessage(trackedDownload, LogLevel.Error, errors);
|
||||
}
|
||||
}
|
||||
|
||||
private void AssociateWithPreviouslyImported(TrackedDownload trackedDownload, List<History.History> grabbedItems, List<History.History> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UpdateStatusMessage(trackedDownload, LogLevel.Error, "Intermediate Download path does not exist: {0}", trackedDownload.DownloadItem.OutputPath);
|
||||
}
|
||||
|
||||
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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
|
||||
|
||||
namespace NzbDrone.Core.Download
|
||||
{
|
||||
public interface IDownloadClientRepository : IProviderRepository<DownloadClientDefinition>
|
||||
|
|
|
@ -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<DownloadFailedEvent>,
|
||||
IHandle<DownloadCompletedEvent>
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<string, string> Data { get; set; }
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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<CheckForFinishedDownloadCommand>,
|
||||
IHandleAsync<ApplicationStartedEvent>,
|
||||
IHandle<EpisodeGrabbedEvent>,
|
||||
IHandle<SceneMappingsUpdatedEvent>
|
||||
{
|
||||
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<TrackedDownload[]> _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<TrackedDownload[]>(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<TrackedDownload> 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<History.History> GetHistoryItems(List<History.History> grabbedHistory, string downloadClientId)
|
||||
{
|
||||
return grabbedHistory.Where(h => downloadClientId.Equals(h.Data.GetValueOrDefault(DOWNLOAD_CLIENT_ID)))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private Boolean UpdateTrackedDownloads(List<History.History> grabbedHistory)
|
||||
{
|
||||
var downloadClients = _downloadClientProvider.GetDownloadClients();
|
||||
|
||||
var oldTrackedDownloads = GetTrackedDownloads().ToDictionary(v => v.TrackingId);
|
||||
var newTrackedDownloads = new Dictionary<String, TrackedDownload>();
|
||||
|
||||
var stateChanged = false;
|
||||
|
||||
foreach (var downloadClient in downloadClients)
|
||||
{
|
||||
List<DownloadClientItem> 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<History.History> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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<History.History> grabbedHistory, List<History.History> 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.History> { 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<History.History> grabbedHistory, List<History.History> failedHistory)
|
||||
public void Process(TrackedDownload trackedDownload)
|
||||
{
|
||||
if (!_configService.EnableFailedDownloadHandling)
|
||||
var grabbedItems = _historyService.Find(trackedDownload.DownloadItem.DownloadId, HistoryEventType.Grabbed)
|
||||
.ToList();
|
||||
|
||||
if (grabbedItems.Empty())
|
||||
{
|
||||
trackedDownload.Warn("Download wasn't grabbed by sonarr, skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
if (trackedDownload.DownloadItem.IsEncrypted && trackedDownload.State == TrackedDownloadState.Downloading)
|
||||
if (trackedDownload.DownloadItem.IsEncrypted)
|
||||
{
|
||||
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");
|
||||
}
|
||||
trackedDownload.State = TrackedDownloadStage.DownloadFailed;
|
||||
PublishDownloadFailedEvent(grabbedItems, "Encrypted download detected");
|
||||
}
|
||||
|
||||
if (trackedDownload.DownloadItem.Status == DownloadItemStatus.Failed && trackedDownload.State == TrackedDownloadState.Downloading)
|
||||
else if (trackedDownload.DownloadItem.Status == DownloadItemStatus.Failed)
|
||||
{
|
||||
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);
|
||||
}
|
||||
trackedDownload.State = TrackedDownloadStage.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<History.History> matchingHistoryItems)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
if (trackedDownload.RetryCount >= _configService.BlacklistRetryLimit)
|
||||
{
|
||||
UpdateStatusMessage(trackedDownload, LogLevel.Info, "Download Failed, Retry limit reached.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (trackedDownload.LastRetry == new DateTime())
|
||||
{
|
||||
trackedDownload.LastRetry = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
if (trackedDownload.LastRetry.AddMinutes(_configService.BlacklistRetryInterval) < DateTime.UtcNow)
|
||||
{
|
||||
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<String, String>(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;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateStatusMessage(trackedDownload, LogLevel.Warn, "Download Failed, waiting for retry interval to expire.");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private List<History.History> GetHistoryItems(List<History.History> grabbedHistory, string downloadClientId)
|
||||
{
|
||||
return grabbedHistory.Where(h => downloadClientId.Equals(h.Data.GetValueOrDefault(DownloadTrackingService.DOWNLOAD_CLIENT_ID)))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private void PublishDownloadFailedEvent(List<History.History> 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<TrackedDownloadStatusMessage>
|
||||
{
|
||||
new TrackedDownloadStatusMessage(trackedDownload.DownloadItem.Title, statusMessage)
|
||||
};
|
||||
_logger.Log(logLevel, logMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Debug(logMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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<int> episodeIds);
|
||||
}
|
||||
|
||||
public class RedownloadFailedDownloadService : IRedownloadFailedDownloads
|
||||
public class RedownloadFailedDownloadService : IHandleAsync<DownloadFailedEvent>
|
||||
{
|
||||
private readonly IConfigService _configService;
|
||||
private readonly IEpisodeService _episodeService;
|
||||
|
@ -28,7 +23,7 @@ namespace NzbDrone.Core.Download
|
|||
_logger = logger;
|
||||
}
|
||||
|
||||
public void Redownload(int seriesId, List<int> 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<TrackedDownloadStatusMessage> StatusMessages { get; set; }
|
||||
|
||||
public TrackedDownload()
|
||||
{
|
||||
StatusMessages = new List<TrackedDownloadStatusMessage>();
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
|
@ -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<CheckForFinishedDownloadCommand>,
|
||||
IHandleAsync<EpisodeGrabbedEvent>,
|
||||
IHandleAsync<EpisodeImportedEvent>
|
||||
{
|
||||
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<TrackedDownload>();
|
||||
|
||||
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<TrackedDownload> ProcessClientDownloads(IDownloadClient downloadClient)
|
||||
{
|
||||
List<DownloadClientItem> downloadClientHistory = new List<DownloadClientItem>();
|
||||
var trackedDownloads = new List<TrackedDownload>();
|
||||
|
||||
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<TrackedDownload> trackedDownloads)
|
||||
{
|
||||
foreach (var trakedDownload in trackedDownloads.Where(c => !c.DownloadItem.IsReadOnly && c.State == TrackedDownloadStage.Imported))
|
||||
{
|
||||
_eventAggregator.PublishEvent(new DownloadCompletedEvent(trakedDownload));
|
||||
}
|
||||
}
|
||||
|
||||
private List<TrackedDownload> ProcessClientItems(IDownloadClient downloadClient, DownloadClientItem downloadItem)
|
||||
{
|
||||
var trackedDownloads = new List<TrackedDownload>();
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
using System.Collections.Generic;
|
||||
using NzbDrone.Common.Messaging;
|
||||
|
||||
namespace NzbDrone.Core.Download.TrackedDownloads
|
||||
{
|
||||
public class TrackedDownloadRefreshedEvent : IEvent
|
||||
{
|
||||
public List<TrackedDownload> TrackedDownloads { get; private set; }
|
||||
|
||||
public TrackedDownloadRefreshedEvent(List<TrackedDownload> trackedDownloads)
|
||||
{
|
||||
TrackedDownloads = trackedDownloads;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<SceneMappingsUpdatedEvent>
|
||||
{
|
||||
private readonly IParsingService _parsingService;
|
||||
private readonly IHistoryService _historyService;
|
||||
private readonly Logger _logger;
|
||||
private readonly ICached<TrackedDownload> _cache;
|
||||
|
||||
public TrackedDownloadService(IParsingService parsingService,
|
||||
ICacheManager cacheManager,
|
||||
IHistoryService historyService,
|
||||
Logger logger)
|
||||
{
|
||||
_parsingService = parsingService;
|
||||
_historyService = historyService;
|
||||
_cache = cacheManager.GetCache<TrackedDownload>(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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<String> Messages { get; set; }
|
||||
|
||||
private TrackedDownloadStatusMessage()
|
||||
{
|
||||
}
|
||||
|
||||
public TrackedDownloadStatusMessage(String title, List<String> messages)
|
||||
{
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -8,6 +8,8 @@ namespace NzbDrone.Core.History
|
|||
{
|
||||
public class History : ModelBase
|
||||
{
|
||||
public const string DOWNLOAD_CLIENT = "downloadClient";
|
||||
|
||||
public History()
|
||||
{
|
||||
Data = new Dictionary<string, string>();
|
||||
|
@ -22,6 +24,9 @@ namespace NzbDrone.Core.History
|
|||
public Series Series { get; set; }
|
||||
public HistoryEventType EventType { get; set; }
|
||||
public Dictionary<string, string> Data { get; set; }
|
||||
|
||||
public string DownloadId { get; set; }
|
||||
|
||||
}
|
||||
|
||||
public enum HistoryEventType
|
||||
|
|
|
@ -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<History>
|
||||
{
|
||||
void Trim();
|
||||
List<QualityModel> GetBestQualityInHistory(int episodeId);
|
||||
List<History> BetweenDates(DateTime startDate, DateTime endDate, HistoryEventType eventType);
|
||||
List<History> Failed();
|
||||
List<History> Grabbed();
|
||||
List<History> Imported();
|
||||
History MostRecentForEpisode(int episodeId);
|
||||
List<History> FindBySourceTitle(string sourceTitle);
|
||||
History MostRecentForDownloadId(string downloadId);
|
||||
List<History> FindByDownloadId(string downloadId);
|
||||
List<History> FindDownloadHistory(int idSeriesId, QualityModel quality);
|
||||
}
|
||||
|
||||
public class HistoryRepository : BasicRepository<History>, 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<QualityModel> GetBestQualityInHistory(int episodeId)
|
||||
{
|
||||
|
@ -44,30 +33,6 @@ namespace NzbDrone.Core.History
|
|||
return history.Select(h => h.Quality).ToList();
|
||||
}
|
||||
|
||||
public List<History> BetweenDates(DateTime startDate, DateTime endDate, HistoryEventType eventType)
|
||||
{
|
||||
return Query.Join<History, Series>(JoinType.Inner, h => h.Series, (h, s) => h.SeriesId == s.Id)
|
||||
.Join<History, Episode>(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<History> Failed()
|
||||
{
|
||||
return Query.Where(h => h.EventType == HistoryEventType.DownloadFailed);
|
||||
}
|
||||
|
||||
public List<History> Grabbed()
|
||||
{
|
||||
return Query.Where(h => h.EventType == HistoryEventType.Grabbed);
|
||||
}
|
||||
|
||||
public List<History> 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<History> FindBySourceTitle(string sourceTitle)
|
||||
public History MostRecentForDownloadId(string downloadId)
|
||||
{
|
||||
return Query.Where(h => h.SourceTitle.Contains(sourceTitle));
|
||||
return Query.Where(h => h.DownloadId == downloadId)
|
||||
.OrderByDescending(h => h.Date)
|
||||
.FirstOrDefault();
|
||||
}
|
||||
|
||||
public List<History> AllForEpisode(int episodeId)
|
||||
public List<History> FindByDownloadId(string downloadId)
|
||||
{
|
||||
return Query.Where(h => h.EpisodeId == episodeId);
|
||||
return Query.Where(h => h.DownloadId == downloadId);
|
||||
}
|
||||
|
||||
public List<History> FindDownloadHistory(int idSeriesId, QualityModel quality)
|
||||
{
|
||||
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<History> GetPagedQuery(QueryBuilder<History> query, PagingSpec<History> pagingSpec)
|
||||
|
|
|
@ -16,19 +16,12 @@ namespace NzbDrone.Core.History
|
|||
{
|
||||
public interface IHistoryService
|
||||
{
|
||||
List<History> All();
|
||||
void Purge();
|
||||
void Trim();
|
||||
QualityModel GetBestQualityInHistory(Profile profile, int episodeId);
|
||||
PagingSpec<History> Paged(PagingSpec<History> pagingSpec);
|
||||
List<History> BetweenDates(DateTime startDate, DateTime endDate, HistoryEventType eventType);
|
||||
List<History> Failed();
|
||||
List<History> Grabbed();
|
||||
List<History> Imported();
|
||||
History MostRecentForEpisode(int episodeId);
|
||||
History Get(int id);
|
||||
List<History> FindBySourceTitle(string sourceTitle);
|
||||
void UpdateHistoryData(Int32 historyId, Dictionary<String, String> data);
|
||||
History MostRecentForDownloadId(string downloadId);
|
||||
History Get(int historyId);
|
||||
List<History> Find(string downloadId, HistoryEventType eventType);
|
||||
}
|
||||
|
||||
public class HistoryService : IHistoryService,
|
||||
|
@ -46,60 +39,31 @@ namespace NzbDrone.Core.History
|
|||
_logger = logger;
|
||||
}
|
||||
|
||||
public List<History> All()
|
||||
{
|
||||
return _historyRepository.All().ToList();
|
||||
}
|
||||
|
||||
public PagingSpec<History> Paged(PagingSpec<History> pagingSpec)
|
||||
{
|
||||
return _historyRepository.GetPaged(pagingSpec);
|
||||
}
|
||||
|
||||
public List<History> BetweenDates(DateTime startDate, DateTime endDate, HistoryEventType eventType)
|
||||
{
|
||||
return _historyRepository.BetweenDates(startDate, endDate, eventType);
|
||||
}
|
||||
|
||||
public List<History> Failed()
|
||||
{
|
||||
return _historyRepository.Failed();
|
||||
}
|
||||
|
||||
public List<History> Grabbed()
|
||||
{
|
||||
return _historyRepository.Grabbed();
|
||||
}
|
||||
|
||||
public List<History> 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<History> FindBySourceTitle(string sourceTitle)
|
||||
public History Get(int historyId)
|
||||
{
|
||||
return _historyRepository.FindBySourceTitle(sourceTitle);
|
||||
return _historyRepository.Get(historyId);
|
||||
}
|
||||
|
||||
public void Purge()
|
||||
public List<History> 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<String, String> 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);
|
||||
|
|
|
@ -14,7 +14,5 @@ namespace NzbDrone.Core.MediaFiles.Commands
|
|||
}
|
||||
|
||||
public Boolean SendUpdates { get; set; }
|
||||
public String Path { get; set; }
|
||||
public String DownloadClientId { get; set; }
|
||||
}
|
||||
}
|
|
@ -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<DownloadedEpisodesScanCommand>
|
||||
{
|
||||
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<ImportResult> ProcessFolder(DownloadedEpisodesScanCommand message)
|
||||
{
|
||||
if (!_diskProvider.FolderExists(message.Path))
|
||||
{
|
||||
_logger.Warn("Folder specified for import scan [{0}] doesn't exist.", message.Path);
|
||||
return new List<ImportResult>();
|
||||
}
|
||||
|
||||
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<ImportResult> 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.
|
||||
|
|
|
@ -16,9 +16,7 @@ namespace NzbDrone.Core.MediaFiles
|
|||
{
|
||||
List<ImportResult> ProcessRootFolder(DirectoryInfo directoryInfo);
|
||||
List<ImportResult> ProcessFolder(DirectoryInfo directoryInfo, DownloadClientItem downloadClientItem = null);
|
||||
List<ImportResult> ProcessFolder(DirectoryInfo directoryInfo, Series series, DownloadClientItem downloadClientItem = null);
|
||||
List<ImportResult> ProcessFile(FileInfo fileInfo, DownloadClientItem downloadClientItem = null);
|
||||
List<ImportResult> ProcessFile(FileInfo fileInfo, Series series, DownloadClientItem downloadClientItem = null);
|
||||
List<ImportResult> 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<ImportResult> ProcessFolder(DirectoryInfo directoryInfo, Series series,
|
||||
private List<ImportResult> 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<ImportResult>();
|
||||
}
|
||||
|
||||
|
@ -147,7 +145,7 @@ namespace NzbDrone.Core.MediaFiles
|
|||
return ProcessFile(fileInfo, series, downloadClientItem);
|
||||
}
|
||||
|
||||
public List<ImportResult> ProcessFile(FileInfo fileInfo, Series series, DownloadClientItem downloadClientItem = null)
|
||||
private List<ImportResult> 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<ImportResult> 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_", "")
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -231,6 +231,7 @@
|
|||
<Compile Include="Datastore\Migration\067_add_added_to_series.cs" />
|
||||
<Compile Include="Datastore\Migration\069_quality_proper.cs" />
|
||||
<Compile Include="Datastore\Migration\071_unknown_quality_in_profile.cs" />
|
||||
<Compile Include="Datastore\Migration\072_history_grabid.cs" />
|
||||
<Compile Include="Datastore\Migration\070_delay_profile.cs" />
|
||||
<Compile Include="Datastore\Migration\Framework\MigrationContext.cs" />
|
||||
<Compile Include="Datastore\Migration\Framework\MigrationController.cs" />
|
||||
|
@ -270,7 +271,6 @@
|
|||
<Compile Include="DecisionEngine\Specifications\QualityAllowedByProfileSpecification.cs" />
|
||||
<Compile Include="DecisionEngine\Specifications\MinimumAgeSpecification.cs" />
|
||||
<Compile Include="DecisionEngine\Specifications\RetentionSpecification.cs" />
|
||||
<Compile Include="DecisionEngine\Specifications\RetrySpecification.cs" />
|
||||
<Compile Include="DecisionEngine\Specifications\RssSync\DelaySpecification.cs" />
|
||||
<Compile Include="DecisionEngine\Specifications\RssSync\HistorySpecification.cs" />
|
||||
<Compile Include="DecisionEngine\Specifications\RssSync\MonitoredEpisodeSpecification.cs" />
|
||||
|
@ -353,7 +353,12 @@
|
|||
<Compile Include="Download\Clients\uTorrent\UTorrentTorrent.cs" />
|
||||
<Compile Include="Download\Clients\uTorrent\UTorrentTorrentStatus.cs" />
|
||||
<Compile Include="Download\CompletedDownloadService.cs" />
|
||||
<Compile Include="Download\TrackedDownloadStatusMessage.cs" />
|
||||
<Compile Include="Download\DownloadEventHub.cs" />
|
||||
<Compile Include="Download\TrackedDownloads\DownloadMonitoringService.cs" />
|
||||
<Compile Include="Download\TrackedDownloads\TrackedDownload.cs" />
|
||||
<Compile Include="Download\TrackedDownloads\TrackedDownloadService.cs" />
|
||||
<Compile Include="Download\TrackedDownloads\TrackedDownloadStatusMessage.cs" />
|
||||
<Compile Include="Download\TrackedDownloads\TrackedDownloadRefreshedEvent.cs" />
|
||||
<Compile Include="Download\UsenetClientBase.cs" />
|
||||
<Compile Include="Download\TorrentClientBase.cs" />
|
||||
<Compile Include="Download\DownloadClientBase.cs" />
|
||||
|
@ -367,7 +372,6 @@
|
|||
<Compile Include="Download\DownloadFailedEvent.cs" />
|
||||
<Compile Include="Download\DownloadItemStatus.cs" />
|
||||
<Compile Include="Download\DownloadService.cs" />
|
||||
<Compile Include="Download\DownloadTrackingService.cs" />
|
||||
<Compile Include="Download\EpisodeGrabbedEvent.cs" />
|
||||
<Compile Include="Download\FailedDownloadService.cs" />
|
||||
<Compile Include="Download\IDownloadClient.cs" />
|
||||
|
@ -378,7 +382,6 @@
|
|||
<Compile Include="Download\ProcessDownloadDecisions.cs" />
|
||||
<Compile Include="Download\ProcessedDecisions.cs" />
|
||||
<Compile Include="Download\RedownloadFailedDownloadService.cs" />
|
||||
<Compile Include="Download\TrackedDownload.cs" />
|
||||
<Compile Include="Exceptions\BadRequestException.cs" />
|
||||
<Compile Include="Exceptions\DownstreamException.cs" />
|
||||
<Compile Include="Exceptions\NzbDroneClientException.cs" />
|
||||
|
@ -765,9 +768,8 @@
|
|||
<Compile Include="Qualities\QualityModel.cs" />
|
||||
<Compile Include="Qualities\QualityModelComparer.cs" />
|
||||
<Compile Include="Queue\Queue.cs" />
|
||||
<Compile Include="Queue\QueueScheduler.cs" />
|
||||
<Compile Include="Queue\QueueService.cs" />
|
||||
<Compile Include="Queue\UpdateQueueEvent.cs" />
|
||||
<Compile Include="Queue\QueueUpdatedEvent.cs" />
|
||||
<Compile Include="Restrictions\Restriction.cs" />
|
||||
<Compile Include="Restrictions\RestrictionRepository.cs" />
|
||||
<Compile Include="Restrictions\RestrictionService.cs" />
|
||||
|
@ -909,6 +911,7 @@
|
|||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
<ItemGroup />
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<PropertyGroup>
|
||||
<PostBuildEvent>
|
||||
|
|
|
@ -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<Int32> episodeIds);
|
||||
RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, Int32 tvRageId = 0, SearchCriteriaBase searchCriteria = null);
|
||||
List<Episode> 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<Int32> episodeIds)
|
||||
{
|
||||
var remoteEpisode = new RemoteEpisode
|
||||
{
|
||||
ParsedEpisodeInfo = parsedEpisodeInfo,
|
||||
Series = _seriesService.GetSeries(seriesId),
|
||||
Episodes = _episodeService.GetEpisodes(episodeIds)
|
||||
};
|
||||
|
||||
return remoteEpisode;
|
||||
}
|
||||
|
||||
public List<Episode> GetEpisodes(ParsedEpisodeInfo parsedEpisodeInfo, Series series, bool sceneSource, SearchCriteriaBase searchCriteria = null)
|
||||
{
|
||||
var result = new List<Episode>();
|
||||
|
|
|
@ -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<TrackedDownloadStatusMessage> StatusMessages { get; set; }
|
||||
public RemoteEpisode RemoteEpisode { get; set; }
|
||||
public String TrackingId { get; set; }
|
||||
public RemoteEpisode RemoteEpisode { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<ApplicationStartedEvent>,
|
||||
IHandle<ApplicationShutdownRequested>
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<TrackedDownloadRefreshedEvent>
|
||||
{
|
||||
private readonly IDownloadTrackingService _downloadTrackingService;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private static List<Queue> _queue = new List<Queue>();
|
||||
|
||||
public QueueService(IDownloadTrackingService downloadTrackingService)
|
||||
public QueueService(IEventAggregator eventAggregator)
|
||||
{
|
||||
_downloadTrackingService = downloadTrackingService;
|
||||
_eventAggregator = eventAggregator;
|
||||
}
|
||||
|
||||
public List<Queue> 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<Queue> MapQueue(IEnumerable<TrackedDownload> trackedDownloads)
|
||||
public void Handle(TrackedDownloadRefreshedEvent message)
|
||||
{
|
||||
var queued = new List<Queue>();
|
||||
_queue = message.TrackedDownloads.OrderBy(c => c.DownloadItem.RemainingTime).SelectMany(MapQueue)
|
||||
.ToList();
|
||||
|
||||
foreach (var trackedDownload in trackedDownloads)
|
||||
_eventAggregator.PublishEvent(new QueueUpdatedEvent());
|
||||
}
|
||||
|
||||
private static IEnumerable<Queue> 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
|
||||
};
|
||||
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
|
||||
};
|
||||
|
||||
if (queue.Timeleft.HasValue)
|
||||
{
|
||||
queue.EstimatedCompletionTime = DateTime.UtcNow.Add(queue.Timeleft.Value);
|
||||
}
|
||||
|
||||
queued.Add(queue);
|
||||
if (queue.Timeleft.HasValue)
|
||||
{
|
||||
queue.EstimatedCompletionTime = DateTime.UtcNow.Add(queue.Timeleft.Value);
|
||||
}
|
||||
|
||||
yield return queue;
|
||||
}
|
||||
|
||||
return queued;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace NzbDrone.Core.Queue
|
||||
{
|
||||
public class UpdateQueueEvent : IEvent
|
||||
public class QueueUpdatedEvent : IEvent
|
||||
{
|
||||
}
|
||||
}
|
|
@ -25,9 +25,9 @@
|
|||
<dd>{{downloadClient}}</dd>
|
||||
{{/if}}
|
||||
|
||||
{{#if downloadClientId}}
|
||||
<dt>Download Client ID:</dt>
|
||||
<dd>{{downloadClientId}}</dd>
|
||||
{{#if downloadId}}
|
||||
<dt>Grab ID:</dt>
|
||||
<dd>{{downloadId}}</dd>
|
||||
{{/if}}
|
||||
|
||||
{{#if age}}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -14,15 +14,13 @@
|
|||
|
||||
<div class="btn btn-primary slide-button"/>
|
||||
</label>
|
||||
|
||||
<span class="help-inline-checkbox">
|
||||
<i class="icon-nd-form-info" title="Import completed downloads in download client history"/>
|
||||
<i class="icon-nd-form-warning" title="Download client history items that are stored in the drone factory will be ignored. Configure the Drone Factory for a different path"/>
|
||||
<i class="icon-nd-form-info" title="Automatically import completed downloads from download client"/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="x-completed-download-options advanced-setting">
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label">Remove</label>
|
||||
|
@ -44,20 +42,19 @@
|
|||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="advanced-setting">
|
||||
|
||||
<fieldset>
|
||||
<legend>Failed Download Handling</legend>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label">Enable</label>
|
||||
<label class="col-sm-3 control-label">Redownload</label>
|
||||
|
||||
<div class="col-sm-8">
|
||||
<div class="input-group">
|
||||
<label class="checkbox toggle well">
|
||||
<input type="checkbox" name="enableFailedDownloadHandling" class="x-failed-download-handling"/>
|
||||
<input type="checkbox" name="autoRedownloadFailed" class="x-failed-auto-redownload"/>
|
||||
<p>
|
||||
<span>Yes</span>
|
||||
<span>No</span>
|
||||
|
@ -66,39 +63,15 @@
|
|||
<div class="btn btn-primary slide-button"/>
|
||||
</label>
|
||||
|
||||
<span class="help-inline-checkbox">
|
||||
<i class="icon-nd-form-info" title="Process failed downloads and blacklist the release"/>
|
||||
</span>
|
||||
<span class="help-inline-checkbox">
|
||||
<i class="icon-nd-form-info" title="Automatically search for and attempt to download a different release"/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="x-failed-download-options">
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label">Redownload</label>
|
||||
|
||||
<div class="col-sm-8">
|
||||
<div class="input-group">
|
||||
<label class="checkbox toggle well">
|
||||
<input type="checkbox" name="autoRedownloadFailed"/>
|
||||
<p>
|
||||
<span>Yes</span>
|
||||
<span>No</span>
|
||||
</p>
|
||||
|
||||
<div class="btn btn-primary slide-button"/>
|
||||
</label>
|
||||
|
||||
<span class="help-inline-checkbox">
|
||||
<i class="icon-nd-form-info" title="Automatically search for and attempt to download another release"/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="x-failed-download-options advanced-setting">
|
||||
<div class="form-group ">
|
||||
<label class="col-sm-3 control-label">Remove</label>
|
||||
|
||||
<div class="col-sm-8">
|
||||
<div class="input-group">
|
||||
<label class="checkbox toggle well">
|
||||
|
@ -110,48 +83,11 @@
|
|||
|
||||
<div class="btn btn-primary slide-button"/>
|
||||
</label>
|
||||
|
||||
<span class="help-inline-checkbox">
|
||||
<i class="icon-nd-form-info" title="Remove failed downloads from download client history"/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group advanced-setting">
|
||||
<label class="col-sm-3 control-label">Grace Period</label>
|
||||
|
||||
<div class="col-sm-1 col-sm-push-2 help-inline">
|
||||
<i class="icon-nd-form-info" title="Age in hours (since posting) where a release can be retried instead of immediately blacklisted"/>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-2 col-sm-pull-1">
|
||||
<input type="number" min="1" max="24" name="blacklistGracePeriod" class="form-control"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group advanced-setting">
|
||||
<label class="col-sm-3 control-label">Retry Interval</label>
|
||||
|
||||
<div class="col-sm-1 col-sm-push-2 help-inline">
|
||||
<i class="icon-nd-form-info" title="Time in minutes before a failed download for a recent release will be retried"/>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-2 col-sm-pull-1">
|
||||
<input type="number" min="5" max="120" name="blacklistRetryInterval" class="form-control"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group advanced-setting">
|
||||
<label class="col-sm-3 control-label">Retry Count</label>
|
||||
|
||||
<div class="col-sm-1 col-sm-push-2 help-inline">
|
||||
<i class="icon-nd-form-info" title="Number of times to retry a release before it is blacklisted"/>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-2 col-sm-pull-1">
|
||||
<input type="number" min="0" max="10" name="blacklistRetryLimit" class="form-control"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
|
@ -1,11 +1,12 @@
|
|||
<fieldset>
|
||||
<fieldset class="advanced-setting">
|
||||
<legend>Drone Factory Options</legend>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label">Drone Factory</label>
|
||||
|
||||
<div class="col-sm-1 col-sm-push-8 help-inline">
|
||||
<i class="icon-nd-form-info" title="Optional folder to periodically scan for available imports"/>
|
||||
<i class="icon-nd-form-info" title="Optional folder to periodically scan for possible imports"/>
|
||||
<i class="icon-nd-form-warning" title="Do not use the folder that contains some or all of your sorted and named TV shows - doing so could cause data loss"></i>
|
||||
<i class="icon-nd-form-warning" title="Download client history items that are stored in the drone factory will be ignored."/>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-8 col-sm-pull-1">
|
||||
|
@ -13,7 +14,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group advanced-setting">
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label">Drone Factory Interval</label>
|
||||
|
||||
<div class="col-sm-1 col-sm-push-2 help-inline">
|
||||
|
|
Loading…
Reference in New Issue