mirror of https://github.com/Sonarr/Sonarr
commit
452649ed1d
|
@ -101,6 +101,7 @@
|
||||||
<Compile Include="Profiles\Delay\DelayProfileModule.cs" />
|
<Compile Include="Profiles\Delay\DelayProfileModule.cs" />
|
||||||
<Compile Include="Profiles\Delay\DelayProfileResource.cs" />
|
<Compile Include="Profiles\Delay\DelayProfileResource.cs" />
|
||||||
<Compile Include="Profiles\Delay\DelayProfileValidator.cs" />
|
<Compile Include="Profiles\Delay\DelayProfileValidator.cs" />
|
||||||
|
<Compile Include="Queue\QueueActionModule.cs" />
|
||||||
<Compile Include="RemotePathMappings\RemotePathMappingModule.cs" />
|
<Compile Include="RemotePathMappings\RemotePathMappingModule.cs" />
|
||||||
<Compile Include="RemotePathMappings\RemotePathMappingResource.cs" />
|
<Compile Include="RemotePathMappings\RemotePathMappingResource.cs" />
|
||||||
<Compile Include="Config\UiConfigModule.cs" />
|
<Compile Include="Config\UiConfigModule.cs" />
|
||||||
|
@ -202,6 +203,7 @@
|
||||||
<Compile Include="ResourceChangeMessage.cs" />
|
<Compile Include="ResourceChangeMessage.cs" />
|
||||||
<Compile Include="Restrictions\RestrictionModule.cs" />
|
<Compile Include="Restrictions\RestrictionModule.cs" />
|
||||||
<Compile Include="Restrictions\RestrictionResource.cs" />
|
<Compile Include="Restrictions\RestrictionResource.cs" />
|
||||||
|
<Compile Include="REST\NotFoundException.cs" />
|
||||||
<Compile Include="REST\BadRequestException.cs" />
|
<Compile Include="REST\BadRequestException.cs" />
|
||||||
<Compile Include="REST\MethodNotAllowedException.cs" />
|
<Compile Include="REST\MethodNotAllowedException.cs" />
|
||||||
<Compile Include="REST\ResourceValidator.cs" />
|
<Compile Include="REST\ResourceValidator.cs" />
|
||||||
|
|
|
@ -0,0 +1,113 @@
|
||||||
|
using System.Linq;
|
||||||
|
using Nancy;
|
||||||
|
using Nancy.Responses;
|
||||||
|
using NzbDrone.Api.Extensions;
|
||||||
|
using NzbDrone.Api.REST;
|
||||||
|
using NzbDrone.Core.Download;
|
||||||
|
using NzbDrone.Core.Download.Pending;
|
||||||
|
using NzbDrone.Core.Queue;
|
||||||
|
|
||||||
|
namespace NzbDrone.Api.Queue
|
||||||
|
{
|
||||||
|
public class QueueActionModule : NzbDroneRestModule<QueueResource>
|
||||||
|
{
|
||||||
|
private readonly IQueueService _queueService;
|
||||||
|
private readonly IDownloadTrackingService _downloadTrackingService;
|
||||||
|
private readonly ICompletedDownloadService _completedDownloadService;
|
||||||
|
private readonly IProvideDownloadClient _downloadClientProvider;
|
||||||
|
private readonly IPendingReleaseService _pendingReleaseService;
|
||||||
|
private readonly IDownloadService _downloadService;
|
||||||
|
|
||||||
|
public QueueActionModule(IQueueService queueService,
|
||||||
|
IDownloadTrackingService downloadTrackingService,
|
||||||
|
ICompletedDownloadService completedDownloadService,
|
||||||
|
IProvideDownloadClient downloadClientProvider,
|
||||||
|
IPendingReleaseService pendingReleaseService,
|
||||||
|
IDownloadService downloadService)
|
||||||
|
{
|
||||||
|
_queueService = queueService;
|
||||||
|
_downloadTrackingService = downloadTrackingService;
|
||||||
|
_completedDownloadService = completedDownloadService;
|
||||||
|
_downloadClientProvider = downloadClientProvider;
|
||||||
|
_pendingReleaseService = pendingReleaseService;
|
||||||
|
_downloadService = downloadService;
|
||||||
|
|
||||||
|
Delete[@"/(?<id>[\d]{1,10})"] = x => Remove((int)x.Id);
|
||||||
|
Post["/import"] = x => Import();
|
||||||
|
Post["/grab"] = x => Grab();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Response Remove(int id)
|
||||||
|
{
|
||||||
|
var pendingRelease = _pendingReleaseService.FindPendingQueueItem(id);
|
||||||
|
|
||||||
|
if (pendingRelease != null)
|
||||||
|
{
|
||||||
|
_pendingReleaseService.RemovePendingQueueItem(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
var trackedDownload = GetTrackedDownload(id);
|
||||||
|
|
||||||
|
if (trackedDownload == null)
|
||||||
|
{
|
||||||
|
throw new NotFoundException();
|
||||||
|
}
|
||||||
|
|
||||||
|
var downloadClient = _downloadClientProvider.Get(trackedDownload.DownloadClient);
|
||||||
|
|
||||||
|
if (downloadClient == null)
|
||||||
|
{
|
||||||
|
throw new BadRequestException();
|
||||||
|
}
|
||||||
|
|
||||||
|
downloadClient.RemoveItem(trackedDownload.DownloadItem.DownloadClientId);
|
||||||
|
|
||||||
|
return new object().AsResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
private JsonResponse<QueueResource> Import()
|
||||||
|
{
|
||||||
|
var resource = Request.Body.FromJson<QueueResource>();
|
||||||
|
var trackedDownload = GetTrackedDownload(resource.Id);
|
||||||
|
|
||||||
|
_completedDownloadService.Import(trackedDownload);
|
||||||
|
|
||||||
|
return resource.AsResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
private JsonResponse<QueueResource> Grab()
|
||||||
|
{
|
||||||
|
var resource = Request.Body.FromJson<QueueResource>();
|
||||||
|
|
||||||
|
var pendingRelease = _pendingReleaseService.FindPendingQueueItem(resource.Id);
|
||||||
|
|
||||||
|
if (pendingRelease == null)
|
||||||
|
{
|
||||||
|
throw new NotFoundException();
|
||||||
|
}
|
||||||
|
|
||||||
|
_downloadService.DownloadReport(pendingRelease.RemoteEpisode);
|
||||||
|
|
||||||
|
return resource.AsResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
private TrackedDownload GetTrackedDownload(int queueId)
|
||||||
|
{
|
||||||
|
var queueItem = _queueService.Find(queueId);
|
||||||
|
|
||||||
|
if (queueItem == null)
|
||||||
|
{
|
||||||
|
throw new NotFoundException();
|
||||||
|
}
|
||||||
|
|
||||||
|
var trackedDownload = _downloadTrackingService.Find(queueItem.TrackingId);
|
||||||
|
|
||||||
|
if (trackedDownload == null)
|
||||||
|
{
|
||||||
|
throw new NotFoundException();
|
||||||
|
}
|
||||||
|
|
||||||
|
return trackedDownload;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
using Nancy;
|
||||||
|
using NzbDrone.Api.ErrorManagement;
|
||||||
|
|
||||||
|
namespace NzbDrone.Api.REST
|
||||||
|
{
|
||||||
|
public class NotFoundException : ApiException
|
||||||
|
{
|
||||||
|
public NotFoundException(object content = null)
|
||||||
|
: base(HttpStatusCode.NotFound, content)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -36,7 +36,7 @@ namespace NzbDrone.Core.Test.Download
|
||||||
.Build()
|
.Build()
|
||||||
.ToList();
|
.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}}
|
||||||
|
@ -115,7 +115,7 @@ namespace NzbDrone.Core.Test.Download
|
||||||
private void GivenCompletedImport()
|
private void GivenCompletedImport()
|
||||||
{
|
{
|
||||||
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<Series>(), 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" }))
|
||||||
|
@ -125,7 +125,7 @@ namespace NzbDrone.Core.Test.Download
|
||||||
private void GivenFailedImport()
|
private void GivenFailedImport()
|
||||||
{
|
{
|
||||||
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<Series>(), It.IsAny<DownloadClientItem>()))
|
||||||
.Returns(new List<ImportResult>()
|
.Returns(new List<ImportResult>()
|
||||||
{
|
{
|
||||||
new ImportResult(new ImportDecision(new LocalEpisode() { Path = @"C:\TestPath\Droned.S01E01.mkv" }, "Test Failure"))
|
new ImportResult(new ImportDecision(new LocalEpisode() { Path = @"C:\TestPath\Droned.S01E01.mkv" }, "Test Failure"))
|
||||||
|
@ -135,13 +135,13 @@ namespace NzbDrone.Core.Test.Download
|
||||||
private void VerifyNoImports()
|
private void VerifyNoImports()
|
||||||
{
|
{
|
||||||
Mocker.GetMock<IDownloadedEpisodesImportService>()
|
Mocker.GetMock<IDownloadedEpisodesImportService>()
|
||||||
.Verify(v => v.ProcessFolder(It.IsAny<DirectoryInfo>(), It.IsAny<DownloadClientItem>()), Times.Never());
|
.Verify(v => v.ProcessFolder(It.IsAny<DirectoryInfo>(), It.IsAny<Series>(), It.IsAny<DownloadClientItem>()), Times.Never());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void VerifyImports()
|
private void VerifyImports()
|
||||||
{
|
{
|
||||||
Mocker.GetMock<IDownloadedEpisodesImportService>()
|
Mocker.GetMock<IDownloadedEpisodesImportService>()
|
||||||
.Verify(v => v.ProcessFolder(It.IsAny<DirectoryInfo>(), It.IsAny<DownloadClientItem>()), Times.Once());
|
.Verify(v => v.ProcessFolder(It.IsAny<DirectoryInfo>(), It.IsAny<Series>(), It.IsAny<DownloadClientItem>()), Times.Once());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -473,7 +473,7 @@ namespace NzbDrone.Core.Test.Download
|
||||||
GivenNoImportedHistory();
|
GivenNoImportedHistory();
|
||||||
|
|
||||||
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<Series>(), It.IsAny<DownloadClientItem>()))
|
||||||
.Returns(new List<ImportResult>
|
.Returns(new List<ImportResult>
|
||||||
{
|
{
|
||||||
new ImportResult(
|
new ImportResult(
|
||||||
|
@ -505,7 +505,7 @@ namespace NzbDrone.Core.Test.Download
|
||||||
GivenNoImportedHistory();
|
GivenNoImportedHistory();
|
||||||
|
|
||||||
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<Series>(), It.IsAny<DownloadClientItem>()))
|
||||||
.Returns(new List<ImportResult>
|
.Returns(new List<ImportResult>
|
||||||
{
|
{
|
||||||
new ImportResult(
|
new ImportResult(
|
||||||
|
@ -537,7 +537,7 @@ namespace NzbDrone.Core.Test.Download
|
||||||
GivenNoImportedHistory();
|
GivenNoImportedHistory();
|
||||||
|
|
||||||
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<Series>(), 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"})),
|
||||||
|
|
|
@ -5,7 +5,9 @@ using Marr.Data;
|
||||||
using Moq;
|
using Moq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using NzbDrone.Core.DecisionEngine;
|
using NzbDrone.Core.DecisionEngine;
|
||||||
|
using NzbDrone.Core.Download;
|
||||||
using NzbDrone.Core.Download.Pending;
|
using NzbDrone.Core.Download.Pending;
|
||||||
|
using NzbDrone.Core.Indexers;
|
||||||
using NzbDrone.Core.Parser;
|
using NzbDrone.Core.Parser;
|
||||||
using NzbDrone.Core.Parser.Model;
|
using NzbDrone.Core.Parser.Model;
|
||||||
using NzbDrone.Core.Profiles;
|
using NzbDrone.Core.Profiles;
|
||||||
|
@ -102,7 +104,7 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
|
||||||
{
|
{
|
||||||
GivenHeldRelease(_parsedEpisodeInfo.Quality);
|
GivenHeldRelease(_parsedEpisodeInfo.Quality);
|
||||||
|
|
||||||
Subject.RemoveGrabbed(new List<DownloadDecision> { _temporarilyRejected });
|
Subject.Handle(new EpisodeGrabbedEvent(_remoteEpisode));
|
||||||
|
|
||||||
VerifyDelete();
|
VerifyDelete();
|
||||||
}
|
}
|
||||||
|
@ -112,7 +114,7 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
|
||||||
{
|
{
|
||||||
GivenHeldRelease(new QualityModel(Quality.SDTV));
|
GivenHeldRelease(new QualityModel(Quality.SDTV));
|
||||||
|
|
||||||
Subject.RemoveGrabbed(new List<DownloadDecision> { _temporarilyRejected });
|
Subject.Handle(new EpisodeGrabbedEvent(_remoteEpisode));
|
||||||
|
|
||||||
VerifyDelete();
|
VerifyDelete();
|
||||||
}
|
}
|
||||||
|
@ -122,7 +124,7 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
|
||||||
{
|
{
|
||||||
GivenHeldRelease(new QualityModel(Quality.Bluray720p));
|
GivenHeldRelease(new QualityModel(Quality.Bluray720p));
|
||||||
|
|
||||||
Subject.RemoveGrabbed(new List<DownloadDecision> { _temporarilyRejected });
|
Subject.Handle(new EpisodeGrabbedEvent(_remoteEpisode));
|
||||||
|
|
||||||
VerifyNoDelete();
|
VerifyNoDelete();
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,9 @@ using Marr.Data;
|
||||||
using Moq;
|
using Moq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using NzbDrone.Core.DecisionEngine;
|
using NzbDrone.Core.DecisionEngine;
|
||||||
|
using NzbDrone.Core.Download;
|
||||||
using NzbDrone.Core.Download.Pending;
|
using NzbDrone.Core.Download.Pending;
|
||||||
|
using NzbDrone.Core.Indexers;
|
||||||
using NzbDrone.Core.Parser;
|
using NzbDrone.Core.Parser;
|
||||||
using NzbDrone.Core.Parser.Model;
|
using NzbDrone.Core.Parser.Model;
|
||||||
using NzbDrone.Core.Profiles;
|
using NzbDrone.Core.Profiles;
|
||||||
|
@ -104,7 +106,9 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
|
||||||
{
|
{
|
||||||
GivenHeldRelease(_release.Title, _release.Indexer, _release.PublishDate);
|
GivenHeldRelease(_release.Title, _release.Indexer, _release.PublishDate);
|
||||||
|
|
||||||
Subject.RemoveRejected(new List<DownloadDecision> { _temporarilyRejected });
|
Subject.Handle(new RssSyncCompleteEvent(new ProcessedDecisions(new List<DownloadDecision>(),
|
||||||
|
new List<DownloadDecision>(),
|
||||||
|
new List<DownloadDecision> { _temporarilyRejected })));
|
||||||
|
|
||||||
VerifyDelete();
|
VerifyDelete();
|
||||||
}
|
}
|
||||||
|
@ -114,7 +118,9 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
|
||||||
{
|
{
|
||||||
GivenHeldRelease(_release.Title + "-RP", _release.Indexer, _release.PublishDate);
|
GivenHeldRelease(_release.Title + "-RP", _release.Indexer, _release.PublishDate);
|
||||||
|
|
||||||
Subject.RemoveRejected(new List<DownloadDecision> { _temporarilyRejected });
|
Subject.Handle(new RssSyncCompleteEvent(new ProcessedDecisions(new List<DownloadDecision>(),
|
||||||
|
new List<DownloadDecision>(),
|
||||||
|
new List<DownloadDecision> { _temporarilyRejected })));
|
||||||
|
|
||||||
VerifyNoDelete();
|
VerifyNoDelete();
|
||||||
}
|
}
|
||||||
|
@ -124,7 +130,9 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
|
||||||
{
|
{
|
||||||
GivenHeldRelease(_release.Title, "AnotherIndexer", _release.PublishDate);
|
GivenHeldRelease(_release.Title, "AnotherIndexer", _release.PublishDate);
|
||||||
|
|
||||||
Subject.RemoveRejected(new List<DownloadDecision> { _temporarilyRejected });
|
Subject.Handle(new RssSyncCompleteEvent(new ProcessedDecisions(new List<DownloadDecision>(),
|
||||||
|
new List<DownloadDecision>(),
|
||||||
|
new List<DownloadDecision> { _temporarilyRejected })));
|
||||||
|
|
||||||
VerifyNoDelete();
|
VerifyNoDelete();
|
||||||
}
|
}
|
||||||
|
@ -134,7 +142,9 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
|
||||||
{
|
{
|
||||||
GivenHeldRelease(_release.Title, _release.Indexer, _release.PublishDate.AddHours(1));
|
GivenHeldRelease(_release.Title, _release.Indexer, _release.PublishDate.AddHours(1));
|
||||||
|
|
||||||
Subject.RemoveRejected(new List<DownloadDecision> { _temporarilyRejected });
|
Subject.Handle(new RssSyncCompleteEvent(new ProcessedDecisions(new List<DownloadDecision>(),
|
||||||
|
new List<DownloadDecision>(),
|
||||||
|
new List<DownloadDecision> { _temporarilyRejected })));
|
||||||
|
|
||||||
VerifyNoDelete();
|
VerifyNoDelete();
|
||||||
}
|
}
|
||||||
|
|
|
@ -105,7 +105,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests
|
||||||
|
|
||||||
Mocker.GetMock<IProcessDownloadDecisions>()
|
Mocker.GetMock<IProcessDownloadDecisions>()
|
||||||
.Setup(s => s.ProcessDecisions(It.IsAny<List<DownloadDecision>>()))
|
.Setup(s => s.ProcessDecisions(It.IsAny<List<DownloadDecision>>()))
|
||||||
.Returns(new ProcessedDecisions(new List<DownloadDecision>(), new List<DownloadDecision>()));
|
.Returns(new ProcessedDecisions(new List<DownloadDecision>(), new List<DownloadDecision>(), new List<DownloadDecision>()));
|
||||||
|
|
||||||
Subject.Handle(new EpisodeInfoRefreshedEvent(_series, _added, _updated));
|
Subject.Handle(new EpisodeInfoRefreshedEvent(_series, _added, _updated));
|
||||||
|
|
||||||
|
|
|
@ -37,7 +37,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests
|
||||||
|
|
||||||
Mocker.GetMock<IProcessDownloadDecisions>()
|
Mocker.GetMock<IProcessDownloadDecisions>()
|
||||||
.Setup(s => s.ProcessDecisions(It.IsAny<List<DownloadDecision>>()))
|
.Setup(s => s.ProcessDecisions(It.IsAny<List<DownloadDecision>>()))
|
||||||
.Returns(new ProcessedDecisions(new List<DownloadDecision>(), new List<DownloadDecision>()));
|
.Returns(new ProcessedDecisions(new List<DownloadDecision>(), new List<DownloadDecision>(), new List<DownloadDecision>()));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
|
|
@ -87,6 +87,8 @@ namespace NzbDrone.Core.Test.MediaFiles
|
||||||
[Test]
|
[Test]
|
||||||
public void should_not_import_if_folder_is_a_series_path()
|
public void should_not_import_if_folder_is_a_series_path()
|
||||||
{
|
{
|
||||||
|
GivenValidSeries();
|
||||||
|
|
||||||
Mocker.GetMock<ISeriesService>()
|
Mocker.GetMock<ISeriesService>()
|
||||||
.Setup(s => s.SeriesPathExists(It.IsAny<String>()))
|
.Setup(s => s.SeriesPathExists(It.IsAny<String>()))
|
||||||
.Returns(true);
|
.Returns(true);
|
||||||
|
@ -97,8 +99,8 @@ namespace NzbDrone.Core.Test.MediaFiles
|
||||||
|
|
||||||
Subject.ProcessRootFolder(new DirectoryInfo(_droneFactory));
|
Subject.ProcessRootFolder(new DirectoryInfo(_droneFactory));
|
||||||
|
|
||||||
Mocker.GetMock<IParsingService>()
|
Mocker.GetMock<IDiskScanService>()
|
||||||
.Verify(v => v.GetSeries(It.IsAny<String>()), Times.Never());
|
.Verify(v => v.GetVideoFiles(It.IsAny<String>(), true), Times.Never());
|
||||||
|
|
||||||
ExceptionVerification.ExpectedWarns(1);
|
ExceptionVerification.ExpectedWarns(1);
|
||||||
}
|
}
|
||||||
|
|
|
@ -118,13 +118,14 @@ namespace NzbDrone.Core.Download
|
||||||
|
|
||||||
if (_diskProvider.FolderExists(outputPath))
|
if (_diskProvider.FolderExists(outputPath))
|
||||||
{
|
{
|
||||||
importResults = _downloadedEpisodesImportService.ProcessFolder(new DirectoryInfo(outputPath), trackedDownload.DownloadItem);
|
importResults = _downloadedEpisodesImportService.ProcessFolder(new DirectoryInfo(outputPath), trackedDownload.RemoteEpisode.Series, trackedDownload.DownloadItem);
|
||||||
|
|
||||||
ProcessImportResults(trackedDownload, outputPath, importResults);
|
ProcessImportResults(trackedDownload, outputPath, importResults);
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (_diskProvider.FileExists(outputPath))
|
else if (_diskProvider.FileExists(outputPath))
|
||||||
{
|
{
|
||||||
importResults = _downloadedEpisodesImportService.ProcessFile(new FileInfo(outputPath), trackedDownload.DownloadItem);
|
importResults = _downloadedEpisodesImportService.ProcessFile(new FileInfo(outputPath), trackedDownload.RemoteEpisode.Series, trackedDownload.DownloadItem);
|
||||||
|
|
||||||
ProcessImportResults(trackedDownload, outputPath, importResults);
|
ProcessImportResults(trackedDownload, outputPath, importResults);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ namespace NzbDrone.Core.Download
|
||||||
{
|
{
|
||||||
IDownloadClient GetDownloadClient(DownloadProtocol downloadProtocol);
|
IDownloadClient GetDownloadClient(DownloadProtocol downloadProtocol);
|
||||||
IEnumerable<IDownloadClient> GetDownloadClients();
|
IEnumerable<IDownloadClient> GetDownloadClients();
|
||||||
|
IDownloadClient Get(int id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class DownloadClientProvider : IProvideDownloadClient
|
public class DownloadClientProvider : IProvideDownloadClient
|
||||||
|
@ -28,5 +29,10 @@ namespace NzbDrone.Core.Download
|
||||||
{
|
{
|
||||||
return _downloadClientFactory.GetAvailableProviders();
|
return _downloadClientFactory.GetAvailableProviders();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IDownloadClient Get(int id)
|
||||||
|
{
|
||||||
|
return _downloadClientFactory.GetAvailableProviders().Single(d => d.Definition.Id == id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -19,7 +19,7 @@ namespace NzbDrone.Core.Download
|
||||||
{
|
{
|
||||||
TrackedDownload[] GetCompletedDownloads();
|
TrackedDownload[] GetCompletedDownloads();
|
||||||
TrackedDownload[] GetQueuedDownloads();
|
TrackedDownload[] GetQueuedDownloads();
|
||||||
|
TrackedDownload Find(string trackingId);
|
||||||
void MarkAsFailed(Int32 historyId);
|
void MarkAsFailed(Int32 historyId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,6 +88,11 @@ namespace NzbDrone.Core.Download
|
||||||
}, TimeSpan.FromSeconds(5.0));
|
}, TimeSpan.FromSeconds(5.0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public TrackedDownload Find(string trackingId)
|
||||||
|
{
|
||||||
|
return GetQueuedDownloads().SingleOrDefault(t => t.TrackingId == trackingId);
|
||||||
|
}
|
||||||
|
|
||||||
public void MarkAsFailed(Int32 historyId)
|
public void MarkAsFailed(Int32 historyId)
|
||||||
{
|
{
|
||||||
var item = _historyService.Get(historyId);
|
var item = _historyService.Get(historyId);
|
||||||
|
|
|
@ -4,6 +4,7 @@ using System.Linq;
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Common.Extensions;
|
using NzbDrone.Common.Extensions;
|
||||||
using NzbDrone.Core.DecisionEngine;
|
using NzbDrone.Core.DecisionEngine;
|
||||||
|
using NzbDrone.Core.Indexers;
|
||||||
using NzbDrone.Core.Messaging.Events;
|
using NzbDrone.Core.Messaging.Events;
|
||||||
using NzbDrone.Core.Parser;
|
using NzbDrone.Core.Parser;
|
||||||
using NzbDrone.Core.Parser.Model;
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
@ -17,15 +18,19 @@ namespace NzbDrone.Core.Download.Pending
|
||||||
public interface IPendingReleaseService
|
public interface IPendingReleaseService
|
||||||
{
|
{
|
||||||
void Add(DownloadDecision decision);
|
void Add(DownloadDecision decision);
|
||||||
void RemoveGrabbed(List<DownloadDecision> grabbed);
|
|
||||||
void RemoveRejected(List<DownloadDecision> rejected);
|
|
||||||
List<ReleaseInfo> GetPending();
|
List<ReleaseInfo> GetPending();
|
||||||
List<RemoteEpisode> GetPendingRemoteEpisodes(int seriesId);
|
List<RemoteEpisode> GetPendingRemoteEpisodes(int seriesId);
|
||||||
List<Queue.Queue> GetPendingQueue();
|
List<Queue.Queue> GetPendingQueue();
|
||||||
|
Queue.Queue FindPendingQueueItem(int queueId);
|
||||||
|
void RemovePendingQueueItem(int queueId);
|
||||||
RemoteEpisode OldestPendingRelease(int seriesId, IEnumerable<int> episodeIds);
|
RemoteEpisode OldestPendingRelease(int seriesId, IEnumerable<int> episodeIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class PendingReleaseService : IPendingReleaseService, IHandle<SeriesDeletedEvent>
|
public class PendingReleaseService : IPendingReleaseService,
|
||||||
|
IHandle<SeriesDeletedEvent>,
|
||||||
|
IHandle<EpisodeGrabbedEvent>,
|
||||||
|
IHandle<RssSyncCompleteEvent>
|
||||||
{
|
{
|
||||||
private readonly IPendingReleaseRepository _repository;
|
private readonly IPendingReleaseRepository _repository;
|
||||||
private readonly ISeriesService _seriesService;
|
private readonly ISeriesService _seriesService;
|
||||||
|
@ -49,6 +54,7 @@ namespace NzbDrone.Core.Download.Pending
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void Add(DownloadDecision decision)
|
public void Add(DownloadDecision decision)
|
||||||
{
|
{
|
||||||
var alreadyPending = GetPendingReleases();
|
var alreadyPending = GetPendingReleases();
|
||||||
|
@ -69,61 +75,6 @@ namespace NzbDrone.Core.Download.Pending
|
||||||
Insert(decision);
|
Insert(decision);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RemoveGrabbed(List<DownloadDecision> grabbed)
|
|
||||||
{
|
|
||||||
_logger.Debug("Removing grabbed releases from pending");
|
|
||||||
var alreadyPending = GetPendingReleases();
|
|
||||||
|
|
||||||
foreach (var decision in grabbed)
|
|
||||||
{
|
|
||||||
var decisionLocal = decision;
|
|
||||||
var episodeIds = decisionLocal.RemoteEpisode.Episodes.Select(e => e.Id);
|
|
||||||
|
|
||||||
var existingReports = alreadyPending.Where(r => r.RemoteEpisode.Episodes.Select(e => e.Id)
|
|
||||||
.Intersect(episodeIds)
|
|
||||||
.Any())
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
if (existingReports.Empty())
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var profile = decisionLocal.RemoteEpisode.Series.Profile.Value;
|
|
||||||
|
|
||||||
foreach (var existingReport in existingReports)
|
|
||||||
{
|
|
||||||
var compare = new QualityModelComparer(profile).Compare(decision.RemoteEpisode.ParsedEpisodeInfo.Quality,
|
|
||||||
existingReport.RemoteEpisode.ParsedEpisodeInfo.Quality);
|
|
||||||
|
|
||||||
//Only remove lower/equal quality pending releases
|
|
||||||
//It is safer to retry these releases on the next round than remove it and try to re-add it (if its still in the feed)
|
|
||||||
if (compare >= 0)
|
|
||||||
{
|
|
||||||
_logger.Debug("Removing previously pending release, as it was grabbed.");
|
|
||||||
Delete(existingReport);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RemoveRejected(List<DownloadDecision> rejected)
|
|
||||||
{
|
|
||||||
_logger.Debug("Removing failed releases from pending");
|
|
||||||
var pending = GetPendingReleases();
|
|
||||||
|
|
||||||
foreach (var rejectedRelease in rejected)
|
|
||||||
{
|
|
||||||
var matching = pending.SingleOrDefault(MatchingReleasePredicate(rejectedRelease));
|
|
||||||
|
|
||||||
if (matching != null)
|
|
||||||
{
|
|
||||||
_logger.Debug("Removing previously pending release, as it has now been rejected.");
|
|
||||||
Delete(matching);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<ReleaseInfo> GetPending()
|
public List<ReleaseInfo> GetPending()
|
||||||
{
|
{
|
||||||
return _repository.All().Select(p => p.Release).ToList();
|
return _repository.All().Select(p => p.Release).ToList();
|
||||||
|
@ -165,6 +116,18 @@ namespace NzbDrone.Core.Download.Pending
|
||||||
return queued;
|
return queued;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Queue.Queue FindPendingQueueItem(int queueId)
|
||||||
|
{
|
||||||
|
return GetPendingQueue().SingleOrDefault(p => p.Id == queueId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemovePendingQueueItem(int queueId)
|
||||||
|
{
|
||||||
|
var id = FindPendingReleaseId(queueId);
|
||||||
|
|
||||||
|
_repository.Delete(id);
|
||||||
|
}
|
||||||
|
|
||||||
public RemoteEpisode OldestPendingRelease(int seriesId, IEnumerable<int> episodeIds)
|
public RemoteEpisode OldestPendingRelease(int seriesId, IEnumerable<int> episodeIds)
|
||||||
{
|
{
|
||||||
return GetPendingRemoteEpisodes(seriesId)
|
return GetPendingRemoteEpisodes(seriesId)
|
||||||
|
@ -243,9 +206,73 @@ namespace NzbDrone.Core.Download.Pending
|
||||||
return delayProfile.GetProtocolDelay(remoteEpisode.Release.DownloadProtocol);
|
return delayProfile.GetProtocolDelay(remoteEpisode.Release.DownloadProtocol);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void RemoveGrabbed(RemoteEpisode remoteEpisode)
|
||||||
|
{
|
||||||
|
var pendingReleases = GetPendingReleases();
|
||||||
|
var episodeIds = remoteEpisode.Episodes.Select(e => e.Id);
|
||||||
|
|
||||||
|
var existingReports = pendingReleases.Where(r => r.RemoteEpisode.Episodes.Select(e => e.Id)
|
||||||
|
.Intersect(episodeIds)
|
||||||
|
.Any())
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (existingReports.Empty())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var profile = remoteEpisode.Series.Profile.Value;
|
||||||
|
|
||||||
|
foreach (var existingReport in existingReports)
|
||||||
|
{
|
||||||
|
var compare = new QualityModelComparer(profile).Compare(remoteEpisode.ParsedEpisodeInfo.Quality,
|
||||||
|
existingReport.RemoteEpisode.ParsedEpisodeInfo.Quality);
|
||||||
|
|
||||||
|
//Only remove lower/equal quality pending releases
|
||||||
|
//It is safer to retry these releases on the next round than remove it and try to re-add it (if its still in the feed)
|
||||||
|
if (compare >= 0)
|
||||||
|
{
|
||||||
|
_logger.Debug("Removing previously pending release, as it was grabbed.");
|
||||||
|
Delete(existingReport);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RemoveRejected(List<DownloadDecision> rejected)
|
||||||
|
{
|
||||||
|
_logger.Debug("Removing failed releases from pending");
|
||||||
|
var pending = GetPendingReleases();
|
||||||
|
|
||||||
|
foreach (var rejectedRelease in rejected)
|
||||||
|
{
|
||||||
|
var matching = pending.SingleOrDefault(MatchingReleasePredicate(rejectedRelease));
|
||||||
|
|
||||||
|
if (matching != null)
|
||||||
|
{
|
||||||
|
_logger.Debug("Removing previously pending release, as it has now been rejected.");
|
||||||
|
Delete(matching);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int FindPendingReleaseId(int queueId)
|
||||||
|
{
|
||||||
|
return GetPendingReleases().First(p => p.RemoteEpisode.Episodes.Any(e => queueId == (e.Id ^ (p.Id << 16)))).Id;
|
||||||
|
}
|
||||||
|
|
||||||
public void Handle(SeriesDeletedEvent message)
|
public void Handle(SeriesDeletedEvent message)
|
||||||
{
|
{
|
||||||
_repository.DeleteBySeriesId(message.Series.Id);
|
_repository.DeleteBySeriesId(message.Series.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Handle(EpisodeGrabbedEvent message)
|
||||||
|
{
|
||||||
|
RemoveGrabbed(message.Episode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Handle(RssSyncCompleteEvent message)
|
||||||
|
{
|
||||||
|
RemoveRejected(message.ProcessedDecisions.Rejected);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,7 +82,7 @@ namespace NzbDrone.Core.Download
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ProcessedDecisions(grabbed, pending);
|
return new ProcessedDecisions(grabbed, pending, decisions.Where(d => d.Rejected).ToList());
|
||||||
}
|
}
|
||||||
|
|
||||||
internal List<DownloadDecision> GetQualifiedReports(IEnumerable<DownloadDecision> decisions)
|
internal List<DownloadDecision> GetQualifiedReports(IEnumerable<DownloadDecision> decisions)
|
||||||
|
|
|
@ -7,11 +7,13 @@ namespace NzbDrone.Core.Download
|
||||||
{
|
{
|
||||||
public List<DownloadDecision> Grabbed { get; set; }
|
public List<DownloadDecision> Grabbed { get; set; }
|
||||||
public List<DownloadDecision> Pending { get; set; }
|
public List<DownloadDecision> Pending { get; set; }
|
||||||
|
public List<DownloadDecision> Rejected { get; set; }
|
||||||
|
|
||||||
public ProcessedDecisions(List<DownloadDecision> grabbed, List<DownloadDecision> pending)
|
public ProcessedDecisions(List<DownloadDecision> grabbed, List<DownloadDecision> pending, List<DownloadDecision> rejected)
|
||||||
{
|
{
|
||||||
Grabbed = grabbed;
|
Grabbed = grabbed;
|
||||||
Pending = pending;
|
Pending = pending;
|
||||||
|
Rejected = rejected;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,10 @@ namespace NzbDrone.Core.Download
|
||||||
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)
|
||||||
{
|
{
|
||||||
Title = title;
|
Title = title;
|
||||||
|
|
|
@ -1,8 +1,15 @@
|
||||||
using NzbDrone.Common.Messaging;
|
using NzbDrone.Common.Messaging;
|
||||||
|
using NzbDrone.Core.Download;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Indexers
|
namespace NzbDrone.Core.Indexers
|
||||||
{
|
{
|
||||||
public class RssSyncCompleteEvent : IEvent
|
public class RssSyncCompleteEvent : IEvent
|
||||||
{
|
{
|
||||||
|
public ProcessedDecisions ProcessedDecisions { get; private set; }
|
||||||
|
|
||||||
|
public RssSyncCompleteEvent(ProcessedDecisions processedDecisions)
|
||||||
|
{
|
||||||
|
ProcessedDecisions = processedDecisions;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,15 +40,13 @@ namespace NzbDrone.Core.Indexers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private List<DownloadDecision> Sync()
|
private ProcessedDecisions Sync()
|
||||||
{
|
{
|
||||||
_logger.ProgressInfo("Starting RSS Sync");
|
_logger.ProgressInfo("Starting RSS Sync");
|
||||||
|
|
||||||
var reports = _rssFetcherAndParser.Fetch().Concat(_pendingReleaseService.GetPending()).ToList();
|
var reports = _rssFetcherAndParser.Fetch().Concat(_pendingReleaseService.GetPending()).ToList();
|
||||||
var decisions = _downloadDecisionMaker.GetRssDecision(reports);
|
var decisions = _downloadDecisionMaker.GetRssDecision(reports);
|
||||||
var processed = _processDownloadDecisions.ProcessDecisions(decisions);
|
var processed = _processDownloadDecisions.ProcessDecisions(decisions);
|
||||||
_pendingReleaseService.RemoveGrabbed(processed.Grabbed);
|
|
||||||
_pendingReleaseService.RemoveRejected(decisions.Where(d => d.Rejected).ToList());
|
|
||||||
|
|
||||||
var message = String.Format("RSS Sync Completed. Reports found: {0}, Reports grabbed: {1}", reports.Count, processed.Grabbed.Count);
|
var message = String.Format("RSS Sync Completed. Reports found: {0}, Reports grabbed: {1}", reports.Count, processed.Grabbed.Count);
|
||||||
|
|
||||||
|
@ -59,20 +57,21 @@ namespace NzbDrone.Core.Indexers
|
||||||
|
|
||||||
_logger.ProgressInfo(message);
|
_logger.ProgressInfo(message);
|
||||||
|
|
||||||
return processed.Grabbed.Concat(processed.Pending).ToList();
|
return processed;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Execute(RssSyncCommand message)
|
public void Execute(RssSyncCommand message)
|
||||||
{
|
{
|
||||||
var processed = Sync();
|
var processed = Sync();
|
||||||
|
var grabbedOrPending = processed.Grabbed.Concat(processed.Pending).ToList();
|
||||||
|
|
||||||
if (message.LastExecutionTime.HasValue && DateTime.UtcNow.Subtract(message.LastExecutionTime.Value).TotalHours > 3)
|
if (message.LastExecutionTime.HasValue && DateTime.UtcNow.Subtract(message.LastExecutionTime.Value).TotalHours > 3)
|
||||||
{
|
{
|
||||||
_logger.Info("RSS Sync hasn't run since: {0}. Searching for any missing episodes since then.", message.LastExecutionTime.Value);
|
_logger.Info("RSS Sync hasn't run since: {0}. Searching for any missing episodes since then.", message.LastExecutionTime.Value);
|
||||||
_episodeSearchService.MissingEpisodesAiredAfter(message.LastExecutionTime.Value.AddDays(-1), processed.SelectMany(d => d.RemoteEpisode.Episodes).Select(e => e.Id));
|
_episodeSearchService.MissingEpisodesAiredAfter(message.LastExecutionTime.Value.AddDays(-1), grabbedOrPending.SelectMany(d => d.RemoteEpisode.Episodes).Select(e => e.Id));
|
||||||
}
|
}
|
||||||
|
|
||||||
_eventAggregator.PublishEvent(new RssSyncCompleteEvent());
|
_eventAggregator.PublishEvent(new RssSyncCompleteEvent(processed));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,6 @@ using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Common.Disk;
|
using NzbDrone.Common.Disk;
|
||||||
using NzbDrone.Core.Configuration;
|
|
||||||
using NzbDrone.Core.MediaFiles.EpisodeImport;
|
using NzbDrone.Core.MediaFiles.EpisodeImport;
|
||||||
using NzbDrone.Core.Parser;
|
using NzbDrone.Core.Parser;
|
||||||
using NzbDrone.Core.Tv;
|
using NzbDrone.Core.Tv;
|
||||||
|
@ -17,7 +16,9 @@ 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> ProcessFile(FileInfo fileInfo, 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
|
||||||
|
@ -26,7 +27,6 @@ namespace NzbDrone.Core.MediaFiles
|
||||||
private readonly IDiskScanService _diskScanService;
|
private readonly IDiskScanService _diskScanService;
|
||||||
private readonly ISeriesService _seriesService;
|
private readonly ISeriesService _seriesService;
|
||||||
private readonly IParsingService _parsingService;
|
private readonly IParsingService _parsingService;
|
||||||
private readonly IConfigService _configService;
|
|
||||||
private readonly IMakeImportDecision _importDecisionMaker;
|
private readonly IMakeImportDecision _importDecisionMaker;
|
||||||
private readonly IImportApprovedEpisodes _importApprovedEpisodes;
|
private readonly IImportApprovedEpisodes _importApprovedEpisodes;
|
||||||
private readonly ISampleService _sampleService;
|
private readonly ISampleService _sampleService;
|
||||||
|
@ -36,7 +36,6 @@ namespace NzbDrone.Core.MediaFiles
|
||||||
IDiskScanService diskScanService,
|
IDiskScanService diskScanService,
|
||||||
ISeriesService seriesService,
|
ISeriesService seriesService,
|
||||||
IParsingService parsingService,
|
IParsingService parsingService,
|
||||||
IConfigService configService,
|
|
||||||
IMakeImportDecision importDecisionMaker,
|
IMakeImportDecision importDecisionMaker,
|
||||||
IImportApprovedEpisodes importApprovedEpisodes,
|
IImportApprovedEpisodes importApprovedEpisodes,
|
||||||
ISampleService sampleService,
|
ISampleService sampleService,
|
||||||
|
@ -46,7 +45,6 @@ namespace NzbDrone.Core.MediaFiles
|
||||||
_diskScanService = diskScanService;
|
_diskScanService = diskScanService;
|
||||||
_seriesService = seriesService;
|
_seriesService = seriesService;
|
||||||
_parsingService = parsingService;
|
_parsingService = parsingService;
|
||||||
_configService = configService;
|
|
||||||
_importDecisionMaker = importDecisionMaker;
|
_importDecisionMaker = importDecisionMaker;
|
||||||
_importApprovedEpisodes = importApprovedEpisodes;
|
_importApprovedEpisodes = importApprovedEpisodes;
|
||||||
_sampleService = sampleService;
|
_sampleService = sampleService;
|
||||||
|
@ -73,6 +71,25 @@ namespace NzbDrone.Core.MediaFiles
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<ImportResult> ProcessFolder(DirectoryInfo directoryInfo, DownloadClientItem downloadClientItem = null)
|
public List<ImportResult> ProcessFolder(DirectoryInfo directoryInfo, DownloadClientItem downloadClientItem = null)
|
||||||
|
{
|
||||||
|
var cleanedUpName = GetCleanedUpFolderName(directoryInfo.Name);
|
||||||
|
var series = _parsingService.GetSeries(cleanedUpName);
|
||||||
|
|
||||||
|
if (series == null)
|
||||||
|
{
|
||||||
|
_logger.Debug("Unknown Series {0}", cleanedUpName);
|
||||||
|
|
||||||
|
return new List<ImportResult>
|
||||||
|
{
|
||||||
|
UnknownSeriesResult("Unknown Series")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return ProcessFolder(directoryInfo, series, downloadClientItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ImportResult> ProcessFolder(DirectoryInfo directoryInfo, Series series,
|
||||||
|
DownloadClientItem downloadClientItem = null)
|
||||||
{
|
{
|
||||||
if (_seriesService.SeriesPathExists(directoryInfo.FullName))
|
if (_seriesService.SeriesPathExists(directoryInfo.FullName))
|
||||||
{
|
{
|
||||||
|
@ -81,18 +98,9 @@ namespace NzbDrone.Core.MediaFiles
|
||||||
}
|
}
|
||||||
|
|
||||||
var cleanedUpName = GetCleanedUpFolderName(directoryInfo.Name);
|
var cleanedUpName = GetCleanedUpFolderName(directoryInfo.Name);
|
||||||
var series = _parsingService.GetSeries(cleanedUpName);
|
|
||||||
var quality = QualityParser.ParseQuality(cleanedUpName);
|
var quality = QualityParser.ParseQuality(cleanedUpName);
|
||||||
_logger.Debug("{0} folder quality: {1}", cleanedUpName, quality);
|
|
||||||
|
|
||||||
if (series == null)
|
_logger.Debug("{0} folder quality: {1}", cleanedUpName, quality);
|
||||||
{
|
|
||||||
_logger.Debug("Unknown Series {0}", cleanedUpName);
|
|
||||||
return new List<ImportResult>
|
|
||||||
{
|
|
||||||
new ImportResult(new ImportDecision(null, "Unknown Series"), "Unknown Series")
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
var videoFiles = _diskScanService.GetVideoFiles(directoryInfo.FullName);
|
var videoFiles = _diskScanService.GetVideoFiles(directoryInfo.FullName);
|
||||||
|
|
||||||
|
@ -102,20 +110,18 @@ namespace NzbDrone.Core.MediaFiles
|
||||||
{
|
{
|
||||||
if (_diskProvider.IsFileLocked(videoFile))
|
if (_diskProvider.IsFileLocked(videoFile))
|
||||||
{
|
{
|
||||||
_logger.Debug("[{0}] is currently locked by another process, skipping", videoFile);
|
|
||||||
return new List<ImportResult>
|
return new List<ImportResult>
|
||||||
{
|
{
|
||||||
new ImportResult(new ImportDecision(new LocalEpisode { Path = videoFile }, "Locked file, try again later"), "Locked file, try again later")
|
FileIsLockedResult(videoFile)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var decisions = _importDecisionMaker.GetImportDecisions(videoFiles.ToList(), series, true, quality);
|
var decisions = _importDecisionMaker.GetImportDecisions(videoFiles.ToList(), series, true, quality);
|
||||||
|
|
||||||
var importResults = _importApprovedEpisodes.Import(decisions, true, downloadClientItem);
|
var importResults = _importApprovedEpisodes.Import(decisions, true, downloadClientItem);
|
||||||
|
|
||||||
if ((downloadClientItem == null || !downloadClientItem.IsReadOnly) && importResults.Any() && ShouldDeleteFolder(directoryInfo))
|
if ((downloadClientItem == null || !downloadClientItem.IsReadOnly) && importResults.Any() && ShouldDeleteFolder(directoryInfo, series))
|
||||||
{
|
{
|
||||||
_logger.Debug("Deleting folder after importing valid files");
|
_logger.Debug("Deleting folder after importing valid files");
|
||||||
_diskProvider.DeleteFolder(directoryInfo.FullName, true);
|
_diskProvider.DeleteFolder(directoryInfo.FullName, true);
|
||||||
|
@ -131,15 +137,26 @@ namespace NzbDrone.Core.MediaFiles
|
||||||
if (series == null)
|
if (series == null)
|
||||||
{
|
{
|
||||||
_logger.Debug("Unknown Series for file: {0}", fileInfo.Name);
|
_logger.Debug("Unknown Series for file: {0}", fileInfo.Name);
|
||||||
return new List<ImportResult>() { new ImportResult(new ImportDecision(new LocalEpisode { Path = fileInfo.FullName }, "Unknown Series"), String.Format("Unknown Series for file: {0}", fileInfo.Name)) };
|
|
||||||
|
return new List<ImportResult>
|
||||||
|
{
|
||||||
|
UnknownSeriesResult(String.Format("Unknown Series for file: {0}", fileInfo.Name), fileInfo.FullName)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return ProcessFile(fileInfo, series, downloadClientItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ImportResult> ProcessFile(FileInfo fileInfo, Series series, DownloadClientItem downloadClientItem = null)
|
||||||
|
{
|
||||||
if (downloadClientItem == null)
|
if (downloadClientItem == null)
|
||||||
{
|
{
|
||||||
if (_diskProvider.IsFileLocked(fileInfo.FullName))
|
if (_diskProvider.IsFileLocked(fileInfo.FullName))
|
||||||
{
|
{
|
||||||
_logger.Debug("[{0}] is currently locked by another process, skipping", fileInfo.FullName);
|
return new List<ImportResult>
|
||||||
return new List<ImportResult>();
|
{
|
||||||
|
FileIsLockedResult(fileInfo.FullName)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,11 +172,9 @@ namespace NzbDrone.Core.MediaFiles
|
||||||
return folder;
|
return folder;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool ShouldDeleteFolder(DirectoryInfo directoryInfo)
|
private bool ShouldDeleteFolder(DirectoryInfo directoryInfo, Series series)
|
||||||
{
|
{
|
||||||
var videoFiles = _diskScanService.GetVideoFiles(directoryInfo.FullName);
|
var videoFiles = _diskScanService.GetVideoFiles(directoryInfo.FullName);
|
||||||
var cleanedUpName = GetCleanedUpFolderName(directoryInfo.Name);
|
|
||||||
var series = _parsingService.GetSeries(cleanedUpName);
|
|
||||||
|
|
||||||
foreach (var videoFile in videoFiles)
|
foreach (var videoFile in videoFiles)
|
||||||
{
|
{
|
||||||
|
@ -184,5 +199,18 @@ namespace NzbDrone.Core.MediaFiles
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ImportResult FileIsLockedResult(string videoFile)
|
||||||
|
{
|
||||||
|
_logger.Debug("[{0}] is currently locked by another process, skipping", videoFile);
|
||||||
|
return new ImportResult(new ImportDecision(new LocalEpisode { Path = videoFile }, "Locked file, try again later"), "Locked file, try again later");
|
||||||
|
}
|
||||||
|
|
||||||
|
private ImportResult UnknownSeriesResult(string message, string videoFile = null)
|
||||||
|
{
|
||||||
|
var localEpisode = videoFile == null ? null : new LocalEpisode { Path = videoFile };
|
||||||
|
|
||||||
|
return new ImportResult(new ImportDecision(localEpisode, "Unknown Series"), message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,40 +59,40 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var parsedEpisode = _parsingService.GetLocalEpisode(file, series, sceneSource);
|
var localEpisode = _parsingService.GetLocalEpisode(file, series, sceneSource);
|
||||||
|
|
||||||
if (parsedEpisode != null)
|
if (localEpisode != null)
|
||||||
{
|
{
|
||||||
if (quality != null &&
|
if (quality != null &&
|
||||||
new QualityModelComparer(parsedEpisode.Series.Profile).Compare(quality,
|
new QualityModelComparer(localEpisode.Series.Profile).Compare(quality,
|
||||||
parsedEpisode.Quality) > 0)
|
localEpisode.Quality) > 0)
|
||||||
{
|
{
|
||||||
_logger.Debug("Using quality from folder: {0}", quality);
|
_logger.Debug("Using quality from folder: {0}", quality);
|
||||||
parsedEpisode.Quality = quality;
|
localEpisode.Quality = quality;
|
||||||
}
|
}
|
||||||
|
|
||||||
parsedEpisode.Size = _diskProvider.GetFileSize(file);
|
localEpisode.Size = _diskProvider.GetFileSize(file);
|
||||||
_logger.Debug("Size: {0}", parsedEpisode.Size);
|
_logger.Debug("Size: {0}", localEpisode.Size);
|
||||||
|
|
||||||
parsedEpisode.MediaInfo = _videoFileInfoReader.GetMediaInfo(file);
|
localEpisode.MediaInfo = _videoFileInfoReader.GetMediaInfo(file);
|
||||||
|
|
||||||
decision = GetDecision(parsedEpisode);
|
decision = GetDecision(localEpisode);
|
||||||
}
|
}
|
||||||
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
parsedEpisode = new LocalEpisode();
|
localEpisode = new LocalEpisode();
|
||||||
parsedEpisode.Path = file;
|
localEpisode.Path = file;
|
||||||
|
|
||||||
decision = new ImportDecision(parsedEpisode, "Unable to parse file");
|
decision = new ImportDecision(localEpisode, "Unable to parse file");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (EpisodeNotFoundException e)
|
catch (EpisodeNotFoundException e)
|
||||||
{
|
{
|
||||||
var parsedEpisode = new LocalEpisode();
|
var localEpisode = new LocalEpisode();
|
||||||
parsedEpisode.Path = file;
|
localEpisode.Path = file;
|
||||||
|
|
||||||
decision = new ImportDecision(parsedEpisode, e.Message);
|
decision = new ImportDecision(localEpisode, e.Message);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using NLog;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.MediaFiles.EpisodeImport
|
||||||
|
{
|
||||||
|
public class ManualImportService
|
||||||
|
{
|
||||||
|
public ManualImportService(Logger logger)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -543,6 +543,7 @@
|
||||||
<Compile Include="MediaFiles\EpisodeImport\ImportDecision.cs" />
|
<Compile Include="MediaFiles\EpisodeImport\ImportDecision.cs" />
|
||||||
<Compile Include="MediaFiles\EpisodeImport\ImportDecisionMaker.cs" />
|
<Compile Include="MediaFiles\EpisodeImport\ImportDecisionMaker.cs" />
|
||||||
<Compile Include="MediaFiles\EpisodeImport\ImportResultType.cs" />
|
<Compile Include="MediaFiles\EpisodeImport\ImportResultType.cs" />
|
||||||
|
<Compile Include="MediaFiles\EpisodeImport\ManualImportService.cs" />
|
||||||
<Compile Include="MediaFiles\EpisodeImport\SampleService.cs" />
|
<Compile Include="MediaFiles\EpisodeImport\SampleService.cs" />
|
||||||
<Compile Include="MediaFiles\EpisodeImport\Specifications\FreeSpaceSpecification.cs" />
|
<Compile Include="MediaFiles\EpisodeImport\Specifications\FreeSpaceSpecification.cs" />
|
||||||
<Compile Include="MediaFiles\EpisodeImport\Specifications\FullSeasonSpecification.cs" />
|
<Compile Include="MediaFiles\EpisodeImport\Specifications\FullSeasonSpecification.cs" />
|
||||||
|
|
|
@ -8,6 +8,7 @@ namespace NzbDrone.Core.Queue
|
||||||
public interface IQueueService
|
public interface IQueueService
|
||||||
{
|
{
|
||||||
List<Queue> GetQueue();
|
List<Queue> GetQueue();
|
||||||
|
Queue Find(int id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class QueueService : IQueueService
|
public class QueueService : IQueueService
|
||||||
|
@ -28,6 +29,11 @@ namespace NzbDrone.Core.Queue
|
||||||
return MapQueue(queueItems);
|
return MapQueue(queueItems);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Queue Find(int id)
|
||||||
|
{
|
||||||
|
return GetQueue().SingleOrDefault(q => q.Id == id);
|
||||||
|
}
|
||||||
|
|
||||||
private List<Queue> MapQueue(IEnumerable<TrackedDownload> trackedDownloads)
|
private List<Queue> MapQueue(IEnumerable<TrackedDownload> trackedDownloads)
|
||||||
{
|
{
|
||||||
var queued = new List<Queue>();
|
var queued = new List<Queue>();
|
||||||
|
|
|
@ -0,0 +1,93 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
define(
|
||||||
|
[
|
||||||
|
'jquery',
|
||||||
|
'marionette',
|
||||||
|
'Cells/NzbDroneCell'
|
||||||
|
], function ($, Marionette, NzbDroneCell) {
|
||||||
|
return NzbDroneCell.extend({
|
||||||
|
|
||||||
|
className : 'queue-actions-cell',
|
||||||
|
|
||||||
|
events: {
|
||||||
|
'click .x-remove' : '_remove',
|
||||||
|
'click .x-import' : '_import',
|
||||||
|
'click .x-grab' : '_grab'
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function () {
|
||||||
|
this.$el.empty();
|
||||||
|
|
||||||
|
if (this.cellValue) {
|
||||||
|
var status = this.cellValue.get('status').toLowerCase();
|
||||||
|
var trackedDownloadStatus = this.cellValue.has('trackedDownloadStatus') ? this.cellValue.get('trackedDownloadStatus').toLowerCase() : 'ok';
|
||||||
|
var icon = '';
|
||||||
|
var title = '';
|
||||||
|
|
||||||
|
if (status === 'completed' && trackedDownloadStatus === 'warning') {
|
||||||
|
icon = 'icon-inbox x-import';
|
||||||
|
title = 'Force import';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status === 'pending') {
|
||||||
|
icon = 'icon-download-alt x-grab';
|
||||||
|
title = 'Add to download queue (Override Delay Profile)';
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: Show manual import if its completed or option to blacklist
|
||||||
|
//if (trackedDownloadStatus === 'error') {
|
||||||
|
// if (status === 'completed') {
|
||||||
|
// icon = 'icon-nd-import-failed';
|
||||||
|
// title = 'Import failed: ' + itemTitle;
|
||||||
|
// }
|
||||||
|
//TODO: What do we show when waiting for retry to take place?
|
||||||
|
|
||||||
|
// else {
|
||||||
|
// icon = 'icon-nd-download-failed';
|
||||||
|
// title = 'Download failed';
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
this.$el.html('<i class="{0}" title="{1}"></i>'.format(icon, title) +
|
||||||
|
'<i class="icon-nd-delete x-remove" title="Remove from Download Client"></i>');
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
_remove : function () {
|
||||||
|
this.model.destroy();
|
||||||
|
},
|
||||||
|
|
||||||
|
_import : function () {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
var promise = $.ajax({
|
||||||
|
url: window.NzbDrone.ApiRoot + '/queue/import',
|
||||||
|
type: 'POST',
|
||||||
|
data: JSON.stringify(this.model.toJSON())
|
||||||
|
});
|
||||||
|
|
||||||
|
promise.success(function () {
|
||||||
|
//find models that have the same series id and episode ids and remove them
|
||||||
|
self.model.trigger('destroy', self.model);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_grab : function () {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
var promise = $.ajax({
|
||||||
|
url: window.NzbDrone.ApiRoot + '/queue/grab',
|
||||||
|
type: 'POST',
|
||||||
|
data: JSON.stringify(this.model.toJSON())
|
||||||
|
});
|
||||||
|
|
||||||
|
promise.success(function () {
|
||||||
|
//find models that have the same series id and episode ids and remove them
|
||||||
|
self.model.trigger('destroy', self.model);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
|
@ -9,6 +9,7 @@ define(
|
||||||
'Cells/EpisodeTitleCell',
|
'Cells/EpisodeTitleCell',
|
||||||
'Cells/QualityCell',
|
'Cells/QualityCell',
|
||||||
'Activity/Queue/QueueStatusCell',
|
'Activity/Queue/QueueStatusCell',
|
||||||
|
'Activity/Queue/QueueActionsCell',
|
||||||
'Activity/Queue/TimeleftCell',
|
'Activity/Queue/TimeleftCell',
|
||||||
'Activity/Queue/ProgressCell',
|
'Activity/Queue/ProgressCell',
|
||||||
'Shared/Grid/Pager'
|
'Shared/Grid/Pager'
|
||||||
|
@ -20,6 +21,7 @@ define(
|
||||||
EpisodeTitleCell,
|
EpisodeTitleCell,
|
||||||
QualityCell,
|
QualityCell,
|
||||||
QueueStatusCell,
|
QueueStatusCell,
|
||||||
|
QueueActionsCell,
|
||||||
TimeleftCell,
|
TimeleftCell,
|
||||||
ProgressCell,
|
ProgressCell,
|
||||||
GridPager) {
|
GridPager) {
|
||||||
|
@ -74,6 +76,12 @@ define(
|
||||||
label : 'Progress',
|
label : 'Progress',
|
||||||
cell : ProgressCell,
|
cell : ProgressCell,
|
||||||
cellValue : 'this'
|
cellValue : 'this'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name : 'status',
|
||||||
|
label : '',
|
||||||
|
cell : QueueActionsCell,
|
||||||
|
cellValue : 'this'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|
|
@ -145,26 +145,36 @@ td.episode-status-cell, td.quality-cell, td.history-quality-cell, td.progress-ce
|
||||||
}
|
}
|
||||||
|
|
||||||
.timeleft-cell {
|
.timeleft-cell {
|
||||||
cursor : default;
|
cursor : default;
|
||||||
width : 80px;
|
width : 80px;
|
||||||
text-align: center;
|
text-align : center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.queue-status-cell {
|
.queue-status-cell {
|
||||||
width: 20px;
|
width : 20px;
|
||||||
text-align: center !important;
|
text-align : center !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.queue-actions-cell {
|
||||||
|
width : 55px;
|
||||||
|
text-align : right !important;
|
||||||
|
|
||||||
|
i {
|
||||||
|
margin-left : 3px;
|
||||||
|
margin-right : 3px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.download-log-cell {
|
.download-log-cell {
|
||||||
width: 80px;
|
width : 80px;
|
||||||
}
|
}
|
||||||
|
|
||||||
td.delete-episode-file-cell {
|
td.delete-episode-file-cell {
|
||||||
.clickable();
|
.clickable();
|
||||||
|
|
||||||
text-align: center;
|
text-align : center;
|
||||||
width: 20px;
|
width : 20px;
|
||||||
font-size: 20px;
|
font-size : 20px;
|
||||||
|
|
||||||
i {
|
i {
|
||||||
.clickable();
|
.clickable();
|
||||||
|
|
|
@ -24,7 +24,8 @@ define(
|
||||||
'slide .x-slider': '_updateSize'
|
'slide .x-slider': '_updateSize'
|
||||||
},
|
},
|
||||||
|
|
||||||
initialize: function () {
|
initialize: function (options) {
|
||||||
|
this.profileCollection = options.profiles;
|
||||||
this.filesize = fileSize;
|
this.filesize = fileSize;
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in New Issue