mirror of https://github.com/Radarr/Radarr
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)
|
.SetValidator(pathExistsValidator)
|
||||||
.When(c => !String.IsNullOrWhiteSpace(c.DownloadedEpisodesFolder));
|
.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 EnableCompletedDownloadHandling { get; set; }
|
||||||
public Boolean RemoveCompletedDownloads { get; set; }
|
public Boolean RemoveCompletedDownloads { get; set; }
|
||||||
|
|
||||||
public Boolean EnableFailedDownloadHandling { get; set; }
|
|
||||||
public Boolean AutoRedownloadFailed { get; set; }
|
public Boolean AutoRedownloadFailed { get; set; }
|
||||||
public Boolean RemoveFailedDownloads { 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 IHistoryService _historyService;
|
||||||
private readonly IQualityUpgradableSpecification _qualityUpgradableSpecification;
|
private readonly IQualityUpgradableSpecification _qualityUpgradableSpecification;
|
||||||
private readonly IDownloadTrackingService _downloadTrackingService;
|
private readonly IFailedDownloadService _failedDownloadService;
|
||||||
|
|
||||||
public HistoryModule(IHistoryService historyService,
|
public HistoryModule(IHistoryService historyService,
|
||||||
IQualityUpgradableSpecification qualityUpgradableSpecification,
|
IQualityUpgradableSpecification qualityUpgradableSpecification,
|
||||||
IDownloadTrackingService downloadTrackingService)
|
IFailedDownloadService failedDownloadService)
|
||||||
{
|
{
|
||||||
_historyService = historyService;
|
_historyService = historyService;
|
||||||
_qualityUpgradableSpecification = qualityUpgradableSpecification;
|
_qualityUpgradableSpecification = qualityUpgradableSpecification;
|
||||||
_downloadTrackingService = downloadTrackingService;
|
_failedDownloadService = failedDownloadService;
|
||||||
GetResourcePaged = GetHistory;
|
GetResourcePaged = GetHistory;
|
||||||
|
|
||||||
Post["/failed"] = x => MarkAsFailed();
|
Post["/failed"] = x => MarkAsFailed();
|
||||||
|
@ -28,9 +28,9 @@ namespace NzbDrone.Api.History
|
||||||
|
|
||||||
protected override HistoryResource ToResource<TModel>(TModel model)
|
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)
|
if (history != null && history.Series != null)
|
||||||
{
|
{
|
||||||
|
@ -70,7 +70,7 @@ namespace NzbDrone.Api.History
|
||||||
private Response MarkAsFailed()
|
private Response MarkAsFailed()
|
||||||
{
|
{
|
||||||
var id = (int)Request.Form.Id;
|
var id = (int)Request.Form.Id;
|
||||||
_downloadTrackingService.MarkAsFailed(id);
|
_failedDownloadService.MarkAsFailed(id);
|
||||||
return new Object().AsResponse();
|
return new Object().AsResponse();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ namespace NzbDrone.Api.History
|
||||||
public string Indexer { get; set; }
|
public string Indexer { get; set; }
|
||||||
public string NzbInfoUrl { get; set; }
|
public string NzbInfoUrl { get; set; }
|
||||||
public string ReleaseGroup { get; set; }
|
public string ReleaseGroup { get; set; }
|
||||||
|
public string DownloadId { get; set; }
|
||||||
|
|
||||||
public HistoryEventType EventType { get; set; }
|
public HistoryEventType EventType { get; set; }
|
||||||
|
|
||||||
|
|
|
@ -63,10 +63,6 @@
|
||||||
<Reference Include="DDay.iCal">
|
<Reference Include="DDay.iCal">
|
||||||
<HintPath>..\packages\DDay.iCal.1.0.2.575\lib\DDay.iCal.dll</HintPath>
|
<HintPath>..\packages\DDay.iCal.1.0.2.575\lib\DDay.iCal.dll</HintPath>
|
||||||
</Reference>
|
</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">
|
<Reference Include="NLog">
|
||||||
<HintPath>..\packages\NLog.2.1.0\lib\net40\NLog.dll</HintPath>
|
<HintPath>..\packages\NLog.2.1.0\lib\net40\NLog.dll</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
using System.Linq;
|
using Nancy;
|
||||||
using Nancy;
|
|
||||||
using Nancy.Responses;
|
using Nancy.Responses;
|
||||||
using NzbDrone.Api.Extensions;
|
using NzbDrone.Api.Extensions;
|
||||||
using NzbDrone.Api.REST;
|
using NzbDrone.Api.REST;
|
||||||
using NzbDrone.Core.Download;
|
using NzbDrone.Core.Download;
|
||||||
using NzbDrone.Core.Download.Pending;
|
using NzbDrone.Core.Download.Pending;
|
||||||
|
using NzbDrone.Core.Download.TrackedDownloads;
|
||||||
using NzbDrone.Core.Queue;
|
using NzbDrone.Core.Queue;
|
||||||
|
|
||||||
namespace NzbDrone.Api.Queue
|
namespace NzbDrone.Api.Queue
|
||||||
|
@ -12,21 +12,21 @@ namespace NzbDrone.Api.Queue
|
||||||
public class QueueActionModule : NzbDroneRestModule<QueueResource>
|
public class QueueActionModule : NzbDroneRestModule<QueueResource>
|
||||||
{
|
{
|
||||||
private readonly IQueueService _queueService;
|
private readonly IQueueService _queueService;
|
||||||
private readonly IDownloadTrackingService _downloadTrackingService;
|
private readonly ITrackedDownloadService _trackedDownloadService;
|
||||||
private readonly ICompletedDownloadService _completedDownloadService;
|
private readonly ICompletedDownloadService _completedDownloadService;
|
||||||
private readonly IProvideDownloadClient _downloadClientProvider;
|
private readonly IProvideDownloadClient _downloadClientProvider;
|
||||||
private readonly IPendingReleaseService _pendingReleaseService;
|
private readonly IPendingReleaseService _pendingReleaseService;
|
||||||
private readonly IDownloadService _downloadService;
|
private readonly IDownloadService _downloadService;
|
||||||
|
|
||||||
public QueueActionModule(IQueueService queueService,
|
public QueueActionModule(IQueueService queueService,
|
||||||
IDownloadTrackingService downloadTrackingService,
|
ITrackedDownloadService trackedDownloadService,
|
||||||
ICompletedDownloadService completedDownloadService,
|
ICompletedDownloadService completedDownloadService,
|
||||||
IProvideDownloadClient downloadClientProvider,
|
IProvideDownloadClient downloadClientProvider,
|
||||||
IPendingReleaseService pendingReleaseService,
|
IPendingReleaseService pendingReleaseService,
|
||||||
IDownloadService downloadService)
|
IDownloadService downloadService)
|
||||||
{
|
{
|
||||||
_queueService = queueService;
|
_queueService = queueService;
|
||||||
_downloadTrackingService = downloadTrackingService;
|
_trackedDownloadService = trackedDownloadService;
|
||||||
_completedDownloadService = completedDownloadService;
|
_completedDownloadService = completedDownloadService;
|
||||||
_downloadClientProvider = downloadClientProvider;
|
_downloadClientProvider = downloadClientProvider;
|
||||||
_pendingReleaseService = pendingReleaseService;
|
_pendingReleaseService = pendingReleaseService;
|
||||||
|
@ -60,7 +60,7 @@ namespace NzbDrone.Api.Queue
|
||||||
throw new BadRequestException();
|
throw new BadRequestException();
|
||||||
}
|
}
|
||||||
|
|
||||||
downloadClient.RemoveItem(trackedDownload.DownloadItem.DownloadClientId);
|
downloadClient.RemoveItem(trackedDownload.DownloadItem.DownloadId);
|
||||||
|
|
||||||
return new object().AsResponse();
|
return new object().AsResponse();
|
||||||
}
|
}
|
||||||
|
@ -70,7 +70,7 @@ namespace NzbDrone.Api.Queue
|
||||||
var resource = Request.Body.FromJson<QueueResource>();
|
var resource = Request.Body.FromJson<QueueResource>();
|
||||||
var trackedDownload = GetTrackedDownload(resource.Id);
|
var trackedDownload = GetTrackedDownload(resource.Id);
|
||||||
|
|
||||||
_completedDownloadService.Import(trackedDownload);
|
_completedDownloadService.Process(trackedDownload);
|
||||||
|
|
||||||
return resource.AsResponse();
|
return resource.AsResponse();
|
||||||
}
|
}
|
||||||
|
@ -100,7 +100,7 @@ namespace NzbDrone.Api.Queue
|
||||||
throw new NotFoundException();
|
throw new NotFoundException();
|
||||||
}
|
}
|
||||||
|
|
||||||
var trackedDownload = _downloadTrackingService.Find(queueItem.TrackingId);
|
var trackedDownload = _trackedDownloadService.Find(queueItem.TrackingId);
|
||||||
|
|
||||||
if (trackedDownload == null)
|
if (trackedDownload == null)
|
||||||
{
|
{
|
||||||
|
|
|
@ -9,7 +9,7 @@ using NzbDrone.SignalR;
|
||||||
namespace NzbDrone.Api.Queue
|
namespace NzbDrone.Api.Queue
|
||||||
{
|
{
|
||||||
public class QueueModule : NzbDroneRestModuleWithSignalR<QueueResource, Core.Queue.Queue>,
|
public class QueueModule : NzbDroneRestModuleWithSignalR<QueueResource, Core.Queue.Queue>,
|
||||||
IHandle<UpdateQueueEvent>, IHandle<PendingReleasesUpdatedEvent>
|
IHandle<QueueUpdatedEvent>, IHandle<PendingReleasesUpdatedEvent>
|
||||||
{
|
{
|
||||||
private readonly IQueueService _queueService;
|
private readonly IQueueService _queueService;
|
||||||
private readonly IPendingReleaseService _pendingReleaseService;
|
private readonly IPendingReleaseService _pendingReleaseService;
|
||||||
|
@ -35,7 +35,7 @@ namespace NzbDrone.Api.Queue
|
||||||
return queue.Concat(pending);
|
return queue.Concat(pending);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Handle(UpdateQueueEvent message)
|
public void Handle(QueueUpdatedEvent message)
|
||||||
{
|
{
|
||||||
BroadcastResourceChange(ModelAction.Sync);
|
BroadcastResourceChange(ModelAction.Sync);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using NzbDrone.Api.REST;
|
using NzbDrone.Api.REST;
|
||||||
using NzbDrone.Core.Download;
|
|
||||||
using NzbDrone.Core.Qualities;
|
using NzbDrone.Core.Qualities;
|
||||||
using NzbDrone.Api.Series;
|
using NzbDrone.Api.Series;
|
||||||
using NzbDrone.Api.Episodes;
|
using NzbDrone.Api.Episodes;
|
||||||
|
using NzbDrone.Core.Download.TrackedDownloads;
|
||||||
|
|
||||||
namespace NzbDrone.Api.Queue
|
namespace NzbDrone.Api.Queue
|
||||||
{
|
{
|
||||||
|
|
|
@ -88,6 +88,7 @@
|
||||||
<Compile Include="ReflectionTests\ReflectionExtensionFixture.cs" />
|
<Compile Include="ReflectionTests\ReflectionExtensionFixture.cs" />
|
||||||
<Compile Include="ServiceFactoryFixture.cs" />
|
<Compile Include="ServiceFactoryFixture.cs" />
|
||||||
<Compile Include="ServiceProviderTests.cs" />
|
<Compile Include="ServiceProviderTests.cs" />
|
||||||
|
<Compile Include="TPLTests\DebouncerFixture.cs" />
|
||||||
<Compile Include="WebClientTests.cs" />
|
<Compile Include="WebClientTests.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<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="ServiceProvider.cs" />
|
||||||
<Compile Include="Extensions\StringExtensions.cs" />
|
<Compile Include="Extensions\StringExtensions.cs" />
|
||||||
<Compile Include="TinyIoC.cs" />
|
<Compile Include="TinyIoC.cs" />
|
||||||
|
<Compile Include="TPL\Debouncer.cs" />
|
||||||
<Compile Include="TPL\LimitedConcurrencyLevelTaskScheduler.cs" />
|
<Compile Include="TPL\LimitedConcurrencyLevelTaskScheduler.cs" />
|
||||||
<Compile Include="TPL\TaskExtensions.cs" />
|
<Compile Include="TPL\TaskExtensions.cs" />
|
||||||
<Compile Include="Extensions\TryParseExtensions.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),
|
Quality = new QualityModel(Quality.Bluray720p),
|
||||||
SourceTitle = "series.title.s01e01",
|
SourceTitle = "series.title.s01e01",
|
||||||
DownloadClient = "SabnzbdClient",
|
DownloadClient = "SabnzbdClient",
|
||||||
DownloadClientId = "Sabnzbd_nzo_2dfh73k"
|
DownloadId = "Sabnzbd_nzo_2dfh73k"
|
||||||
};
|
};
|
||||||
|
|
||||||
_event.Data.Add("publishedDate", DateTime.UtcNow.ToString("s") + "Z");
|
_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]
|
[Test]
|
||||||
public void should_add_to_repository()
|
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.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using FizzWare.NBuilder;
|
using FizzWare.NBuilder;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using NzbDrone.Core.DecisionEngine.Specifications;
|
using NzbDrone.Core.DecisionEngine.Specifications;
|
||||||
using NzbDrone.Core.Download;
|
using NzbDrone.Core.Download.TrackedDownloads;
|
||||||
using NzbDrone.Core.Parser.Model;
|
using NzbDrone.Core.Parser.Model;
|
||||||
using NzbDrone.Core.Profiles;
|
using NzbDrone.Core.Profiles;
|
||||||
using NzbDrone.Core.Qualities;
|
using NzbDrone.Core.Qualities;
|
||||||
|
using NzbDrone.Core.Queue;
|
||||||
using NzbDrone.Core.Tv;
|
using NzbDrone.Core.Tv;
|
||||||
using NzbDrone.Core.Test.Framework;
|
using NzbDrone.Core.Test.Framework;
|
||||||
|
|
||||||
|
@ -47,33 +49,27 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
|
||||||
_remoteEpisode = Builder<RemoteEpisode>.CreateNew()
|
_remoteEpisode = Builder<RemoteEpisode>.CreateNew()
|
||||||
.With(r => r.Series = _series)
|
.With(r => r.Series = _series)
|
||||||
.With(r => r.Episodes = new List<Episode> { _episode })
|
.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();
|
.Build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GivenEmptyQueue()
|
private void GivenEmptyQueue()
|
||||||
{
|
{
|
||||||
Mocker.GetMock<IDownloadTrackingService>()
|
Mocker.GetMock<IQueueService>()
|
||||||
.Setup(s => s.GetQueuedDownloads())
|
.Setup(s => s.GetQueue())
|
||||||
.Returns(new TrackedDownload[0]);
|
.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>();
|
var queue = remoteEpisodes.Select(remoteEpisode => new Queue.Queue
|
||||||
|
|
||||||
foreach (var remoteEpisode in remoteEpisodes)
|
|
||||||
{
|
{
|
||||||
queue.Add(new TrackedDownload
|
RemoteEpisode = remoteEpisode
|
||||||
{
|
});
|
||||||
State = state,
|
|
||||||
RemoteEpisode = remoteEpisode
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Mocker.GetMock<IDownloadTrackingService>()
|
Mocker.GetMock<IQueueService>()
|
||||||
.Setup(s => s.GetQueuedDownloads())
|
.Setup(s => s.GetQueue())
|
||||||
.Returns(queue.ToArray());
|
.Returns(queue.ToList());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -95,22 +91,6 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
|
||||||
Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeTrue();
|
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]
|
[Test]
|
||||||
public void should_return_true_when_quality_in_queue_is_lower()
|
public void should_return_true_when_quality_in_queue_is_lower()
|
||||||
|
@ -241,9 +221,9 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
|
||||||
Quality.HDTV720p)
|
Quality.HDTV720p)
|
||||||
})
|
})
|
||||||
.TheFirst(1)
|
.TheFirst(1)
|
||||||
.With(r => r.Episodes = new List<Episode> {_episode})
|
.With(r => r.Episodes = new List<Episode> { _episode })
|
||||||
.TheNext(1)
|
.TheNext(1)
|
||||||
.With(r => r.Episodes = new List<Episode> {_otherEpisode})
|
.With(r => r.Episodes = new List<Episode> { _otherEpisode })
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
_remoteEpisode.Episodes.Add(_otherEpisode);
|
_remoteEpisode.Episodes.Add(_otherEpisode);
|
||||||
|
|
|
@ -1,560 +1,225 @@
|
||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.IO;
|
|
||||||
using FizzWare.NBuilder;
|
using FizzWare.NBuilder;
|
||||||
|
using FluentAssertions;
|
||||||
using Moq;
|
using Moq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using NzbDrone.Common.Disk;
|
using NzbDrone.Common.Disk;
|
||||||
using NzbDrone.Core.Configuration;
|
using NzbDrone.Core.Configuration;
|
||||||
using NzbDrone.Core.Download;
|
using NzbDrone.Core.Download;
|
||||||
|
using NzbDrone.Core.Download.TrackedDownloads;
|
||||||
using NzbDrone.Core.History;
|
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;
|
||||||
using NzbDrone.Core.MediaFiles.EpisodeImport;
|
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.Core.Tv;
|
||||||
using NzbDrone.Test.Common;
|
using NzbDrone.Test.Common;
|
||||||
using NzbDrone.Core.Parser.Model;
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.Test.Download
|
namespace NzbDrone.Core.Test.Download
|
||||||
{
|
{
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class CompletedDownloadServiceFixture : CoreTest<DownloadTrackingService>
|
public class CompletedDownloadServiceFixture : CoreTest<CompletedDownloadService>
|
||||||
{
|
{
|
||||||
private List<DownloadClientItem> _completed;
|
private TrackedDownload _trackedDownload;
|
||||||
|
|
||||||
[SetUp]
|
[SetUp]
|
||||||
public void Setup()
|
public void Setup()
|
||||||
{
|
{
|
||||||
_completed = Builder<DownloadClientItem>.CreateListOfSize(1)
|
var completed = Builder<DownloadClientItem>.CreateNew()
|
||||||
.All()
|
|
||||||
.With(h => h.Status = DownloadItemStatus.Completed)
|
.With(h => h.Status = DownloadItemStatus.Completed)
|
||||||
.With(h => h.OutputPath = new OsPath(@"C:\DropFolder\MyDownload".AsOsAgnostic()))
|
.With(h => h.OutputPath = new OsPath(@"C:\DropFolder\MyDownload".AsOsAgnostic()))
|
||||||
.With(h => h.Title = "Drone.S01E01.HDTV")
|
.With(h => h.Title = "Drone.S01E01.HDTV")
|
||||||
.Build()
|
.Build();
|
||||||
.ToList();
|
|
||||||
|
|
||||||
var remoteEpisode = new RemoteEpisode
|
var remoteEpisode = new RemoteEpisode
|
||||||
{
|
{
|
||||||
Series = new Series(),
|
Series = new Series(),
|
||||||
Episodes = new List<Episode> {new Episode {Id = 1}}
|
Episodes = new List<Episode> { new Episode { Id = 1 } }
|
||||||
};
|
};
|
||||||
|
|
||||||
Mocker.GetMock<IProvideDownloadClient>()
|
_trackedDownload = Builder<TrackedDownload>.CreateNew()
|
||||||
.Setup(c => c.GetDownloadClients())
|
.With(c => c.State = TrackedDownloadStage.Downloading)
|
||||||
.Returns( new[] { Mocker.GetMock<IDownloadClient>().Object });
|
.With(c => c.DownloadItem = completed)
|
||||||
|
.With(c => c.RemoteEpisode = remoteEpisode)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
|
||||||
Mocker.GetMock<IDownloadClient>()
|
Mocker.GetMock<IDownloadClient>()
|
||||||
.SetupGet(c => c.Definition)
|
.SetupGet(c => c.Definition)
|
||||||
.Returns(new DownloadClientDefinition { Id = 1, Name = "testClient" });
|
.Returns(new DownloadClientDefinition { Id = 1, Name = "testClient" });
|
||||||
|
|
||||||
Mocker.GetMock<IConfigService>()
|
Mocker.GetMock<IProvideDownloadClient>()
|
||||||
.SetupGet(s => s.EnableCompletedDownloadHandling)
|
.Setup(c => c.Get(It.IsAny<int>()))
|
||||||
.Returns(true);
|
.Returns(Mocker.GetMock<IDownloadClient>().Object);
|
||||||
|
|
||||||
Mocker.GetMock<IConfigService>()
|
|
||||||
.SetupGet(s => s.RemoveCompletedDownloads)
|
|
||||||
.Returns(true);
|
|
||||||
|
|
||||||
Mocker.GetMock<IHistoryService>()
|
Mocker.GetMock<IHistoryService>()
|
||||||
.Setup(s => s.Failed())
|
.Setup(s => s.MostRecentForDownloadId(_trackedDownload.DownloadItem.DownloadId))
|
||||||
.Returns(new List<History.History>());
|
.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()
|
private void GivenNoGrabbedHistory()
|
||||||
{
|
{
|
||||||
Mocker.GetMock<IHistoryService>()
|
Mocker.GetMock<IHistoryService>()
|
||||||
.Setup(s => s.Grabbed())
|
.Setup(s => s.MostRecentForDownloadId(_trackedDownload.DownloadItem.DownloadId))
|
||||||
.Returns(new List<History.History>());
|
.Returns((History.History)null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GivenGrabbedHistory(List<History.History> history)
|
|
||||||
{
|
|
||||||
Mocker.GetMock<IHistoryService>()
|
|
||||||
.Setup(s => s.Grabbed())
|
|
||||||
.Returns(history);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void GivenNoImportedHistory()
|
private void GivenSuccessfulImport()
|
||||||
{
|
|
||||||
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()
|
|
||||||
{
|
{
|
||||||
Mocker.GetMock<IDownloadedEpisodesImportService>()
|
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>
|
.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" }))
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
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>()
|
_trackedDownload.DownloadItem.Status = status;
|
||||||
.Setup(v => v.ProcessFolder(It.IsAny<DirectoryInfo>(), It.IsAny<Series>(), It.IsAny<DownloadClientItem>()))
|
|
||||||
.Returns(new List<ImportResult>()
|
Subject.Process(_trackedDownload);
|
||||||
{
|
|
||||||
new ImportResult(new ImportDecision(new LocalEpisode() { Path = @"C:\TestPath\Droned.S01E01.mkv" }, "Test Failure"))
|
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>()
|
_trackedDownload.DownloadItem.Category = null;
|
||||||
.Verify(v => v.ProcessFolder(It.IsAny<DirectoryInfo>(), It.IsAny<Series>(), It.IsAny<DownloadClientItem>()), Times.Once());
|
GivenNoGrabbedHistory();
|
||||||
|
|
||||||
|
Subject.Process(_trackedDownload);
|
||||||
|
|
||||||
|
AssertNoAttemptedImport();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_process_if_matching_history_is_not_found_but_category_specified()
|
public void should_process_if_matching_history_is_not_found_but_category_specified()
|
||||||
{
|
{
|
||||||
_completed.First().Category = "tv";
|
_trackedDownload.DownloadItem.Category = "tv";
|
||||||
|
|
||||||
GivenCompletedDownloadClientHistory();
|
|
||||||
GivenNoGrabbedHistory();
|
GivenNoGrabbedHistory();
|
||||||
GivenNoImportedHistory();
|
GivenSuccessfulImport();
|
||||||
GivenCompletedImport();
|
|
||||||
|
|
||||||
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]
|
[Test]
|
||||||
public void should_not_process_if_storage_directory_in_drone_factory()
|
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>()
|
Mocker.GetMock<IConfigService>()
|
||||||
.SetupGet(v => v.DownloadedEpisodesFolder)
|
.SetupGet(v => v.DownloadedEpisodesFolder)
|
||||||
.Returns(@"C:\DropFolder".AsOsAgnostic());
|
.Returns(@"C:\DropFolder".AsOsAgnostic());
|
||||||
|
|
||||||
history.First().Data.Add("downloadClient", "SabnzbdClient");
|
_trackedDownload.DownloadItem.OutputPath = new OsPath(@"C:\DropFolder\SomeOtherFolder".AsOsAgnostic());
|
||||||
history.First().Data.Add("downloadClientId", _completed.First().DownloadClientId);
|
|
||||||
|
|
||||||
Subject.Execute(new CheckForFinishedDownloadCommand());
|
Subject.Process(_trackedDownload);
|
||||||
|
|
||||||
VerifyNoImports();
|
AssertNoAttemptedImport();
|
||||||
|
|
||||||
ExceptionVerification.IgnoreWarns();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[Test]
|
[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();
|
Subject.Process(_trackedDownload);
|
||||||
_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());
|
|
||||||
|
|
||||||
var grabbedHistory = Builder<History.History>.CreateListOfSize(2)
|
AssertNoAttemptedImport();
|
||||||
.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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[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]
|
[Test]
|
||||||
public void should_not_mark_as_imported_if_all_files_were_rejected()
|
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>()
|
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>
|
.Returns(new List<ImportResult>
|
||||||
{
|
{
|
||||||
new ImportResult(
|
new ImportResult(new ImportDecision(new LocalEpisode {Path = @"C:\TestPath\Droned.S01E01.mkv"}, "Rejected!"),"Test Failure"),
|
||||||
new ImportDecision(new LocalEpisode() {Path = @"C:\TestPath\Droned.S01E01.mkv"}, "Rejected!"),
|
new ImportResult(new ImportDecision(new LocalEpisode {Path = @"C:\TestPath\Droned.S01E02.mkv"}, "Rejected!"),"Test Failure")
|
||||||
"Test Failure")
|
|
||||||
});
|
});
|
||||||
|
|
||||||
history.First().Data.Add("downloadClient", "SabnzbdClient");
|
Subject.Process(_trackedDownload);
|
||||||
history.First().Data.Add("downloadClientId", _completed.First().DownloadClientId);
|
|
||||||
|
|
||||||
Subject.Execute(new CheckForFinishedDownloadCommand());
|
|
||||||
|
|
||||||
Mocker.GetMock<IDiskProvider>()
|
_trackedDownload.State.Should().NotBe(TrackedDownloadStage.Imported);
|
||||||
.Verify(c => c.DeleteFolder(It.IsAny<string>(), true), Times.Never());
|
|
||||||
|
|
||||||
ExceptionVerification.ExpectedErrors(1);
|
AssertNoCompletedDownload();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_not_mark_as_imported_if_all_files_were_skipped()
|
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>()
|
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>
|
.Returns(new List<ImportResult>
|
||||||
{
|
{
|
||||||
new ImportResult(
|
new ImportResult(new ImportDecision(new LocalEpisode {Path = @"C:\TestPath\Droned.S01E01.mkv"}),"Test Failure"),
|
||||||
new ImportDecision(new LocalEpisode() {Path = @"C:\TestPath\Droned.S01E01.mkv"}),
|
new ImportResult(new ImportDecision(new LocalEpisode {Path = @"C:\TestPath\Droned.S01E01.mkv"}),"Test Failure")
|
||||||
"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>()
|
AssertNoCompletedDownload();
|
||||||
.Verify(c => c.DeleteFolder(It.IsAny<string>(), true), Times.Never());
|
|
||||||
|
|
||||||
ExceptionVerification.ExpectedErrors(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_not_mark_as_imported_if_some_files_were_skipped()
|
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>()
|
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>
|
.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"})),
|
||||||
new ImportResult(
|
new ImportResult(new ImportDecision(new LocalEpisode{Path = @"C:\TestPath\Droned.S01E01.mkv"}),"Test Failure")
|
||||||
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>()
|
AssertNoCompletedDownload();
|
||||||
.Verify(c => c.DeleteFolder(It.IsAny<string>(), true), Times.Never());
|
}
|
||||||
|
|
||||||
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)
|
protected void VerifyIdentifiable(DownloadClientItem downloadClientItem)
|
||||||
{
|
{
|
||||||
downloadClientItem.DownloadClient.Should().Be(Subject.Definition.Name);
|
downloadClientItem.DownloadClient.Should().Be(Subject.Definition.Name);
|
||||||
downloadClientItem.DownloadClientId.Should().NotBeNullOrEmpty();
|
downloadClientItem.DownloadId.Should().NotBeNullOrEmpty();
|
||||||
downloadClientItem.Title.Should().NotBeNullOrEmpty();
|
downloadClientItem.Title.Should().NotBeNullOrEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,13 @@
|
||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using FizzWare.NBuilder;
|
using FizzWare.NBuilder;
|
||||||
|
using FluentAssertions;
|
||||||
using Moq;
|
using Moq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using NzbDrone.Core.Configuration;
|
using NzbDrone.Common.Disk;
|
||||||
using NzbDrone.Core.Download;
|
using NzbDrone.Core.Download;
|
||||||
|
using NzbDrone.Core.Download.TrackedDownloads;
|
||||||
using NzbDrone.Core.History;
|
using NzbDrone.Core.History;
|
||||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
|
||||||
using NzbDrone.Core.Messaging.Events;
|
using NzbDrone.Core.Messaging.Events;
|
||||||
using NzbDrone.Core.Parser;
|
|
||||||
using NzbDrone.Core.Parser.Model;
|
using NzbDrone.Core.Parser.Model;
|
||||||
using NzbDrone.Core.Test.Framework;
|
using NzbDrone.Core.Test.Framework;
|
||||||
using NzbDrone.Core.Tv;
|
using NzbDrone.Core.Tv;
|
||||||
|
@ -18,28 +16,21 @@ using NzbDrone.Test.Common;
|
||||||
namespace NzbDrone.Core.Test.Download
|
namespace NzbDrone.Core.Test.Download
|
||||||
{
|
{
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class FailedDownloadServiceFixture : CoreTest<DownloadTrackingService>
|
public class FailedDownloadServiceFixture : CoreTest<FailedDownloadService>
|
||||||
{
|
{
|
||||||
private List<DownloadClientItem> _completed;
|
private TrackedDownload _trackedDownload;
|
||||||
private List<DownloadClientItem> _failed;
|
private List<History.History> _grabHistory;
|
||||||
|
|
||||||
[SetUp]
|
[SetUp]
|
||||||
public void Setup()
|
public void Setup()
|
||||||
{
|
{
|
||||||
_completed = Builder<DownloadClientItem>.CreateListOfSize(5)
|
var completed = Builder<DownloadClientItem>.CreateNew()
|
||||||
.All()
|
|
||||||
.With(h => h.Status = DownloadItemStatus.Completed)
|
.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")
|
.With(h => h.Title = "Drone.S01E01.HDTV")
|
||||||
.Build()
|
.Build();
|
||||||
.ToList();
|
|
||||||
|
|
||||||
_failed = Builder<DownloadClientItem>.CreateListOfSize(1)
|
_grabHistory = Builder<History.History>.CreateListOfSize(2).BuildList();
|
||||||
.All()
|
|
||||||
.With(h => h.Status = DownloadItemStatus.Failed)
|
|
||||||
.With(h => h.Title = "Drone.S01E01.HDTV")
|
|
||||||
.Build()
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
var remoteEpisode = new RemoteEpisode
|
var remoteEpisode = new RemoteEpisode
|
||||||
{
|
{
|
||||||
|
@ -47,410 +38,74 @@ namespace NzbDrone.Core.Test.Download
|
||||||
Episodes = new List<Episode> { new Episode { Id = 1 } }
|
Episodes = new List<Episode> { new Episode { Id = 1 } }
|
||||||
};
|
};
|
||||||
|
|
||||||
Mocker.GetMock<IProvideDownloadClient>()
|
_trackedDownload = Builder<TrackedDownload>.CreateNew()
|
||||||
.Setup(c => c.GetDownloadClients())
|
.With(c => c.State = TrackedDownloadStage.Downloading)
|
||||||
.Returns( new IDownloadClient[] { Mocker.GetMock<IDownloadClient>().Object });
|
.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>()
|
Mocker.GetMock<IHistoryService>()
|
||||||
.Setup(s => s.Imported())
|
.Setup(s => s.Find(_trackedDownload.DownloadItem.DownloadId, HistoryEventType.Grabbed))
|
||||||
.Returns(new List<History.History>());
|
.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()
|
private void GivenNoGrabbedHistory()
|
||||||
{
|
{
|
||||||
Mocker.GetMock<IHistoryService>()
|
Mocker.GetMock<IHistoryService>()
|
||||||
.Setup(s => s.Grabbed())
|
.Setup(s => s.Find(_trackedDownload.DownloadItem.DownloadId, HistoryEventType.Grabbed))
|
||||||
.Returns(new List<History.History>());
|
.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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_not_process_if_no_download_client_history()
|
public void should_not_fail_if_matching_history_is_not_found()
|
||||||
{
|
|
||||||
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()
|
|
||||||
{
|
{
|
||||||
GivenNoGrabbedHistory();
|
GivenNoGrabbedHistory();
|
||||||
GivenNoFailedHistory();
|
|
||||||
|
|
||||||
Mocker.GetMock<IDownloadClient>()
|
Subject.Process(_trackedDownload);
|
||||||
.Setup(s => s.GetItems())
|
|
||||||
.Returns(_completed);
|
|
||||||
|
|
||||||
Subject.Execute(new CheckForFinishedDownloadCommand());
|
AssertDownloadNotFailed();
|
||||||
|
|
||||||
Mocker.GetMock<IHistoryService>()
|
|
||||||
.Verify(s => s.BetweenDates(It.IsAny<DateTime>(), It.IsAny<DateTime>(), HistoryEventType.Grabbed),
|
|
||||||
Times.Never());
|
|
||||||
|
|
||||||
VerifyNoFailedDownloads();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_not_process_if_matching_history_is_not_found()
|
public void should_mark_failed_if_encrypted()
|
||||||
{
|
{
|
||||||
GivenNoGrabbedHistory();
|
_trackedDownload.DownloadItem.IsEncrypted = true;
|
||||||
GivenFailedDownloadClientHistory();
|
|
||||||
|
|
||||||
Subject.Execute(new CheckForFinishedDownloadCommand());
|
Subject.Process(_trackedDownload);
|
||||||
|
|
||||||
VerifyNoFailedDownloads();
|
AssertDownloadFailed();
|
||||||
ExceptionVerification.ExpectedWarns(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[Test]
|
[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)
|
Subject.Process(_trackedDownload);
|
||||||
.Build()
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
historyGrabbed.First().Data.Add("downloadClient", "SabnzbdClient");
|
AssertDownloadFailed();
|
||||||
historyGrabbed.First().Data.Add("downloadClientId", null);
|
|
||||||
|
|
||||||
GivenGrabbedHistory(historyGrabbed);
|
|
||||||
GivenNoFailedHistory();
|
|
||||||
|
|
||||||
Subject.Execute(new CheckForFinishedDownloadCommand());
|
|
||||||
|
|
||||||
VerifyNoFailedDownloads();
|
|
||||||
ExceptionVerification.ExpectedWarns(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[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)
|
_trackedDownload.State.Should().NotBe(TrackedDownloadStage.DownloadFailed);
|
||||||
.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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void should_not_process_if_already_added_to_history_as_failed()
|
private void AssertDownloadFailed()
|
||||||
{
|
{
|
||||||
GivenFailedDownloadClientHistory();
|
Mocker.GetMock<IEventAggregator>()
|
||||||
|
.Verify(v => v.PublishEvent(It.IsAny<DownloadFailedEvent>()), Times.Once());
|
||||||
var history = Builder<History.History>.CreateListOfSize(1)
|
|
||||||
.Build()
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
GivenGrabbedHistory(history);
|
|
||||||
GivenFailedHistory(history);
|
|
||||||
|
|
||||||
history.First().Data.Add("downloadClient", "SabnzbdClient");
|
_trackedDownload.State.Should().Be(TrackedDownloadStage.DownloadFailed);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,9 @@
|
||||||
using System.Linq;
|
using NUnit.Framework;
|
||||||
using System.Collections.Generic;
|
|
||||||
using FizzWare.NBuilder;
|
|
||||||
using NUnit.Framework;
|
|
||||||
using NzbDrone.Test.Common;
|
|
||||||
using NzbDrone.Common.Disk;
|
using NzbDrone.Common.Disk;
|
||||||
using NzbDrone.Core.Configuration;
|
using NzbDrone.Core.Configuration;
|
||||||
using NzbDrone.Core.HealthCheck.Checks;
|
using NzbDrone.Core.HealthCheck.Checks;
|
||||||
using NzbDrone.Core.Test.Framework;
|
using NzbDrone.Core.Test.Framework;
|
||||||
using NzbDrone.Core.Download;
|
using NzbDrone.Test.Common;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Test.HealthCheck.Checks
|
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 const string DRONE_FACTORY_FOLDER = @"C:\Test\Unsorted";
|
||||||
|
|
||||||
private IList<TrackedDownload> _completed;
|
|
||||||
|
|
||||||
private void GivenCompletedDownloadHandling(bool? enabled = null)
|
private void GivenCompletedDownloadHandling(bool? enabled = null)
|
||||||
{
|
{
|
||||||
|
@ -30,18 +25,6 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
|
||||||
.SetupGet(s => s.EnableCompletedDownloadHandling)
|
.SetupGet(s => s.EnableCompletedDownloadHandling)
|
||||||
.Returns(enabled.Value);
|
.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)
|
private void GivenDroneFactoryFolder(bool exists = false)
|
||||||
|
@ -68,17 +51,6 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
|
||||||
|
|
||||||
Subject.Check().ShouldBeWarning();
|
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]
|
[Test]
|
||||||
public void should_return_ok_when_no_issues_found()
|
public void should_return_ok_when_no_issues_found()
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using FizzWare.NBuilder;
|
||||||
using FizzWare.NBuilder;
|
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using NzbDrone.Core.History;
|
using NzbDrone.Core.History;
|
||||||
|
@ -11,25 +10,6 @@ namespace NzbDrone.Core.Test.HistoryTests
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class HistoryRepositoryFixture : DbTest<HistoryRepository, History.History>
|
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]
|
[Test]
|
||||||
public void should_read_write_dictionary()
|
public void should_read_write_dictionary()
|
||||||
|
@ -38,29 +18,37 @@ namespace NzbDrone.Core.Test.HistoryTests
|
||||||
.With(c => c.Quality = new QualityModel())
|
.With(c => c.Quality = new QualityModel())
|
||||||
.BuildNew();
|
.BuildNew();
|
||||||
|
|
||||||
history.Data.Add("key1","value1");
|
history.Data.Add("key1", "value1");
|
||||||
history.Data.Add("key2","value2");
|
history.Data.Add("key2", "value2");
|
||||||
|
|
||||||
Subject.Insert(history);
|
Subject.Insert(history);
|
||||||
|
|
||||||
StoredModel.Data.Should().HaveCount(2);
|
StoredModel.Data.Should().HaveCount(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void grabbed_should_return_grabbed_items()
|
public void should_get_download_history()
|
||||||
{
|
{
|
||||||
var history = Builder<History.History>
|
var historyBluray = Builder<History.History>.CreateNew()
|
||||||
.CreateListOfSize(5)
|
.With(c => c.Quality = new QualityModel(Quality.Bluray1080p))
|
||||||
.All()
|
.With(c => c.SeriesId = 12)
|
||||||
.With(c => c.Quality = new QualityModel())
|
|
||||||
.With(c => c.EventType = HistoryEventType.Unknown)
|
|
||||||
.Random(3)
|
|
||||||
.With(c => c.EventType = HistoryEventType.Grabbed)
|
.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"
|
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>()
|
Mocker.GetMock<IHistoryRepository>()
|
||||||
.Verify(v => v.Insert(It.Is<History.History>(h => h.SourceTitle == Path.GetFileNameWithoutExtension(localEpisode.Path))));
|
.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;
|
||||||
using NzbDrone.Core.MediaFiles.Commands;
|
using NzbDrone.Core.MediaFiles.Commands;
|
||||||
using NzbDrone.Core.MediaFiles.EpisodeImport;
|
using NzbDrone.Core.MediaFiles.EpisodeImport;
|
||||||
using NzbDrone.Core.Parser.Model;
|
|
||||||
using NzbDrone.Core.Test.Framework;
|
using NzbDrone.Core.Test.Framework;
|
||||||
using NzbDrone.Test.Common;
|
using NzbDrone.Test.Common;
|
||||||
|
|
||||||
|
@ -19,9 +18,7 @@ namespace NzbDrone.Core.Test.MediaFiles
|
||||||
public class DownloadedEpisodesCommandServiceFixture : CoreTest<DownloadedEpisodesCommandService>
|
public class DownloadedEpisodesCommandServiceFixture : CoreTest<DownloadedEpisodesCommandService>
|
||||||
{
|
{
|
||||||
private string _droneFactory = "c:\\drop\\".AsOsAgnostic();
|
private string _droneFactory = "c:\\drop\\".AsOsAgnostic();
|
||||||
private string _downloadFolder = "c:\\drop_other\\Show.S01E01\\".AsOsAgnostic();
|
|
||||||
|
|
||||||
private TrackedDownload _trackedDownload;
|
|
||||||
|
|
||||||
[SetUp]
|
[SetUp]
|
||||||
public void Setup()
|
public void Setup()
|
||||||
|
@ -39,56 +36,7 @@ namespace NzbDrone.Core.Test.MediaFiles
|
||||||
Mocker.GetMock<IDownloadedEpisodesImportService>()
|
Mocker.GetMock<IDownloadedEpisodesImportService>()
|
||||||
.Setup(v => v.ProcessFolder(It.IsAny<DirectoryInfo>(), It.IsAny<DownloadClientItem>()))
|
.Setup(v => v.ProcessFolder(It.IsAny<DirectoryInfo>(), It.IsAny<DownloadClientItem>()))
|
||||||
.Returns(new List<ImportResult>());
|
.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]
|
[Test]
|
||||||
|
@ -110,41 +58,6 @@ namespace NzbDrone.Core.Test.MediaFiles
|
||||||
|
|
||||||
ExceptionVerification.ExpectedWarns(1);
|
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\MappingExtentionFixture.cs" />
|
||||||
<Compile Include="Datastore\MarrDataLazyLoadingFixture.cs" />
|
<Compile Include="Datastore\MarrDataLazyLoadingFixture.cs" />
|
||||||
<Compile Include="Datastore\Migration\071_unknown_quality_in_profileFixture.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\Migration\070_delay_profileFixture.cs" />
|
||||||
<Compile Include="Datastore\ObjectDatabaseFixture.cs" />
|
<Compile Include="Datastore\ObjectDatabaseFixture.cs" />
|
||||||
<Compile Include="Datastore\PagingSpecExtensionsTests\PagingOffsetFixture.cs" />
|
<Compile Include="Datastore\PagingSpecExtensionsTests\PagingOffsetFixture.cs" />
|
||||||
|
|
|
@ -11,7 +11,7 @@ namespace NzbDrone.Core.Blacklisting
|
||||||
{
|
{
|
||||||
public interface IBlacklistService
|
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);
|
PagingSpec<Blacklist> Paged(PagingSpec<Blacklist> pagingSpec);
|
||||||
void Delete(int id);
|
void Delete(int id);
|
||||||
}
|
}
|
||||||
|
@ -19,16 +19,13 @@ namespace NzbDrone.Core.Blacklisting
|
||||||
public class BlacklistService : IBlacklistService,
|
public class BlacklistService : IBlacklistService,
|
||||||
IExecute<ClearBlacklistCommand>,
|
IExecute<ClearBlacklistCommand>,
|
||||||
IHandle<DownloadFailedEvent>,
|
IHandle<DownloadFailedEvent>,
|
||||||
IHandle<SeriesDeletedEvent>
|
IHandleAsync<SeriesDeletedEvent>
|
||||||
{
|
{
|
||||||
private readonly IBlacklistRepository _blacklistRepository;
|
private readonly IBlacklistRepository _blacklistRepository;
|
||||||
private readonly IRedownloadFailedDownloads _redownloadFailedDownloadService;
|
|
||||||
|
|
||||||
public BlacklistService(IBlacklistRepository blacklistRepository,
|
public BlacklistService(IBlacklistRepository blacklistRepository)
|
||||||
IRedownloadFailedDownloads redownloadFailedDownloadService)
|
|
||||||
{
|
{
|
||||||
_blacklistRepository = blacklistRepository;
|
_blacklistRepository = blacklistRepository;
|
||||||
_redownloadFailedDownloadService = redownloadFailedDownloadService;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Blacklisted(int seriesId, string sourceTitle, DateTime publishedDate)
|
public bool Blacklisted(int seriesId, string sourceTitle, DateTime publishedDate)
|
||||||
|
@ -48,7 +45,7 @@ namespace NzbDrone.Core.Blacklisting
|
||||||
_blacklistRepository.Delete(id);
|
_blacklistRepository.Delete(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool HasSamePublishedDate(Blacklist item, DateTime publishedDate)
|
private static bool HasSamePublishedDate(Blacklist item, DateTime publishedDate)
|
||||||
{
|
{
|
||||||
if (!item.PublishedDate.HasValue) return true;
|
if (!item.PublishedDate.HasValue) return true;
|
||||||
|
|
||||||
|
@ -70,15 +67,13 @@ namespace NzbDrone.Core.Blacklisting
|
||||||
SourceTitle = message.SourceTitle,
|
SourceTitle = message.SourceTitle,
|
||||||
Quality = message.Quality,
|
Quality = message.Quality,
|
||||||
Date = DateTime.UtcNow,
|
Date = DateTime.UtcNow,
|
||||||
PublishedDate = DateTime.Parse(message.Data.GetValueOrDefault("publishedDate", null))
|
PublishedDate = DateTime.Parse(message.Data.GetValueOrDefault("publishedDate"))
|
||||||
};
|
};
|
||||||
|
|
||||||
_blacklistRepository.Insert(blacklist);
|
_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);
|
var blacklisted = _blacklistRepository.BlacklistedBySeries(message.Series.Id);
|
||||||
|
|
||||||
|
|
|
@ -124,7 +124,7 @@ namespace NzbDrone.Core.Configuration
|
||||||
|
|
||||||
public Boolean EnableCompletedDownloadHandling
|
public Boolean EnableCompletedDownloadHandling
|
||||||
{
|
{
|
||||||
get { return GetValueBoolean("EnableCompletedDownloadHandling", false); }
|
get { return GetValueBoolean("EnableCompletedDownloadHandling", true); }
|
||||||
|
|
||||||
set { SetValue("EnableCompletedDownloadHandling", value); }
|
set { SetValue("EnableCompletedDownloadHandling", value); }
|
||||||
}
|
}
|
||||||
|
@ -136,13 +136,6 @@ namespace NzbDrone.Core.Configuration
|
||||||
set { SetValue("RemoveCompletedDownloads", value); }
|
set { SetValue("RemoveCompletedDownloads", value); }
|
||||||
}
|
}
|
||||||
|
|
||||||
public Boolean EnableFailedDownloadHandling
|
|
||||||
{
|
|
||||||
get { return GetValueBoolean("EnableFailedDownloadHandling", true); }
|
|
||||||
|
|
||||||
set { SetValue("EnableFailedDownloadHandling", value); }
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean AutoRedownloadFailed
|
public Boolean AutoRedownloadFailed
|
||||||
{
|
{
|
||||||
get { return GetValueBoolean("AutoRedownloadFailed", true); }
|
get { return GetValueBoolean("AutoRedownloadFailed", true); }
|
||||||
|
@ -157,27 +150,6 @@ namespace NzbDrone.Core.Configuration
|
||||||
set { SetValue("RemoveFailedDownloads", value); }
|
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
|
public Boolean CreateEmptySeriesFolders
|
||||||
{
|
{
|
||||||
get { return GetValueBoolean("CreateEmptySeriesFolders", false); }
|
get { return GetValueBoolean("CreateEmptySeriesFolders", false); }
|
||||||
|
|
|
@ -22,12 +22,8 @@ namespace NzbDrone.Core.Configuration
|
||||||
Boolean EnableCompletedDownloadHandling { get; set; }
|
Boolean EnableCompletedDownloadHandling { get; set; }
|
||||||
Boolean RemoveCompletedDownloads { get; set; }
|
Boolean RemoveCompletedDownloads { get; set; }
|
||||||
|
|
||||||
Boolean EnableFailedDownloadHandling { get; set; }
|
|
||||||
Boolean AutoRedownloadFailed { get; set; }
|
Boolean AutoRedownloadFailed { get; set; }
|
||||||
Boolean RemoveFailedDownloads { get; set; }
|
Boolean RemoveFailedDownloads { get; set; }
|
||||||
Int32 BlacklistGracePeriod { get; set; }
|
|
||||||
Int32 BlacklistRetryInterval { get; set; }
|
|
||||||
Int32 BlacklistRetryLimit { get; set; }
|
|
||||||
|
|
||||||
//Media Management
|
//Media Management
|
||||||
Boolean AutoUnmonitorPreviouslyDownloadedEpisodes { get; set; }
|
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 Boolean Accepted { get; private set; }
|
||||||
public String Reason { get; private set; }
|
public String Reason { get; private set; }
|
||||||
|
|
||||||
|
private static readonly Decision AcceptDecision = new Decision { Accepted = true };
|
||||||
|
private Decision()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public static Decision Accept()
|
public static Decision Accept()
|
||||||
{
|
{
|
||||||
return new Decision
|
return AcceptDecision;
|
||||||
{
|
|
||||||
Accepted = true
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Decision Reject(String reason, params object[] args)
|
public static Decision Reject(String reason, params object[] args)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Core.Blacklisting;
|
using NzbDrone.Core.Blacklisting;
|
||||||
using NzbDrone.Core.Configuration;
|
using NzbDrone.Core.Indexers;
|
||||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||||
using NzbDrone.Core.Parser.Model;
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
|
||||||
|
@ -9,13 +9,11 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||||
public class BlacklistSpecification : IDecisionEngineSpecification
|
public class BlacklistSpecification : IDecisionEngineSpecification
|
||||||
{
|
{
|
||||||
private readonly IBlacklistService _blacklistService;
|
private readonly IBlacklistService _blacklistService;
|
||||||
private readonly IConfigService _configService;
|
|
||||||
private readonly Logger _logger;
|
private readonly Logger _logger;
|
||||||
|
|
||||||
public BlacklistSpecification(IBlacklistService blacklistService, IConfigService configService, Logger logger)
|
public BlacklistSpecification(IBlacklistService blacklistService, Logger logger)
|
||||||
{
|
{
|
||||||
_blacklistService = blacklistService;
|
_blacklistService = blacklistService;
|
||||||
_configService = configService;
|
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,12 +21,12 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||||
|
|
||||||
public Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
|
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();
|
return Decision.Accept();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (_blacklistService.Blacklisted(subject.Series.Id, subject.Release.Title, subject.Release.PublishDate))
|
if (_blacklistService.Blacklisted(subject.Series.Id, subject.Release.Title, subject.Release.PublishDate))
|
||||||
{
|
{
|
||||||
_logger.Debug("{0} is blacklisted, rejecting.", subject.Release.Title);
|
_logger.Debug("{0} is blacklisted, rejecting.", subject.Release.Title);
|
||||||
|
|
|
@ -1,21 +1,21 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Core.Download;
|
|
||||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||||
using NzbDrone.Core.Parser.Model;
|
using NzbDrone.Core.Parser.Model;
|
||||||
using NzbDrone.Core.Qualities;
|
using NzbDrone.Core.Qualities;
|
||||||
|
using NzbDrone.Core.Queue;
|
||||||
|
|
||||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||||
{
|
{
|
||||||
public class NotInQueueSpecification : IDecisionEngineSpecification
|
public class NotInQueueSpecification : IDecisionEngineSpecification
|
||||||
{
|
{
|
||||||
private readonly IDownloadTrackingService _downloadTrackingService;
|
private readonly IQueueService _queueService;
|
||||||
private readonly Logger _logger;
|
private readonly Logger _logger;
|
||||||
|
|
||||||
public NotInQueueSpecification(IDownloadTrackingService downloadTrackingService, Logger logger)
|
public NotInQueueSpecification(IQueueService queueService, Logger logger)
|
||||||
{
|
{
|
||||||
_downloadTrackingService = downloadTrackingService;
|
_queueService = queueService;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,8 +23,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||||
|
|
||||||
public Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
|
public Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
|
||||||
{
|
{
|
||||||
var queue = _downloadTrackingService.GetQueuedDownloads()
|
var queue = _queueService.GetQueue()
|
||||||
.Where(v => v.State == TrackedDownloadState.Downloading)
|
|
||||||
.Select(q => q.RemoteEpisode).ToList();
|
.Select(q => q.RemoteEpisode).ToList();
|
||||||
|
|
||||||
if (IsInQueue(subject, queue))
|
if (IsInQueue(subject, queue))
|
||||||
|
@ -36,9 +35,9 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||||
return Decision.Accept();
|
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);
|
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());
|
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)
|
foreach (var torrent in torrents)
|
||||||
{
|
{
|
||||||
var item = new DownloadClientItem();
|
var item = new DownloadClientItem();
|
||||||
item.DownloadClientId = torrent.Hash.ToUpper();
|
item.DownloadId = torrent.Hash.ToUpper();
|
||||||
item.Title = torrent.Name;
|
item.Title = torrent.Name;
|
||||||
item.Category = Settings.TvCategory;
|
item.Category = Settings.TvCategory;
|
||||||
|
|
||||||
item.DownloadClient = Definition.Name;
|
item.DownloadClient = Definition.Name;
|
||||||
item.DownloadTime = TimeSpan.FromSeconds(torrent.SecondsDownloading);
|
|
||||||
|
|
||||||
var outputPath = _remotePathMappingService.RemapRemoteToLocal(Settings.Host, new OsPath(torrent.DownloadPath));
|
var outputPath = _remotePathMappingService.RemapRemoteToLocal(Settings.Host, new OsPath(torrent.DownloadPath));
|
||||||
item.OutputPath = outputPath + torrent.Name;
|
item.OutputPath = outputPath + torrent.Name;
|
||||||
|
|
|
@ -283,7 +283,7 @@ namespace NzbDrone.Core.Download.Clients.Deluge
|
||||||
if (resultHosts.Result != null)
|
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
|
// 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)
|
if (connection != null)
|
||||||
{
|
{
|
||||||
|
|
|
@ -69,10 +69,11 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
|
||||||
var droneParameter = item.Parameters.SingleOrDefault(p => p.Name == "drone");
|
var droneParameter = item.Parameters.SingleOrDefault(p => p.Name == "drone");
|
||||||
|
|
||||||
var queueItem = new DownloadClientItem();
|
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.Title = item.NzbName;
|
||||||
queueItem.TotalSize = totalSize;
|
queueItem.TotalSize = totalSize;
|
||||||
queueItem.Category = item.Category;
|
queueItem.Category = item.Category;
|
||||||
|
queueItem.DownloadClient = Definition.Name;
|
||||||
|
|
||||||
if (globalStatus.DownloadPaused || remainingSize == pausedSize)
|
if (globalStatus.DownloadPaused || remainingSize == pausedSize)
|
||||||
{
|
{
|
||||||
|
@ -128,7 +129,7 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
|
||||||
|
|
||||||
var historyItem = new DownloadClientItem();
|
var historyItem = new DownloadClientItem();
|
||||||
historyItem.DownloadClient = Definition.Name;
|
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.Title = item.Name;
|
||||||
historyItem.TotalSize = MakeInt64(item.FileSizeHi, item.FileSizeLo);
|
historyItem.TotalSize = MakeInt64(item.FileSizeHi, item.FileSizeLo);
|
||||||
historyItem.OutputPath = _remotePathMappingService.RemapRemoteToLocal(Settings.Host, new OsPath(item.DestDir));
|
historyItem.OutputPath = _remotePathMappingService.RemapRemoteToLocal(Settings.Host, new OsPath(item.DestDir));
|
||||||
|
@ -181,13 +182,7 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
|
||||||
{
|
{
|
||||||
MigrateLocalCategoryPath();
|
MigrateLocalCategoryPath();
|
||||||
|
|
||||||
foreach (var downloadClientItem in GetQueue().Concat(GetHistory()))
|
return GetQueue().Concat(GetHistory()).Where(downloadClientItem => downloadClientItem.Category == Settings.TvCategory);
|
||||||
{
|
|
||||||
if (downloadClientItem.Category == Settings.TvCategory)
|
|
||||||
{
|
|
||||||
yield return downloadClientItem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void RemoveItem(String id)
|
public override void RemoveItem(String id)
|
||||||
|
|
|
@ -84,7 +84,7 @@ namespace NzbDrone.Core.Download.Clients.Pneumatic
|
||||||
var historyItem = new DownloadClientItem
|
var historyItem = new DownloadClientItem
|
||||||
{
|
{
|
||||||
DownloadClient = Definition.Name,
|
DownloadClient = Definition.Name,
|
||||||
DownloadClientId = GetDownloadClientId(file),
|
DownloadId = GetDownloadClientId(file),
|
||||||
Title = title,
|
Title = title,
|
||||||
|
|
||||||
TotalSize = _diskProvider.GetFileSize(file),
|
TotalSize = _diskProvider.GetFileSize(file),
|
||||||
|
|
|
@ -67,7 +67,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
||||||
{
|
{
|
||||||
var queueItem = new DownloadClientItem();
|
var queueItem = new DownloadClientItem();
|
||||||
queueItem.DownloadClient = Definition.Name;
|
queueItem.DownloadClient = Definition.Name;
|
||||||
queueItem.DownloadClientId = sabQueueItem.Id;
|
queueItem.DownloadId = sabQueueItem.Id;
|
||||||
queueItem.Category = sabQueueItem.Category;
|
queueItem.Category = sabQueueItem.Category;
|
||||||
queueItem.Title = sabQueueItem.Title;
|
queueItem.Title = sabQueueItem.Title;
|
||||||
queueItem.TotalSize = (long)(sabQueueItem.Size * 1024 * 1024);
|
queueItem.TotalSize = (long)(sabQueueItem.Size * 1024 * 1024);
|
||||||
|
@ -122,13 +122,12 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
||||||
var historyItem = new DownloadClientItem
|
var historyItem = new DownloadClientItem
|
||||||
{
|
{
|
||||||
DownloadClient = Definition.Name,
|
DownloadClient = Definition.Name,
|
||||||
DownloadClientId = sabHistoryItem.Id,
|
DownloadId = sabHistoryItem.Id,
|
||||||
Category = sabHistoryItem.Category,
|
Category = sabHistoryItem.Category,
|
||||||
Title = sabHistoryItem.Title,
|
Title = sabHistoryItem.Title,
|
||||||
|
|
||||||
TotalSize = sabHistoryItem.Size,
|
TotalSize = sabHistoryItem.Size,
|
||||||
RemainingSize = 0,
|
RemainingSize = 0,
|
||||||
DownloadTime = TimeSpan.FromSeconds(sabHistoryItem.DownloadTime),
|
|
||||||
RemainingTime = TimeSpan.Zero,
|
RemainingTime = TimeSpan.Zero,
|
||||||
|
|
||||||
Message = sabHistoryItem.FailMessage
|
Message = sabHistoryItem.FailMessage
|
||||||
|
@ -193,7 +192,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
||||||
|
|
||||||
public override void RemoveItem(String id)
|
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);
|
_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)
|
// 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 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)
|
if (currentHistoryItems.Count != 1)
|
||||||
{
|
{
|
||||||
|
@ -219,7 +218,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
||||||
|
|
||||||
var currentHistoryItem = currentHistoryItems.First();
|
var currentHistoryItem = currentHistoryItems.First();
|
||||||
var otherItemsWithSameTitle = currentHistory.Where(h => h.Title == currentHistoryItem.Title &&
|
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);
|
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 &&
|
var history = GetHistory().Where(v => v.Category == currentHistoryItem.Category &&
|
||||||
v.Title == currentHistoryItem.Title &&
|
v.Title == currentHistoryItem.Title &&
|
||||||
!otherItemsWithSameTitle.Select(h => h.DownloadClientId)
|
!otherItemsWithSameTitle.Select(h => h.DownloadId)
|
||||||
.Contains(v.DownloadClientId)).ToList();
|
.Contains(v.DownloadId)).ToList();
|
||||||
|
|
||||||
if (queue.Count == 1)
|
if (queue.Count == 1)
|
||||||
{
|
{
|
||||||
return queue.First().DownloadClientId;
|
return queue.First().DownloadId;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (history.Count == 1)
|
if (history.Count == 1)
|
||||||
{
|
{
|
||||||
return history.First().DownloadClientId;
|
return history.First().DownloadId;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (queue.Count > 1 || history.Count > 1)
|
if (queue.Count > 1 || history.Count > 1)
|
||||||
|
|
|
@ -70,7 +70,7 @@ namespace NzbDrone.Core.Download.Clients.TorrentBlackhole
|
||||||
var historyItem = new DownloadClientItem
|
var historyItem = new DownloadClientItem
|
||||||
{
|
{
|
||||||
DownloadClient = Definition.Name,
|
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",
|
Category = "nzbdrone",
|
||||||
Title = title,
|
Title = title,
|
||||||
|
|
||||||
|
@ -100,7 +100,7 @@ namespace NzbDrone.Core.Download.Clients.TorrentBlackhole
|
||||||
var historyItem = new DownloadClientItem
|
var historyItem = new DownloadClientItem
|
||||||
{
|
{
|
||||||
DownloadClient = Definition.Name,
|
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",
|
Category = "nzbdrone",
|
||||||
Title = title,
|
Title = title,
|
||||||
|
|
||||||
|
|
|
@ -102,12 +102,11 @@ namespace NzbDrone.Core.Download.Clients.Transmission
|
||||||
}
|
}
|
||||||
|
|
||||||
var item = new DownloadClientItem();
|
var item = new DownloadClientItem();
|
||||||
item.DownloadClientId = torrent.HashString.ToUpper();
|
item.DownloadId = torrent.HashString.ToUpper();
|
||||||
item.Category = Settings.TvCategory;
|
item.Category = Settings.TvCategory;
|
||||||
item.Title = torrent.Name;
|
item.Title = torrent.Name;
|
||||||
|
|
||||||
item.DownloadClient = Definition.Name;
|
item.DownloadClient = Definition.Name;
|
||||||
item.DownloadTime = TimeSpan.FromSeconds(torrent.SecondsDownloading);
|
|
||||||
|
|
||||||
item.OutputPath = outputPath + torrent.Name;
|
item.OutputPath = outputPath + torrent.Name;
|
||||||
item.RemainingSize = torrent.LeftUntilDone;
|
item.RemainingSize = torrent.LeftUntilDone;
|
||||||
|
|
|
@ -68,7 +68,7 @@ namespace NzbDrone.Core.Download.Clients.UsenetBlackhole
|
||||||
var historyItem = new DownloadClientItem
|
var historyItem = new DownloadClientItem
|
||||||
{
|
{
|
||||||
DownloadClient = Definition.Name,
|
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",
|
Category = "nzbdrone",
|
||||||
Title = title,
|
Title = title,
|
||||||
|
|
||||||
|
@ -98,7 +98,7 @@ namespace NzbDrone.Core.Download.Clients.UsenetBlackhole
|
||||||
var historyItem = new DownloadClientItem
|
var historyItem = new DownloadClientItem
|
||||||
{
|
{
|
||||||
DownloadClient = Definition.Name,
|
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",
|
Category = "nzbdrone",
|
||||||
Title = title,
|
Title = title,
|
||||||
|
|
||||||
|
|
|
@ -89,7 +89,7 @@ namespace NzbDrone.Core.Download.Clients.UTorrent
|
||||||
}
|
}
|
||||||
|
|
||||||
var item = new DownloadClientItem();
|
var item = new DownloadClientItem();
|
||||||
item.DownloadClientId = torrent.Hash;
|
item.DownloadId = torrent.Hash;
|
||||||
item.Title = torrent.Name;
|
item.Title = torrent.Name;
|
||||||
item.TotalSize = torrent.Size;
|
item.TotalSize = torrent.Size;
|
||||||
item.Category = torrent.Label;
|
item.Category = torrent.Label;
|
||||||
|
|
|
@ -1,244 +1,98 @@
|
||||||
using System;
|
using System.IO;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Common.Disk;
|
using NzbDrone.Common.Disk;
|
||||||
using NzbDrone.Common.Extensions;
|
using NzbDrone.Common.Extensions;
|
||||||
using NzbDrone.Core.Configuration;
|
using NzbDrone.Core.Configuration;
|
||||||
|
using NzbDrone.Core.Download.TrackedDownloads;
|
||||||
using NzbDrone.Core.History;
|
using NzbDrone.Core.History;
|
||||||
using NzbDrone.Core.MediaFiles;
|
using NzbDrone.Core.MediaFiles;
|
||||||
using NzbDrone.Core.MediaFiles.EpisodeImport;
|
using NzbDrone.Core.MediaFiles.EpisodeImport;
|
||||||
using System.IO;
|
using NzbDrone.Core.Messaging.Events;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Download
|
namespace NzbDrone.Core.Download
|
||||||
{
|
{
|
||||||
public interface ICompletedDownloadService
|
public interface ICompletedDownloadService
|
||||||
{
|
{
|
||||||
void CheckForCompletedItem(IDownloadClient downloadClient, TrackedDownload trackedDownload, List<History.History> grabbedHistory, List<History.History> importedHistory);
|
void Process(TrackedDownload trackedDownload);
|
||||||
List<ImportResult> Import(TrackedDownload trackedDownload, String overrideOutputPath = null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class CompletedDownloadService : ICompletedDownloadService
|
public class CompletedDownloadService : ICompletedDownloadService
|
||||||
{
|
{
|
||||||
private readonly IConfigService _configService;
|
private readonly IConfigService _configService;
|
||||||
private readonly IDiskProvider _diskProvider;
|
private readonly IEventAggregator _eventAggregator;
|
||||||
private readonly IDownloadedEpisodesImportService _downloadedEpisodesImportService;
|
|
||||||
private readonly IHistoryService _historyService;
|
private readonly IHistoryService _historyService;
|
||||||
private readonly Logger _logger;
|
private readonly IDownloadedEpisodesImportService _downloadedEpisodesImportService;
|
||||||
|
|
||||||
public CompletedDownloadService(IConfigService configService,
|
public CompletedDownloadService(IConfigService configService,
|
||||||
IDiskProvider diskProvider,
|
IEventAggregator eventAggregator,
|
||||||
IDownloadedEpisodesImportService downloadedEpisodesImportService,
|
|
||||||
IHistoryService historyService,
|
IHistoryService historyService,
|
||||||
Logger logger)
|
IDownloadedEpisodesImportService downloadedEpisodesImportService)
|
||||||
{
|
{
|
||||||
_configService = configService;
|
_configService = configService;
|
||||||
_diskProvider = diskProvider;
|
_eventAggregator = eventAggregator;
|
||||||
_downloadedEpisodesImportService = downloadedEpisodesImportService;
|
|
||||||
_historyService = historyService;
|
_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)))
|
if (trackedDownload.DownloadItem.Status != DownloadItemStatus.Completed)
|
||||||
.ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void CheckForCompletedItem(IDownloadClient downloadClient, TrackedDownload trackedDownload, List<History.History> grabbedHistory, List<History.History> importedHistory)
|
|
||||||
{
|
|
||||||
if (!_configService.EnableCompletedDownloadHandling)
|
|
||||||
{
|
{
|
||||||
return;
|
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);
|
trackedDownload.Warn("Download wasn't grabbed by Sonarr and not in a category, Skipping.");
|
||||||
|
return;
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 = trackedDownload.DownloadItem.OutputPath.FullPath;
|
||||||
var outputPath = overrideOutputPath ?? 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())
|
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)
|
trackedDownload.State = TrackedDownloadStage.Imported;
|
||||||
.Select(v => new TrackedDownloadStatusMessage(Path.GetFileName(v.ImportDecision.LocalEpisode.Path), v.Errors)).ToList();
|
_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;
|
||||||
|
using System.Diagnostics;
|
||||||
using NzbDrone.Common.Disk;
|
using NzbDrone.Common.Disk;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Download
|
namespace NzbDrone.Core.Download
|
||||||
{
|
{
|
||||||
|
[DebuggerDisplay("{DownloadClient}:{Title}")]
|
||||||
public class DownloadClientItem
|
public class DownloadClientItem
|
||||||
{
|
{
|
||||||
public String DownloadClient { get; set; }
|
public String DownloadClient { get; set; }
|
||||||
public String DownloadClientId { get; set; }
|
public String DownloadId { get; set; }
|
||||||
public String Category { get; set; }
|
public String Category { get; set; }
|
||||||
public String Title { get; set; }
|
public String Title { get; set; }
|
||||||
|
|
||||||
public Int64 TotalSize { get; set; }
|
public Int64 TotalSize { get; set; }
|
||||||
public Int64 RemainingSize { get; set; }
|
public Int64 RemainingSize { get; set; }
|
||||||
public TimeSpan? DownloadTime { get; set; }
|
|
||||||
public TimeSpan? RemainingTime { get; set; }
|
public TimeSpan? RemainingTime { get; set; }
|
||||||
|
|
||||||
public OsPath OutputPath { get; set; }
|
public OsPath OutputPath { get; set; }
|
||||||
|
@ -21,5 +22,7 @@ namespace NzbDrone.Core.Download
|
||||||
public DownloadItemStatus Status { get; set; }
|
public DownloadItemStatus Status { get; set; }
|
||||||
public Boolean IsEncrypted { get; set; }
|
public Boolean IsEncrypted { get; set; }
|
||||||
public Boolean IsReadOnly { get; set; }
|
public Boolean IsReadOnly { get; set; }
|
||||||
|
|
||||||
|
public bool Removed { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,10 +29,9 @@ namespace NzbDrone.Core.Download
|
||||||
{
|
{
|
||||||
return _downloadClientFactory.GetAvailableProviders();
|
return _downloadClientFactory.GetAvailableProviders();
|
||||||
}
|
}
|
||||||
|
|
||||||
public IDownloadClient Get(int id)
|
public IDownloadClient Get(int id)
|
||||||
{
|
{
|
||||||
return _downloadClientFactory.GetAvailableProviders().Single(d => d.Definition.Id == id);
|
return _downloadClientFactory.GetAvailableProviders().Single(d => d.Definition.Id == id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
using NzbDrone.Core.Messaging.Events;
|
using NzbDrone.Core.Messaging.Events;
|
||||||
using NzbDrone.Core.ThingiProvider;
|
using NzbDrone.Core.ThingiProvider;
|
||||||
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.Download
|
namespace NzbDrone.Core.Download
|
||||||
{
|
{
|
||||||
public interface IDownloadClientRepository : IProviderRepository<DownloadClientDefinition>
|
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 QualityModel Quality { get; set; }
|
||||||
public String SourceTitle { get; set; }
|
public String SourceTitle { get; set; }
|
||||||
public String DownloadClient { get; set; }
|
public String DownloadClient { get; set; }
|
||||||
public String DownloadClientId { get; set; }
|
public String DownloadId { get; set; }
|
||||||
public String Message { get; set; }
|
public String Message { get; set; }
|
||||||
public Dictionary<string, string> Data { get; set; }
|
public Dictionary<string, string> Data { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,7 +47,7 @@ namespace NzbDrone.Core.Download
|
||||||
|
|
||||||
if (!String.IsNullOrWhiteSpace(downloadClientId))
|
if (!String.IsNullOrWhiteSpace(downloadClientId))
|
||||||
{
|
{
|
||||||
episodeGrabbedEvent.DownloadClientId = downloadClientId;
|
episodeGrabbedEvent.DownloadId = downloadClientId;
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.ProgressInfo("Report sent to download client. {0}", downloadTitle);
|
_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 RemoteEpisode Episode { get; private set; }
|
||||||
public String DownloadClient { get; set; }
|
public String DownloadClient { get; set; }
|
||||||
public String DownloadClientId { get; set; }
|
public String DownloadId { get; set; }
|
||||||
|
|
||||||
public EpisodeGrabbedEvent(RemoteEpisode episode)
|
public EpisodeGrabbedEvent(RemoteEpisode episode)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NLog;
|
|
||||||
using NzbDrone.Common.Extensions;
|
using NzbDrone.Common.Extensions;
|
||||||
using NzbDrone.Core.Configuration;
|
using NzbDrone.Core.Download.TrackedDownloads;
|
||||||
using NzbDrone.Core.History;
|
using NzbDrone.Core.History;
|
||||||
using NzbDrone.Core.Messaging.Events;
|
using NzbDrone.Core.Messaging.Events;
|
||||||
|
|
||||||
|
@ -11,208 +9,59 @@ namespace NzbDrone.Core.Download
|
||||||
{
|
{
|
||||||
public interface IFailedDownloadService
|
public interface IFailedDownloadService
|
||||||
{
|
{
|
||||||
void MarkAsFailed(TrackedDownload trackedDownload, History.History grabbedHistory);
|
void MarkAsFailed(int historyId);
|
||||||
void CheckForFailedItem(IDownloadClient downloadClient, TrackedDownload trackedDownload, List<History.History> grabbedHistory, List<History.History> failedHistory);
|
void Process(TrackedDownload trackedDownload);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class FailedDownloadService : IFailedDownloadService
|
public class FailedDownloadService : IFailedDownloadService
|
||||||
{
|
{
|
||||||
private readonly IHistoryService _historyService;
|
private readonly IHistoryService _historyService;
|
||||||
private readonly IEventAggregator _eventAggregator;
|
private readonly IEventAggregator _eventAggregator;
|
||||||
private readonly IConfigService _configService;
|
|
||||||
private readonly Logger _logger;
|
|
||||||
|
|
||||||
public FailedDownloadService(IHistoryService historyService,
|
public FailedDownloadService(IHistoryService historyService,
|
||||||
IEventAggregator eventAggregator,
|
IEventAggregator eventAggregator)
|
||||||
IConfigService configService,
|
|
||||||
Logger logger)
|
|
||||||
{
|
{
|
||||||
_historyService = historyService;
|
_historyService = historyService;
|
||||||
_eventAggregator = eventAggregator;
|
_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)
|
var history = _historyService.Get(historyId);
|
||||||
{
|
|
||||||
trackedDownload.State = TrackedDownloadState.DownloadFailed;
|
|
||||||
}
|
|
||||||
|
|
||||||
var downloadClientId = history.Data.GetValueOrDefault(DownloadTrackingService.DOWNLOAD_CLIENT_ID);
|
var downloadId = history.DownloadId;
|
||||||
if (downloadClientId.IsNullOrWhiteSpace())
|
if (downloadId.IsNullOrWhiteSpace())
|
||||||
{
|
{
|
||||||
PublishDownloadFailedEvent(new List<History.History> { history }, "Manually marked as failed");
|
PublishDownloadFailedEvent(new List<History.History> { history }, "Manually marked as failed");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var grabbedHistory = GetHistoryItems(_historyService.Grabbed(), downloadClientId);
|
var grabbedHistory = _historyService.Find(downloadId, HistoryEventType.Grabbed).ToList();
|
||||||
|
|
||||||
PublishDownloadFailedEvent(grabbedHistory, "Manually marked as failed");
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (trackedDownload.DownloadItem.IsEncrypted && trackedDownload.State == TrackedDownloadState.Downloading)
|
if (trackedDownload.DownloadItem.IsEncrypted)
|
||||||
{
|
{
|
||||||
var grabbedItems = GetHistoryItems(grabbedHistory, trackedDownload.DownloadItem.DownloadClientId);
|
trackedDownload.State = TrackedDownloadStage.DownloadFailed;
|
||||||
|
PublishDownloadFailedEvent(grabbedItems, "Encrypted download detected");
|
||||||
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");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
else if (trackedDownload.DownloadItem.Status == DownloadItemStatus.Failed)
|
||||||
if (trackedDownload.DownloadItem.Status == DownloadItemStatus.Failed && trackedDownload.State == TrackedDownloadState.Downloading)
|
|
||||||
{
|
{
|
||||||
var grabbedItems = GetHistoryItems(grabbedHistory, trackedDownload.DownloadItem.DownloadClientId);
|
trackedDownload.State = TrackedDownloadStage.DownloadFailed;
|
||||||
|
PublishDownloadFailedEvent(grabbedItems, trackedDownload.DownloadItem.Message);
|
||||||
if (!grabbedItems.Any())
|
|
||||||
{
|
|
||||||
UpdateStatusMessage(trackedDownload, LogLevel.Warn, "Download wasn't grabbed by drone, ignoring download");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var failedItems = GetHistoryItems(failedHistory, trackedDownload.DownloadItem.DownloadClientId);
|
|
||||||
|
|
||||||
if (failedItems.Any())
|
|
||||||
{
|
|
||||||
trackedDownload.State = TrackedDownloadState.DownloadFailed;
|
|
||||||
UpdateStatusMessage(trackedDownload, LogLevel.Debug, "Already added to history as failed.");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (FailedDownloadForRecentRelease(downloadClient, trackedDownload, grabbedItems))
|
|
||||||
{
|
|
||||||
_logger.Debug("[{0}] Recent release Failed, do not blacklist.", trackedDownload.DownloadItem.Title);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
trackedDownload.State = TrackedDownloadState.DownloadFailed;
|
|
||||||
|
|
||||||
PublishDownloadFailedEvent(grabbedItems, trackedDownload.DownloadItem.Message);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (trackedDownload.DownloadItem.Status != DownloadItemStatus.Failed && trackedDownload.State == TrackedDownloadState.Downloading)
|
|
||||||
{
|
|
||||||
var grabbedItems = GetHistoryItems(grabbedHistory, trackedDownload.DownloadItem.DownloadClientId);
|
|
||||||
var failedItems = GetHistoryItems(failedHistory, trackedDownload.DownloadItem.DownloadClientId);
|
|
||||||
|
|
||||||
if (grabbedItems.Any() && failedItems.Any())
|
|
||||||
{
|
|
||||||
UpdateStatusMessage(trackedDownload, LogLevel.Debug, "Already added to history as failed, updating tracked state.");
|
|
||||||
trackedDownload.State = TrackedDownloadState.DownloadFailed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_configService.RemoveFailedDownloads && trackedDownload.State == TrackedDownloadState.DownloadFailed)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_logger.Debug("[{0}] Removing failed download from client.", trackedDownload.DownloadItem.Title);
|
|
||||||
downloadClient.RemoveItem(trackedDownload.DownloadItem.DownloadClientId);
|
|
||||||
|
|
||||||
trackedDownload.State = TrackedDownloadState.Removed;
|
|
||||||
}
|
|
||||||
catch (NotSupportedException)
|
|
||||||
{
|
|
||||||
UpdateStatusMessage(trackedDownload, LogLevel.Debug, "Removing item not supported by your download client.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool FailedDownloadForRecentRelease(IDownloadClient downloadClient, TrackedDownload trackedDownload, List<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)
|
private void PublishDownloadFailedEvent(List<History.History> historyItems, string message)
|
||||||
|
@ -225,35 +74,15 @@ namespace NzbDrone.Core.Download
|
||||||
EpisodeIds = historyItems.Select(h => h.EpisodeId).ToList(),
|
EpisodeIds = historyItems.Select(h => h.EpisodeId).ToList(),
|
||||||
Quality = historyItem.Quality,
|
Quality = historyItem.Quality,
|
||||||
SourceTitle = historyItem.SourceTitle,
|
SourceTitle = historyItem.SourceTitle,
|
||||||
DownloadClient = historyItem.Data.GetValueOrDefault(DownloadTrackingService.DOWNLOAD_CLIENT),
|
DownloadClient = historyItem.Data.GetValueOrDefault(History.History.DOWNLOAD_CLIENT),
|
||||||
DownloadClientId = historyItem.Data.GetValueOrDefault(DownloadTrackingService.DOWNLOAD_CLIENT_ID),
|
DownloadId = historyItem.DownloadId,
|
||||||
Message = message
|
Message = message,
|
||||||
|
Data = historyItem.Data
|
||||||
};
|
};
|
||||||
|
|
||||||
downloadFailedEvent.Data = downloadFailedEvent.Data.Merge(historyItem.Data);
|
|
||||||
|
|
||||||
_eventAggregator.PublishEvent(downloadFailedEvent);
|
_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,
|
Title = pendingRelease.Title,
|
||||||
Size = pendingRelease.RemoteEpisode.Release.Size,
|
Size = pendingRelease.RemoteEpisode.Release.Size,
|
||||||
Sizeleft = pendingRelease.RemoteEpisode.Release.Size,
|
Sizeleft = pendingRelease.RemoteEpisode.Release.Size,
|
||||||
|
RemoteEpisode = pendingRelease.RemoteEpisode,
|
||||||
Timeleft = ect.Subtract(DateTime.UtcNow),
|
Timeleft = ect.Subtract(DateTime.UtcNow),
|
||||||
EstimatedCompletionTime = ect,
|
EstimatedCompletionTime = ect,
|
||||||
Status = "Pending",
|
Status = "Pending"
|
||||||
RemoteEpisode = pendingRelease.RemoteEpisode
|
|
||||||
};
|
};
|
||||||
queued.Add(queue);
|
queued.Add(queue);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,14 @@
|
||||||
using System.Collections.Generic;
|
using System.Linq;
|
||||||
using System.Linq;
|
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Core.Configuration;
|
using NzbDrone.Core.Configuration;
|
||||||
using NzbDrone.Core.IndexerSearch;
|
using NzbDrone.Core.IndexerSearch;
|
||||||
using NzbDrone.Core.Messaging.Commands;
|
using NzbDrone.Core.Messaging.Commands;
|
||||||
|
using NzbDrone.Core.Messaging.Events;
|
||||||
using NzbDrone.Core.Tv;
|
using NzbDrone.Core.Tv;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Download
|
namespace NzbDrone.Core.Download
|
||||||
{
|
{
|
||||||
public interface IRedownloadFailedDownloads
|
public class RedownloadFailedDownloadService : IHandleAsync<DownloadFailedEvent>
|
||||||
{
|
|
||||||
void Redownload(int seriesId, List<int> episodeIds);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class RedownloadFailedDownloadService : IRedownloadFailedDownloads
|
|
||||||
{
|
{
|
||||||
private readonly IConfigService _configService;
|
private readonly IConfigService _configService;
|
||||||
private readonly IEpisodeService _episodeService;
|
private readonly IEpisodeService _episodeService;
|
||||||
|
@ -28,7 +23,7 @@ namespace NzbDrone.Core.Download
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Redownload(int seriesId, List<int> episodeIds)
|
public void HandleAsync(DownloadFailedEvent message)
|
||||||
{
|
{
|
||||||
if (!_configService.AutoRedownloadFailed)
|
if (!_configService.AutoRedownloadFailed)
|
||||||
{
|
{
|
||||||
|
@ -36,34 +31,34 @@ namespace NzbDrone.Core.Download
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (episodeIds.Count == 1)
|
if (message.EpisodeIds.Count == 1)
|
||||||
{
|
{
|
||||||
_logger.Debug("Failed download only contains one episode, searching again");
|
_logger.Debug("Failed download only contains one episode, searching again");
|
||||||
|
|
||||||
_commandExecutor.PublishCommandAsync(new EpisodeSearchCommand(episodeIds));
|
_commandExecutor.PublishCommandAsync(new EpisodeSearchCommand(message.EpisodeIds));
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var seasonNumber = _episodeService.GetEpisode(episodeIds.First()).SeasonNumber;
|
var seasonNumber = _episodeService.GetEpisode(message.EpisodeIds.First()).SeasonNumber;
|
||||||
var episodesInSeason = _episodeService.GetEpisodesBySeason(seriesId, 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");
|
_logger.Debug("Failed download was entire season, searching again");
|
||||||
|
|
||||||
_commandExecutor.PublishCommandAsync(new SeasonSearchCommand
|
_commandExecutor.PublishCommandAsync(new SeasonSearchCommand
|
||||||
{
|
{
|
||||||
SeriesId = seriesId,
|
SeriesId = message.SeriesId,
|
||||||
SeasonNumber = seasonNumber
|
SeasonNumber = seasonNumber
|
||||||
});
|
});
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.Debug("Failed download contains multiple episodes, probably a double episode, searching again");
|
_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;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Download
|
namespace NzbDrone.Core.Download.TrackedDownloads
|
||||||
{
|
{
|
||||||
public class TrackedDownloadStatusMessage
|
public class TrackedDownloadStatusMessage
|
||||||
{
|
{
|
||||||
public String Title { get; set; }
|
public String Title { get; set; }
|
||||||
public List<String> Messages { get; set; }
|
public List<String> Messages { get; set; }
|
||||||
|
|
||||||
private TrackedDownloadStatusMessage()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public TrackedDownloadStatusMessage(String title, List<String> messages)
|
public TrackedDownloadStatusMessage(String title, List<String> messages)
|
||||||
{
|
{
|
|
@ -2,8 +2,8 @@
|
||||||
using NzbDrone.Common.Disk;
|
using NzbDrone.Common.Disk;
|
||||||
using NzbDrone.Core.Configuration;
|
using NzbDrone.Core.Configuration;
|
||||||
using NzbDrone.Core.Download;
|
using NzbDrone.Core.Download;
|
||||||
using NzbDrone.Core.Download.Clients.Sabnzbd;
|
|
||||||
using NzbDrone.Core.Download.Clients.Nzbget;
|
using NzbDrone.Core.Download.Clients.Nzbget;
|
||||||
|
using NzbDrone.Core.Download.Clients.Sabnzbd;
|
||||||
|
|
||||||
namespace NzbDrone.Core.HealthCheck.Checks
|
namespace NzbDrone.Core.HealthCheck.Checks
|
||||||
{
|
{
|
||||||
|
@ -11,13 +11,12 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
||||||
{
|
{
|
||||||
private readonly IConfigService _configService;
|
private readonly IConfigService _configService;
|
||||||
private readonly IProvideDownloadClient _provideDownloadClient;
|
private readonly IProvideDownloadClient _provideDownloadClient;
|
||||||
private readonly IDownloadTrackingService _downloadTrackingService;
|
|
||||||
|
|
||||||
public ImportMechanismCheck(IConfigService configService, IProvideDownloadClient provideDownloadClient, IDownloadTrackingService downloadTrackingService)
|
|
||||||
|
public ImportMechanismCheck(IConfigService configService, IProvideDownloadClient provideDownloadClient)
|
||||||
{
|
{
|
||||||
_configService = configService;
|
_configService = configService;
|
||||||
_provideDownloadClient = provideDownloadClient;
|
_provideDownloadClient = provideDownloadClient;
|
||||||
_downloadTrackingService = downloadTrackingService;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override HealthCheck Check()
|
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");
|
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());
|
return new HealthCheck(GetType());
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,8 @@ namespace NzbDrone.Core.History
|
||||||
{
|
{
|
||||||
public class History : ModelBase
|
public class History : ModelBase
|
||||||
{
|
{
|
||||||
|
public const string DOWNLOAD_CLIENT = "downloadClient";
|
||||||
|
|
||||||
public History()
|
public History()
|
||||||
{
|
{
|
||||||
Data = new Dictionary<string, string>();
|
Data = new Dictionary<string, string>();
|
||||||
|
@ -22,6 +24,9 @@ namespace NzbDrone.Core.History
|
||||||
public Series Series { get; set; }
|
public Series Series { get; set; }
|
||||||
public HistoryEventType EventType { get; set; }
|
public HistoryEventType EventType { get; set; }
|
||||||
public Dictionary<string, string> Data { get; set; }
|
public Dictionary<string, string> Data { get; set; }
|
||||||
|
|
||||||
|
public string DownloadId { get; set; }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum HistoryEventType
|
public enum HistoryEventType
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Marr.Data.QGen;
|
using Marr.Data.QGen;
|
||||||
using NzbDrone.Core.Datastore;
|
using NzbDrone.Core.Datastore;
|
||||||
|
@ -11,31 +10,21 @@ namespace NzbDrone.Core.History
|
||||||
{
|
{
|
||||||
public interface IHistoryRepository : IBasicRepository<History>
|
public interface IHistoryRepository : IBasicRepository<History>
|
||||||
{
|
{
|
||||||
void Trim();
|
|
||||||
List<QualityModel> GetBestQualityInHistory(int episodeId);
|
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);
|
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
|
public class HistoryRepository : BasicRepository<History>, IHistoryRepository
|
||||||
{
|
{
|
||||||
private readonly IDatabase _database;
|
|
||||||
|
|
||||||
public HistoryRepository(IDatabase database, IEventAggregator eventAggregator)
|
public HistoryRepository(IDatabase database, IEventAggregator eventAggregator)
|
||||||
: base(database, 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)
|
public List<QualityModel> GetBestQualityInHistory(int episodeId)
|
||||||
{
|
{
|
||||||
|
@ -44,30 +33,6 @@ namespace NzbDrone.Core.History
|
||||||
return history.Select(h => h.Quality).ToList();
|
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)
|
public History MostRecentForEpisode(int episodeId)
|
||||||
{
|
{
|
||||||
return Query.Where(h => h.EpisodeId == episodeId)
|
return Query.Where(h => h.EpisodeId == episodeId)
|
||||||
|
@ -75,14 +40,27 @@ namespace NzbDrone.Core.History
|
||||||
.FirstOrDefault();
|
.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)
|
protected override SortBuilder<History> GetPagedQuery(QueryBuilder<History> query, PagingSpec<History> pagingSpec)
|
||||||
|
|
|
@ -16,19 +16,12 @@ namespace NzbDrone.Core.History
|
||||||
{
|
{
|
||||||
public interface IHistoryService
|
public interface IHistoryService
|
||||||
{
|
{
|
||||||
List<History> All();
|
|
||||||
void Purge();
|
|
||||||
void Trim();
|
|
||||||
QualityModel GetBestQualityInHistory(Profile profile, int episodeId);
|
QualityModel GetBestQualityInHistory(Profile profile, int episodeId);
|
||||||
PagingSpec<History> Paged(PagingSpec<History> pagingSpec);
|
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 MostRecentForEpisode(int episodeId);
|
||||||
History Get(int id);
|
History MostRecentForDownloadId(string downloadId);
|
||||||
List<History> FindBySourceTitle(string sourceTitle);
|
History Get(int historyId);
|
||||||
void UpdateHistoryData(Int32 historyId, Dictionary<String, String> data);
|
List<History> Find(string downloadId, HistoryEventType eventType);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class HistoryService : IHistoryService,
|
public class HistoryService : IHistoryService,
|
||||||
|
@ -46,60 +39,31 @@ namespace NzbDrone.Core.History
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<History> All()
|
|
||||||
{
|
|
||||||
return _historyRepository.All().ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
public PagingSpec<History> Paged(PagingSpec<History> pagingSpec)
|
public PagingSpec<History> Paged(PagingSpec<History> pagingSpec)
|
||||||
{
|
{
|
||||||
return _historyRepository.GetPaged(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)
|
public History MostRecentForEpisode(int episodeId)
|
||||||
{
|
{
|
||||||
return _historyRepository.MostRecentForEpisode(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)
|
public QualityModel GetBestQualityInHistory(Profile profile, int episodeId)
|
||||||
{
|
{
|
||||||
|
@ -109,13 +73,6 @@ namespace NzbDrone.Core.History
|
||||||
.FirstOrDefault();
|
.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)
|
public void Handle(EpisodeGrabbedEvent message)
|
||||||
{
|
{
|
||||||
foreach (var episode in message.Episode.Episodes)
|
foreach (var episode in message.Episode.Episodes)
|
||||||
|
@ -128,6 +85,7 @@ namespace NzbDrone.Core.History
|
||||||
SourceTitle = message.Episode.Release.Title,
|
SourceTitle = message.Episode.Release.Title,
|
||||||
SeriesId = episode.SeriesId,
|
SeriesId = episode.SeriesId,
|
||||||
EpisodeId = episode.Id,
|
EpisodeId = episode.Id,
|
||||||
|
DownloadId = message.DownloadId
|
||||||
};
|
};
|
||||||
|
|
||||||
history.Data.Add("Indexer", message.Episode.Release.Indexer);
|
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("PublishedDate", message.Episode.Release.PublishDate.ToString("s") + "Z");
|
||||||
history.Data.Add("DownloadClient", message.DownloadClient);
|
history.Data.Add("DownloadClient", message.DownloadClient);
|
||||||
|
|
||||||
if (!String.IsNullOrWhiteSpace(message.DownloadClientId))
|
|
||||||
{
|
|
||||||
history.Data.Add("DownloadClientId", message.DownloadClientId);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!message.Episode.ParsedEpisodeInfo.ReleaseHash.IsNullOrWhiteSpace())
|
if (!message.Episode.ParsedEpisodeInfo.ReleaseHash.IsNullOrWhiteSpace())
|
||||||
{
|
{
|
||||||
history.Data.Add("ReleaseHash", message.Episode.ParsedEpisodeInfo.ReleaseHash);
|
history.Data.Add("ReleaseHash", message.Episode.ParsedEpisodeInfo.ReleaseHash);
|
||||||
|
@ -159,6 +112,13 @@ namespace NzbDrone.Core.History
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var downloadId = message.DownloadId;
|
||||||
|
|
||||||
|
if (downloadId.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
downloadId = FindDownloadId(message);
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var episode in message.EpisodeInfo.Episodes)
|
foreach (var episode in message.EpisodeInfo.Episodes)
|
||||||
{
|
{
|
||||||
var history = new History
|
var history = new History
|
||||||
|
@ -168,7 +128,8 @@ namespace NzbDrone.Core.History
|
||||||
Quality = message.EpisodeInfo.Quality,
|
Quality = message.EpisodeInfo.Quality,
|
||||||
SourceTitle = message.ImportedEpisode.SceneName ?? Path.GetFileNameWithoutExtension(message.EpisodeInfo.Path),
|
SourceTitle = message.ImportedEpisode.SceneName ?? Path.GetFileNameWithoutExtension(message.EpisodeInfo.Path),
|
||||||
SeriesId = message.ImportedEpisode.SeriesId,
|
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.
|
//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("DroppedPath", message.EpisodeInfo.Path);
|
||||||
history.Data.Add("ImportedPath", Path.Combine(message.EpisodeInfo.Series.Path, message.ImportedEpisode.RelativePath));
|
history.Data.Add("ImportedPath", Path.Combine(message.EpisodeInfo.Series.Path, message.ImportedEpisode.RelativePath));
|
||||||
history.Data.Add("DownloadClient", message.DownloadClient);
|
history.Data.Add("DownloadClient", message.DownloadClient);
|
||||||
history.Data.Add("DownloadClientId", message.DownloadClientId);
|
|
||||||
|
|
||||||
_historyRepository.Insert(history);
|
_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)
|
public void Handle(DownloadFailedEvent message)
|
||||||
{
|
{
|
||||||
foreach (var episodeId in message.EpisodeIds)
|
foreach (var episodeId in message.EpisodeIds)
|
||||||
|
@ -194,10 +200,10 @@ namespace NzbDrone.Core.History
|
||||||
SourceTitle = message.SourceTitle,
|
SourceTitle = message.SourceTitle,
|
||||||
SeriesId = message.SeriesId,
|
SeriesId = message.SeriesId,
|
||||||
EpisodeId = episodeId,
|
EpisodeId = episodeId,
|
||||||
|
DownloadId = message.DownloadId
|
||||||
};
|
};
|
||||||
|
|
||||||
history.Data.Add("DownloadClient", message.DownloadClient);
|
history.Data.Add("DownloadClient", message.DownloadClient);
|
||||||
history.Data.Add("DownloadClientId", message.DownloadClientId);
|
|
||||||
history.Data.Add("Message", message.Message);
|
history.Data.Add("Message", message.Message);
|
||||||
|
|
||||||
_historyRepository.Insert(history);
|
_historyRepository.Insert(history);
|
||||||
|
|
|
@ -14,7 +14,5 @@ namespace NzbDrone.Core.MediaFiles.Commands
|
||||||
}
|
}
|
||||||
|
|
||||||
public Boolean SendUpdates { get; set; }
|
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 System.Linq;
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Common.Disk;
|
using NzbDrone.Common.Disk;
|
||||||
using NzbDrone.Common.Extensions;
|
|
||||||
using NzbDrone.Core.Configuration;
|
using NzbDrone.Core.Configuration;
|
||||||
using NzbDrone.Core.Download;
|
|
||||||
using NzbDrone.Core.MediaFiles.Commands;
|
using NzbDrone.Core.MediaFiles.Commands;
|
||||||
using NzbDrone.Core.MediaFiles.EpisodeImport;
|
using NzbDrone.Core.MediaFiles.EpisodeImport;
|
||||||
using NzbDrone.Core.Messaging.Commands;
|
using NzbDrone.Core.Messaging.Commands;
|
||||||
|
@ -16,22 +14,16 @@ namespace NzbDrone.Core.MediaFiles
|
||||||
public class DownloadedEpisodesCommandService : IExecute<DownloadedEpisodesScanCommand>
|
public class DownloadedEpisodesCommandService : IExecute<DownloadedEpisodesScanCommand>
|
||||||
{
|
{
|
||||||
private readonly IDownloadedEpisodesImportService _downloadedEpisodesImportService;
|
private readonly IDownloadedEpisodesImportService _downloadedEpisodesImportService;
|
||||||
private readonly IDownloadTrackingService _downloadTrackingService;
|
|
||||||
private readonly ICompletedDownloadService _completedDownloadService;
|
|
||||||
private readonly IDiskProvider _diskProvider;
|
private readonly IDiskProvider _diskProvider;
|
||||||
private readonly IConfigService _configService;
|
private readonly IConfigService _configService;
|
||||||
private readonly Logger _logger;
|
private readonly Logger _logger;
|
||||||
|
|
||||||
public DownloadedEpisodesCommandService(IDownloadedEpisodesImportService downloadedEpisodesImportService,
|
public DownloadedEpisodesCommandService(IDownloadedEpisodesImportService downloadedEpisodesImportService,
|
||||||
IDownloadTrackingService downloadTrackingService,
|
|
||||||
ICompletedDownloadService completedDownloadService,
|
|
||||||
IDiskProvider diskProvider,
|
IDiskProvider diskProvider,
|
||||||
IConfigService configService,
|
IConfigService configService,
|
||||||
Logger logger)
|
Logger logger)
|
||||||
{
|
{
|
||||||
_downloadedEpisodesImportService = downloadedEpisodesImportService;
|
_downloadedEpisodesImportService = downloadedEpisodesImportService;
|
||||||
_downloadTrackingService = downloadTrackingService;
|
|
||||||
_completedDownloadService = completedDownloadService;
|
|
||||||
_diskProvider = diskProvider;
|
_diskProvider = diskProvider;
|
||||||
_configService = configService;
|
_configService = configService;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
@ -56,43 +48,12 @@ namespace NzbDrone.Core.MediaFiles
|
||||||
return _downloadedEpisodesImportService.ProcessRootFolder(new DirectoryInfo(downloadedEpisodesFolder));
|
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)
|
public void Execute(DownloadedEpisodesScanCommand message)
|
||||||
{
|
{
|
||||||
List<ImportResult> importResults;
|
var importResults = ProcessDroneFactoryFolder();
|
||||||
|
if (importResults == null || importResults.All(v => v.Result != ImportResultType.Imported))
|
||||||
if (message.Path.IsNotNullOrWhiteSpace())
|
|
||||||
{
|
|
||||||
importResults = ProcessFolder(message);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
importResults = ProcessDroneFactoryFolder();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (importResults == null || !importResults.Any(v => v.Result == ImportResultType.Imported))
|
|
||||||
{
|
{
|
||||||
// Atm we don't report it as a command failure, coz that would cause the download to be failed.
|
// 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.
|
// 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> ProcessRootFolder(DirectoryInfo directoryInfo);
|
||||||
List<ImportResult> ProcessFolder(DirectoryInfo directoryInfo, DownloadClientItem downloadClientItem = null);
|
List<ImportResult> ProcessFolder(DirectoryInfo directoryInfo, DownloadClientItem downloadClientItem = null);
|
||||||
List<ImportResult> ProcessFolder(DirectoryInfo directoryInfo, Series series, DownloadClientItem downloadClientItem = null);
|
List<ImportResult> ProcessPath(string path, DownloadClientItem downloadClientItem = null);
|
||||||
List<ImportResult> ProcessFile(FileInfo fileInfo, DownloadClientItem downloadClientItem = null);
|
|
||||||
List<ImportResult> ProcessFile(FileInfo fileInfo, Series series, DownloadClientItem downloadClientItem = null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class DownloadedEpisodesImportService : IDownloadedEpisodesImportService
|
public class DownloadedEpisodesImportService : IDownloadedEpisodesImportService
|
||||||
|
@ -88,12 +86,12 @@ namespace NzbDrone.Core.MediaFiles
|
||||||
return ProcessFolder(directoryInfo, series, downloadClientItem);
|
return ProcessFolder(directoryInfo, series, downloadClientItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<ImportResult> ProcessFolder(DirectoryInfo directoryInfo, Series series,
|
private List<ImportResult> ProcessFolder(DirectoryInfo directoryInfo, Series series,
|
||||||
DownloadClientItem downloadClientItem = null)
|
DownloadClientItem downloadClientItem = null)
|
||||||
{
|
{
|
||||||
if (_seriesService.SeriesPathExists(directoryInfo.FullName))
|
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>();
|
return new List<ImportResult>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -147,7 +145,7 @@ namespace NzbDrone.Core.MediaFiles
|
||||||
return ProcessFile(fileInfo, series, downloadClientItem);
|
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)
|
if (downloadClientItem == null)
|
||||||
{
|
{
|
||||||
|
@ -164,6 +162,16 @@ namespace NzbDrone.Core.MediaFiles
|
||||||
return _importApprovedEpisodes.Import(decisions, true, downloadClientItem);
|
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)
|
private string GetCleanedUpFolderName(string folder)
|
||||||
{
|
{
|
||||||
folder = folder.Replace("_UNPACK_", "")
|
folder = folder.Replace("_UNPACK_", "")
|
||||||
|
|
|
@ -99,7 +99,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
|
||||||
|
|
||||||
if (downloadClientItem != null)
|
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
|
else
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
using NzbDrone.Core.Parser.Model;
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
|
||||||
namespace NzbDrone.Core.MediaFiles.EpisodeImport
|
namespace NzbDrone.Core.MediaFiles.EpisodeImport
|
||||||
|
@ -13,7 +14,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
return !Rejections.Any();
|
return Rejections.Empty();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NzbDrone.Common.EnsureThat;
|
using NzbDrone.Common.EnsureThat;
|
||||||
using NzbDrone.Common.Extensions;
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.MediaFiles.EpisodeImport
|
namespace NzbDrone.Core.MediaFiles.EpisodeImport
|
||||||
{
|
{
|
||||||
|
@ -15,14 +14,17 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
//Approved and imported
|
if (Errors.Any())
|
||||||
if (Errors.Empty()) return ImportResultType.Imported;
|
{
|
||||||
|
if (ImportDecision.Approved)
|
||||||
|
{
|
||||||
|
return ImportResultType.Skipped;
|
||||||
|
}
|
||||||
|
|
||||||
//Decision was approved, but it was not imported
|
return ImportResultType.Rejected;
|
||||||
if (ImportDecision.Approved) return ImportResultType.Skipped;
|
}
|
||||||
|
|
||||||
//Decision was rejected
|
return ImportResultType.Imported;
|
||||||
return ImportResultType.Rejected;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,9 +8,9 @@ namespace NzbDrone.Core.MediaFiles.Events
|
||||||
{
|
{
|
||||||
public LocalEpisode EpisodeInfo { get; private set; }
|
public LocalEpisode EpisodeInfo { get; private set; }
|
||||||
public EpisodeFile ImportedEpisode { get; private set; }
|
public EpisodeFile ImportedEpisode { get; private set; }
|
||||||
public Boolean NewDownload { get; set; }
|
public Boolean NewDownload { get; private set; }
|
||||||
public String DownloadClient { get; set; }
|
public String DownloadClient { get; private set; }
|
||||||
public String DownloadClientId { get; set; }
|
public String DownloadId { get; private set; }
|
||||||
|
|
||||||
public EpisodeImportedEvent(LocalEpisode episodeInfo, EpisodeFile importedEpisode, bool newDownload)
|
public EpisodeImportedEvent(LocalEpisode episodeInfo, EpisodeFile importedEpisode, bool newDownload)
|
||||||
{
|
{
|
||||||
|
@ -19,13 +19,13 @@ namespace NzbDrone.Core.MediaFiles.Events
|
||||||
NewDownload = newDownload;
|
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;
|
EpisodeInfo = episodeInfo;
|
||||||
ImportedEpisode = importedEpisode;
|
ImportedEpisode = importedEpisode;
|
||||||
NewDownload = newDownload;
|
NewDownload = newDownload;
|
||||||
DownloadClient = downloadClient;
|
DownloadClient = downloadClient;
|
||||||
DownloadClientId = downloadClientId;
|
DownloadId = downloadId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -231,6 +231,7 @@
|
||||||
<Compile Include="Datastore\Migration\067_add_added_to_series.cs" />
|
<Compile Include="Datastore\Migration\067_add_added_to_series.cs" />
|
||||||
<Compile Include="Datastore\Migration\069_quality_proper.cs" />
|
<Compile Include="Datastore\Migration\069_quality_proper.cs" />
|
||||||
<Compile Include="Datastore\Migration\071_unknown_quality_in_profile.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\070_delay_profile.cs" />
|
||||||
<Compile Include="Datastore\Migration\Framework\MigrationContext.cs" />
|
<Compile Include="Datastore\Migration\Framework\MigrationContext.cs" />
|
||||||
<Compile Include="Datastore\Migration\Framework\MigrationController.cs" />
|
<Compile Include="Datastore\Migration\Framework\MigrationController.cs" />
|
||||||
|
@ -270,7 +271,6 @@
|
||||||
<Compile Include="DecisionEngine\Specifications\QualityAllowedByProfileSpecification.cs" />
|
<Compile Include="DecisionEngine\Specifications\QualityAllowedByProfileSpecification.cs" />
|
||||||
<Compile Include="DecisionEngine\Specifications\MinimumAgeSpecification.cs" />
|
<Compile Include="DecisionEngine\Specifications\MinimumAgeSpecification.cs" />
|
||||||
<Compile Include="DecisionEngine\Specifications\RetentionSpecification.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\DelaySpecification.cs" />
|
||||||
<Compile Include="DecisionEngine\Specifications\RssSync\HistorySpecification.cs" />
|
<Compile Include="DecisionEngine\Specifications\RssSync\HistorySpecification.cs" />
|
||||||
<Compile Include="DecisionEngine\Specifications\RssSync\MonitoredEpisodeSpecification.cs" />
|
<Compile Include="DecisionEngine\Specifications\RssSync\MonitoredEpisodeSpecification.cs" />
|
||||||
|
@ -353,7 +353,12 @@
|
||||||
<Compile Include="Download\Clients\uTorrent\UTorrentTorrent.cs" />
|
<Compile Include="Download\Clients\uTorrent\UTorrentTorrent.cs" />
|
||||||
<Compile Include="Download\Clients\uTorrent\UTorrentTorrentStatus.cs" />
|
<Compile Include="Download\Clients\uTorrent\UTorrentTorrentStatus.cs" />
|
||||||
<Compile Include="Download\CompletedDownloadService.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\UsenetClientBase.cs" />
|
||||||
<Compile Include="Download\TorrentClientBase.cs" />
|
<Compile Include="Download\TorrentClientBase.cs" />
|
||||||
<Compile Include="Download\DownloadClientBase.cs" />
|
<Compile Include="Download\DownloadClientBase.cs" />
|
||||||
|
@ -367,7 +372,6 @@
|
||||||
<Compile Include="Download\DownloadFailedEvent.cs" />
|
<Compile Include="Download\DownloadFailedEvent.cs" />
|
||||||
<Compile Include="Download\DownloadItemStatus.cs" />
|
<Compile Include="Download\DownloadItemStatus.cs" />
|
||||||
<Compile Include="Download\DownloadService.cs" />
|
<Compile Include="Download\DownloadService.cs" />
|
||||||
<Compile Include="Download\DownloadTrackingService.cs" />
|
|
||||||
<Compile Include="Download\EpisodeGrabbedEvent.cs" />
|
<Compile Include="Download\EpisodeGrabbedEvent.cs" />
|
||||||
<Compile Include="Download\FailedDownloadService.cs" />
|
<Compile Include="Download\FailedDownloadService.cs" />
|
||||||
<Compile Include="Download\IDownloadClient.cs" />
|
<Compile Include="Download\IDownloadClient.cs" />
|
||||||
|
@ -378,7 +382,6 @@
|
||||||
<Compile Include="Download\ProcessDownloadDecisions.cs" />
|
<Compile Include="Download\ProcessDownloadDecisions.cs" />
|
||||||
<Compile Include="Download\ProcessedDecisions.cs" />
|
<Compile Include="Download\ProcessedDecisions.cs" />
|
||||||
<Compile Include="Download\RedownloadFailedDownloadService.cs" />
|
<Compile Include="Download\RedownloadFailedDownloadService.cs" />
|
||||||
<Compile Include="Download\TrackedDownload.cs" />
|
|
||||||
<Compile Include="Exceptions\BadRequestException.cs" />
|
<Compile Include="Exceptions\BadRequestException.cs" />
|
||||||
<Compile Include="Exceptions\DownstreamException.cs" />
|
<Compile Include="Exceptions\DownstreamException.cs" />
|
||||||
<Compile Include="Exceptions\NzbDroneClientException.cs" />
|
<Compile Include="Exceptions\NzbDroneClientException.cs" />
|
||||||
|
@ -765,9 +768,8 @@
|
||||||
<Compile Include="Qualities\QualityModel.cs" />
|
<Compile Include="Qualities\QualityModel.cs" />
|
||||||
<Compile Include="Qualities\QualityModelComparer.cs" />
|
<Compile Include="Qualities\QualityModelComparer.cs" />
|
||||||
<Compile Include="Queue\Queue.cs" />
|
<Compile Include="Queue\Queue.cs" />
|
||||||
<Compile Include="Queue\QueueScheduler.cs" />
|
|
||||||
<Compile Include="Queue\QueueService.cs" />
|
<Compile Include="Queue\QueueService.cs" />
|
||||||
<Compile Include="Queue\UpdateQueueEvent.cs" />
|
<Compile Include="Queue\QueueUpdatedEvent.cs" />
|
||||||
<Compile Include="Restrictions\Restriction.cs" />
|
<Compile Include="Restrictions\Restriction.cs" />
|
||||||
<Compile Include="Restrictions\RestrictionRepository.cs" />
|
<Compile Include="Restrictions\RestrictionRepository.cs" />
|
||||||
<Compile Include="Restrictions\RestrictionService.cs" />
|
<Compile Include="Restrictions\RestrictionService.cs" />
|
||||||
|
@ -909,6 +911,7 @@
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup />
|
||||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<PostBuildEvent>
|
<PostBuildEvent>
|
||||||
|
|
|
@ -15,8 +15,7 @@ namespace NzbDrone.Core.Parser
|
||||||
{
|
{
|
||||||
LocalEpisode GetLocalEpisode(string filename, Series series, bool sceneSource);
|
LocalEpisode GetLocalEpisode(string filename, Series series, bool sceneSource);
|
||||||
Series GetSeries(string title);
|
Series GetSeries(string title);
|
||||||
RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, Int32 tvRageId, SearchCriteriaBase searchCriteria = null);
|
RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, Int32 tvRageId = 0, SearchCriteriaBase searchCriteria = null);
|
||||||
RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, Int32 seriesId, IEnumerable<Int32> episodeIds);
|
|
||||||
List<Episode> GetEpisodes(ParsedEpisodeInfo parsedEpisodeInfo, Series series, bool sceneSource, SearchCriteriaBase searchCriteria = null);
|
List<Episode> GetEpisodes(ParsedEpisodeInfo parsedEpisodeInfo, Series series, bool sceneSource, SearchCriteriaBase searchCriteria = null);
|
||||||
ParsedEpisodeInfo ParseSpecialEpisodeTitle(string title, int tvRageId, SearchCriteriaBase searchCriteria = null);
|
ParsedEpisodeInfo ParseSpecialEpisodeTitle(string title, int tvRageId, SearchCriteriaBase searchCriteria = null);
|
||||||
}
|
}
|
||||||
|
@ -119,18 +118,6 @@ namespace NzbDrone.Core.Parser
|
||||||
return remoteEpisode;
|
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)
|
public List<Episode> GetEpisodes(ParsedEpisodeInfo parsedEpisodeInfo, Series series, bool sceneSource, SearchCriteriaBase searchCriteria = null)
|
||||||
{
|
{
|
||||||
var result = new List<Episode>();
|
var result = new List<Episode>();
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using NzbDrone.Core.Datastore;
|
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.Qualities;
|
||||||
using NzbDrone.Core.Tv;
|
using NzbDrone.Core.Tv;
|
||||||
using NzbDrone.Core.Parser.Model;
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.Queue
|
namespace NzbDrone.Core.Queue
|
||||||
{
|
{
|
||||||
|
@ -21,7 +21,7 @@ namespace NzbDrone.Core.Queue
|
||||||
public String Status { get; set; }
|
public String Status { get; set; }
|
||||||
public String TrackedDownloadStatus { get; set; }
|
public String TrackedDownloadStatus { get; set; }
|
||||||
public List<TrackedDownloadStatusMessage> StatusMessages { get; set; }
|
public List<TrackedDownloadStatusMessage> StatusMessages { get; set; }
|
||||||
public RemoteEpisode RemoteEpisode { get; set; }
|
|
||||||
public String TrackingId { 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;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using NzbDrone.Core.Download;
|
using NzbDrone.Core.Download.TrackedDownloads;
|
||||||
|
using NzbDrone.Core.Messaging.Events;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Queue
|
namespace NzbDrone.Core.Queue
|
||||||
{
|
{
|
||||||
|
@ -11,64 +12,63 @@ namespace NzbDrone.Core.Queue
|
||||||
Queue Find(int id);
|
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()
|
public List<Queue> GetQueue()
|
||||||
{
|
{
|
||||||
var queueItems = _downloadTrackingService.GetQueuedDownloads()
|
return _queue;
|
||||||
.OrderBy(v => v.DownloadItem.RemainingTime)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
return MapQueue(queueItems);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Queue Find(int id)
|
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.DownloadId.GetHashCode() << 16),
|
||||||
{
|
Series = trackedDownload.RemoteEpisode.Series,
|
||||||
Id = episode.Id ^ (trackedDownload.DownloadItem.DownloadClientId.GetHashCode() << 16),
|
Episode = episode,
|
||||||
Series = trackedDownload.RemoteEpisode.Series,
|
Quality = trackedDownload.RemoteEpisode.ParsedEpisodeInfo.Quality,
|
||||||
Episode = episode,
|
Title = trackedDownload.DownloadItem.Title,
|
||||||
Quality = trackedDownload.RemoteEpisode.ParsedEpisodeInfo.Quality,
|
Size = trackedDownload.DownloadItem.TotalSize,
|
||||||
Title = trackedDownload.DownloadItem.Title,
|
Sizeleft = trackedDownload.DownloadItem.RemainingSize,
|
||||||
Size = trackedDownload.DownloadItem.TotalSize,
|
Timeleft = trackedDownload.DownloadItem.RemainingTime,
|
||||||
Sizeleft = trackedDownload.DownloadItem.RemainingSize,
|
Status = trackedDownload.DownloadItem.Status.ToString(),
|
||||||
Timeleft = trackedDownload.DownloadItem.RemainingTime,
|
TrackedDownloadStatus = trackedDownload.Status.ToString(),
|
||||||
Status = trackedDownload.DownloadItem.Status.ToString(),
|
StatusMessages = trackedDownload.StatusMessages.ToList(),
|
||||||
RemoteEpisode = trackedDownload.RemoteEpisode,
|
RemoteEpisode = trackedDownload.RemoteEpisode,
|
||||||
TrackedDownloadStatus = trackedDownload.Status.ToString(),
|
TrackingId = trackedDownload.TrackingId
|
||||||
StatusMessages = trackedDownload.StatusMessages,
|
};
|
||||||
TrackingId = trackedDownload.TrackingId
|
|
||||||
};
|
|
||||||
|
|
||||||
if (queue.Timeleft.HasValue)
|
if (queue.Timeleft.HasValue)
|
||||||
{
|
{
|
||||||
queue.EstimatedCompletionTime = DateTime.UtcNow.Add(queue.Timeleft.Value);
|
queue.EstimatedCompletionTime = DateTime.UtcNow.Add(queue.Timeleft.Value);
|
||||||
}
|
|
||||||
|
|
||||||
queued.Add(queue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
yield return queue;
|
||||||
}
|
}
|
||||||
|
|
||||||
return queued;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
namespace NzbDrone.Core.Queue
|
namespace NzbDrone.Core.Queue
|
||||||
{
|
{
|
||||||
public class UpdateQueueEvent : IEvent
|
public class QueueUpdatedEvent : IEvent
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -25,9 +25,9 @@
|
||||||
<dd>{{downloadClient}}</dd>
|
<dd>{{downloadClient}}</dd>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
{{#if downloadClientId}}
|
{{#if downloadId}}
|
||||||
<dt>Download Client ID:</dt>
|
<dt>Grab ID:</dt>
|
||||||
<dd>{{downloadClientId}}</dd>
|
<dd>{{downloadId}}</dd>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
{{#if age}}
|
{{#if age}}
|
||||||
|
|
|
@ -12,20 +12,20 @@ define(
|
||||||
ui: {
|
ui: {
|
||||||
completedDownloadHandlingCheckbox : '.x-completed-download-handling',
|
completedDownloadHandlingCheckbox : '.x-completed-download-handling',
|
||||||
completedDownloadOptions : '.x-completed-download-options',
|
completedDownloadOptions : '.x-completed-download-options',
|
||||||
failedDownloadHandlingCheckbox : '.x-failed-download-handling',
|
failedAutoRedownladCheckbox : '.x-failed-auto-redownload',
|
||||||
failedDownloadOptions : '.x-failed-download-options'
|
failedDownloadOptions : '.x-failed-download-options'
|
||||||
},
|
},
|
||||||
|
|
||||||
events: {
|
events: {
|
||||||
'change .x-completed-download-handling' : '_setCompletedDownloadOptionsVisibility',
|
'change .x-completed-download-handling' : '_setCompletedDownloadOptionsVisibility',
|
||||||
'change .x-failed-download-handling' : '_setFailedDownloadOptionsVisibility'
|
'change .x-failed-auto-redownload' : '_setFailedDownloadOptionsVisibility'
|
||||||
},
|
},
|
||||||
|
|
||||||
onRender: function () {
|
onRender: function () {
|
||||||
if (!this.ui.completedDownloadHandlingCheckbox.prop('checked')) {
|
if (!this.ui.completedDownloadHandlingCheckbox.prop('checked')) {
|
||||||
this.ui.completedDownloadOptions.hide();
|
this.ui.completedDownloadOptions.hide();
|
||||||
}
|
}
|
||||||
if (!this.ui.failedDownloadHandlingCheckbox.prop('checked')) {
|
if (!this.ui.failedAutoRedownladCheckbox.prop('checked')) {
|
||||||
this.ui.failedDownloadOptions.hide();
|
this.ui.failedDownloadOptions.hide();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -42,7 +42,7 @@ define(
|
||||||
},
|
},
|
||||||
|
|
||||||
_setFailedDownloadOptionsVisibility: function () {
|
_setFailedDownloadOptionsVisibility: function () {
|
||||||
var checked = this.ui.failedDownloadHandlingCheckbox.prop('checked');
|
var checked = this.ui.failedAutoRedownladCheckbox.prop('checked');
|
||||||
if (checked) {
|
if (checked) {
|
||||||
this.ui.failedDownloadOptions.slideDown();
|
this.ui.failedDownloadOptions.slideDown();
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,15 +14,13 @@
|
||||||
|
|
||||||
<div class="btn btn-primary slide-button"/>
|
<div class="btn btn-primary slide-button"/>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<span class="help-inline-checkbox">
|
<span class="help-inline-checkbox">
|
||||||
<i class="icon-nd-form-info" title="Import completed downloads in download client history"/>
|
<i class="icon-nd-form-info" title="Automatically import completed downloads from download client"/>
|
||||||
<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"/>
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="x-completed-download-options advanced-setting">
|
<div class="x-completed-download-options advanced-setting">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="col-sm-3 control-label">Remove</label>
|
<label class="col-sm-3 control-label">Remove</label>
|
||||||
|
@ -44,20 +42,19 @@
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<fieldset class="advanced-setting">
|
<fieldset>
|
||||||
<legend>Failed Download Handling</legend>
|
<legend>Failed Download Handling</legend>
|
||||||
|
|
||||||
<div class="form-group">
|
<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="col-sm-8">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<label class="checkbox toggle well">
|
<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>
|
<p>
|
||||||
<span>Yes</span>
|
<span>Yes</span>
|
||||||
<span>No</span>
|
<span>No</span>
|
||||||
|
@ -66,39 +63,15 @@
|
||||||
<div class="btn btn-primary slide-button"/>
|
<div class="btn btn-primary slide-button"/>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<span class="help-inline-checkbox">
|
<span class="help-inline-checkbox">
|
||||||
<i class="icon-nd-form-info" title="Process failed downloads and blacklist the release"/>
|
<i class="icon-nd-form-info" title="Automatically search for and attempt to download a different release"/>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="x-failed-download-options advanced-setting">
|
||||||
<div class="x-failed-download-options">
|
<div class="form-group ">
|
||||||
<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">
|
|
||||||
<label class="col-sm-3 control-label">Remove</label>
|
<label class="col-sm-3 control-label">Remove</label>
|
||||||
|
|
||||||
<div class="col-sm-8">
|
<div class="col-sm-8">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<label class="checkbox toggle well">
|
<label class="checkbox toggle well">
|
||||||
|
@ -110,48 +83,11 @@
|
||||||
|
|
||||||
<div class="btn btn-primary slide-button"/>
|
<div class="btn btn-primary slide-button"/>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<span class="help-inline-checkbox">
|
<span class="help-inline-checkbox">
|
||||||
<i class="icon-nd-form-info" title="Remove failed downloads from download client history"/>
|
<i class="icon-nd-form-info" title="Remove failed downloads from download client history"/>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
|
@ -1,11 +1,12 @@
|
||||||
<fieldset>
|
<fieldset class="advanced-setting">
|
||||||
<legend>Drone Factory Options</legend>
|
<legend>Drone Factory Options</legend>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="col-sm-3 control-label">Drone Factory</label>
|
<label class="col-sm-3 control-label">Drone Factory</label>
|
||||||
|
|
||||||
<div class="col-sm-1 col-sm-push-8 help-inline">
|
<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="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>
|
||||||
|
|
||||||
<div class="col-sm-8 col-sm-pull-1">
|
<div class="col-sm-8 col-sm-pull-1">
|
||||||
|
@ -13,7 +14,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group advanced-setting">
|
<div class="form-group">
|
||||||
<label class="col-sm-3 control-label">Drone Factory Interval</label>
|
<label class="col-sm-3 control-label">Drone Factory Interval</label>
|
||||||
|
|
||||||
<div class="col-sm-1 col-sm-push-2 help-inline">
|
<div class="col-sm-1 col-sm-push-2 help-inline">
|
||||||
|
|
Loading…
Reference in New Issue